Repository: prebid/prebid-server
Pull request #1384 (2020-07-07 08:34:12)

Pubmatic: Support for video duration and primary category


Patch:
diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go
index 3aeea62e..69f1c12f 100644
--- a/adapters/adform/adform.go
+++ b/adapters/adform/adform.go
@@ -42,6 +42,7 @@ type adformRequest struct {
 	consent       string
 	digitrust     *adformDigitrust
 	currency      string
+	eids          string
 }
 
 type adformDigitrust struct {
@@ -279,6 +280,9 @@ func (r *adformRequest) buildAdformUrl(a *AdformAdapter) string {
 
 	parameters.Add("gdpr", r.gdprApplies)
 	parameters.Add("gdpr_consent", r.consent)
+	if r.eids != "" {
+		parameters.Add("eids", r.eids)
+	}
 
 	URL := *a.URL
 	URL.RawQuery = parameters.Encode()
@@ -465,6 +469,7 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro
 		}
 	}
 
+	eids := ""
 	consent := ""
 	var digitrustData *openrtb_ext.ExtUserDigiTrust
 	if request.User != nil {
@@ -472,6 +477,7 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro
 		if err := json.Unmarshal(request.User.Ext, &extUser); err == nil {
 			consent = extUser.Consent
 			digitrustData = extUser.DigiTrust
+			eids = encodeEids(extUser.Eids)
 		}
 	}
 
@@ -513,9 +519,34 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro
 		consent:       consent,
 		digitrust:     digitrust,
 		currency:      requestCurrency,
+		eids:          eids,
 	}, errors
 }
 
+func encodeEids(eids []openrtb_ext.ExtUserEid) string {
+	if eids == nil {
+		return ""
+	}
+
+	eidsMap := make(map[string]map[string][]int)
+	for _, eid := range eids {
+		_, ok := eidsMap[eid.Source]
+		if !ok {
+			eidsMap[eid.Source] = make(map[string][]int)
+		}
+		for _, uid := range eid.Uids {
+			eidsMap[eid.Source][uid.ID] = append(eidsMap[eid.Source][uid.ID], uid.Atype)
+		}
+	}
+
+	encodedEids := ""
+	if eidsString, err := json.Marshal(eidsMap); err == nil {
+		encodedEids = base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(eidsString)
+	}
+
+	return encodedEids
+}
+
 func getIPSafely(device *openrtb.Device) string {
 	if device == nil {
 		return ""
diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go
index 4b1215de..054fa7f6 100644
--- a/adapters/adgeneration/adgeneration.go
+++ b/adapters/adgeneration/adgeneration.go
@@ -210,6 +210,7 @@ func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb.BidRequest, ex
 				Bid:     &bid,
 				BidType: bitType,
 			})
+			bidResponse.Currency = adg.getCurrency(internalRequest)
 			return bidResponse, nil
 		}
 	}
@@ -254,7 +255,7 @@ func removeWrapper(ad string) string {
 func NewAdgenerationAdapter(endpoint string) *AdgenerationAdapter {
 	return &AdgenerationAdapter{
 		endpoint,
-		"1.0.0",
+		"1.0.1",
 		"JPY",
 	}
 }
diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go
index f4091e4e..d9f6719f 100644
--- a/adapters/audienceNetwork/facebook.go
+++ b/adapters/audienceNetwork/facebook.go
@@ -54,6 +54,12 @@ func (this *FacebookAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *
 		}}
 	}
 
+	if request.Site != nil {
+		return nil, []error{&errortypes.BadInput{
+			Message: "Site impressions are not supported.",
+		}}
+	}
+
 	return this.buildRequests(request)
 }
 
@@ -143,10 +149,6 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error {
 		app := *out.App
 		app.Publisher = &openrtb.Publisher{ID: pubId}
 		out.App = &app
-	} else {
-		site := *out.Site
-		site.Publisher = &openrtb.Publisher{ID: pubId}
-		out.Site = &site
 	}
 
 	if err = this.modifyImp(imp); err != nil {
@@ -468,15 +470,11 @@ func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (*
 		return &adapters.RequestData{}, []error{err}
 	}
 
-	// The publisher ID is either in the app object or the site object, depending on the supply of the request so we need
-	// to check both
+	// The publisher ID is expected in the app object
 	pubID, err = jsonparser.GetString(req.Body, "app", "publisher", "id")
 	if err != nil {
-		pubID, err = jsonparser.GetString(req.Body, "site", "publisher", "id")
-		if err != nil {
-			return &adapters.RequestData{}, []error{
-				errors.New("path [app|site].publisher.id not found in the request"),
-			}
+		return &adapters.RequestData{}, []error{
+			errors.New("path app.publisher.id not found in the request"),
 		}
 	}
 
diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go
index 6b4f698d..de33bd39 100644
--- a/adapters/dmx/dmx.go
+++ b/adapters/dmx/dmx.go
@@ -160,7 +160,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte
 	}
 
 	headers := http.Header{}
-	headers.Add("Content-Type", "Application/json;charset=utf-8")
+	headers.Add("Content-Type", "application/json;charset=utf-8")
 	reqBidder := &adapters.RequestData{
 		Method:  "POST",
 		Uri:     adapter.endpoint + addParams(sellerId), //adapter.endpoint,
diff --git a/adapters/logicad/logicad.go b/adapters/logicad/logicad.go
new file mode 100644
index 00000000..e757705a
--- /dev/null
+++ b/adapters/logicad/logicad.go
@@ -0,0 +1,155 @@
+package logicad
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type LogicadAdapter struct {
+	endpoint string
+}
+
+func (adapter *LogicadAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	if len(request.Imp) == 0 {
+		return nil, []error{&errortypes.BadInput{Message: "No impression in the bid request"}}
+	}
+
+	pub2impressions, imps, errs := getImpressionsInfo(request.Imp)
+	if len(pub2impressions) == 0 || len(imps) == 0 {
+		return nil, errs
+	}
+
+	result := make([]*adapters.RequestData, 0, len(pub2impressions))
+	for k, imps := range pub2impressions {
+		bidRequest, err := adapter.buildAdapterRequest(request, &k, imps)
+		if err != nil {
+			errs = append(errs, err)
+		} else {
+			result = append(result, bidRequest)
+		}
+	}
+	return result, errs
+}
+
+func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLogicad][]openrtb.Imp, []openrtb.Imp, []error) {
+	errors := make([]error, 0, len(imps))
+	resImps := make([]openrtb.Imp, 0, len(imps))
+	res := make(map[openrtb_ext.ExtImpLogicad][]openrtb.Imp)
+
+	for _, imp := range imps {
+		impExt, err := getImpressionExt(&imp)
+		if err != nil {
+			errors = append(errors, err)
+			continue
+		}
+		if err := validateImpression(&impExt); err != nil {
+			errors = append(errors, err)
+			continue
+		}
+
+		if res[impExt] == nil {
+			res[impExt] = make([]openrtb.Imp, 0)
+		}
+		res[impExt] = append(res[impExt], imp)
+		resImps = append(resImps, imp)
+	}
+	return res, resImps, errors
+}
+
+func validateImpression(impExt *openrtb_ext.ExtImpLogicad) error {
+	if impExt.Tid == "" {
+		return &errortypes.BadInput{Message: "No tid value provided"}
+	}
+	return nil
+}
+
+func getImpressionExt(imp *openrtb.Imp) (openrtb_ext.ExtImpLogicad, error) {
+	var bidderExt adapters.ExtImpBidder
+	var logicadExt openrtb_ext.ExtImpLogicad
+
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return logicadExt, &errortypes.BadInput{
+			Message: err.Error(),
+		}
+	}
+	if err := json.Unmarshal(bidderExt.Bidder, &logicadExt); err != nil {
+		return logicadExt, &errortypes.BadInput{
+			Message: err.Error(),
+		}
+	}
+	return logicadExt, nil
+}
+
+func (adapter *LogicadAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb.Imp) (*adapters.RequestData, error) {
+	newBidRequest := createBidRequest(prebidBidRequest, params, imps)
+	reqJSON, err := json.Marshal(newBidRequest)
+	if err != nil {
+		return nil, err
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	return &adapters.RequestData{
+		Method:  "POST",
+		Uri:     adapter.endpoint,
+		Body:    reqJSON,
+		Headers: headers}, nil
+}
+
+func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb.Imp) *openrtb.BidRequest {
+	bidRequest := *prebidBidRequest
+	bidRequest.Imp = imps
+	for idx := range bidRequest.Imp {
+		imp := &bidRequest.Imp[idx]
+		imp.TagID = params.Tid
+		imp.Ext = nil
+	}
+	return &bidRequest
+}
+
+//MakeBids translates Logicad bid response to prebid-server specific format
+func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+	if response.StatusCode != http.StatusOK {
+		msg := fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)
+		return nil, []error{&errortypes.BadServerResponse{Message: msg}}
+
+	}
+	var bidResp openrtb.BidResponse
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		msg := fmt.Sprintf("Bad server response: %d", err)
+		return nil, []error{&errortypes.BadServerResponse{Message: msg}}
+	}
+	if len(bidResp.SeatBid) != 1 {
+		msg := fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid))
+		return nil, []error{&errortypes.BadServerResponse{Message: msg}}
+	}
+
+	seatBid := bidResp.SeatBid[0]
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seatBid.Bid))
+
+	for i := 0; i < len(seatBid.Bid); i++ {
+		bid := seatBid.Bid[i]
+		bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+			Bid:     &bid,
+			BidType: openrtb_ext.BidTypeBanner,
+		})
+	}
+	return bidResponse, nil
+}
+
+func NewLogicadBidder(endpoint string) adapters.Bidder {
+	return &LogicadAdapter{
+		endpoint: endpoint,
+	}
+}
diff --git a/adapters/logicad/usersync.go b/adapters/logicad/usersync.go
new file mode 100644
index 00000000..d26a197b
--- /dev/null
+++ b/adapters/logicad/usersync.go
@@ -0,0 +1,12 @@
+package logicad
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+func NewLogicadSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("logicad", 0, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index aae11538..79565504 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -21,7 +21,6 @@ import (
 )
 
 const MAX_IMPRESSIONS_PUBMATIC = 30
-const bidTypeExtKey = "BidType"
 
 type PubmaticAdapter struct {
 	http *adapters.HTTPAdapter
@@ -48,6 +47,15 @@ type pubmaticParams struct {
 	Keywords    map[string]string `json:"keywords,omitempty"`
 }
 
+type pubmaticBidExtVideo struct {
+	Duration *int `json:"duration,omitempty"`
+}
+
+type pubmaticBidExt struct {
+	BidType           *int                 `json:"BidType,omitempty"`
+	VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"`
+}
+
 const (
 	INVALID_PARAMS    = "Invalid BidParam"
 	MISSING_PUBID     = "Missing PubID"
@@ -289,7 +297,11 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder
 				DealId:      bid.DealID,
 			}
 
-			mediaType := getBidType(bid.Ext)
+			var bidExt pubmaticBidExt
+			mediaType := openrtb_ext.BidTypeBanner
+			if err := json.Unmarshal(bid.Ext, &bidExt); err == nil {
+				mediaType = getBidType(&bidExt)
+			}
 			pbid.CreativeMediaType = string(mediaType)
 
 			bids = append(bids, &pbid)
@@ -549,9 +561,24 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb.BidRequest, external
 	for _, sb := range bidResp.SeatBid {
 		for i := 0; i < len(sb.Bid); i++ {
 			bid := sb.Bid[i]
+			impVideo := &openrtb_ext.ExtBidPrebidVideo{}
+
+			if len(bid.Cat) > 1 {
+				bid.Cat = bid.Cat[0:1]
+			}
+
+			var bidExt *pubmaticBidExt
+			bidType := openrtb_ext.BidTypeBanner
+			if err := json.Unmarshal(bid.Ext, &bidExt); err == nil && bidExt != nil {
+				if bidExt.VideoCreativeInfo != nil && bidExt.VideoCreativeInfo.Duration != nil {
+					impVideo.Duration = *bidExt.VideoCreativeInfo.Duration
+				}
+				bidType = getBidType(bidExt)
+			}
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
-				Bid:     &bid,
-				BidType: getBidType(bid.Ext),
+				Bid:      &bid,
+				BidType:  bidType,
+				BidVideo: impVideo,
 			})
 
 		}
@@ -560,28 +587,20 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb.BidRequest, external
 }
 
 // getBidType returns the bid type specified in the response bid.ext
-func getBidType(bidExt json.RawMessage) openrtb_ext.BidType {
+func getBidType(bidExt *pubmaticBidExt) openrtb_ext.BidType {
 	// setting "banner" as the default bid type
 	bidType := openrtb_ext.BidTypeBanner
-	if bidExt != nil {
-		bidExtMap := make(map[string]interface{})
-		extbyte, err := json.Marshal(bidExt)
-		if err == nil {
-			err = json.Unmarshal(extbyte, &bidExtMap)
-			if err == nil && bidExtMap[bidTypeExtKey] != nil {
-				bidTypeVal := int(bidExtMap[bidTypeExtKey].(float64))
-				switch bidTypeVal {
-				case 0:
-					bidType = openrtb_ext.BidTypeBanner
-				case 1:
-					bidType = openrtb_ext.BidTypeVideo
-				case 2:
-					bidType = openrtb_ext.BidTypeNative
-				default:
-					// default value is banner
-					bidType = openrtb_ext.BidTypeBanner
-				}
-			}
+	if bidExt != nil && bidExt.BidType != nil {
+		switch *bidExt.BidType {
+		case 0:
+			bidType = openrtb_ext.BidTypeBanner
+		case 1:
+			bidType = openrtb_ext.BidTypeVideo
+		case 2:
+			bidType = openrtb_ext.BidTypeNative
+		default:
+			// default value is banner
+			bidType = openrtb_ext.BidTypeBanner
 		}
 	}
 	return bidType
diff --git a/config/config.go b/config/config.go
index cc1d4a0a..a82dbb5e 100755
--- a/config/config.go
+++ b/config/config.go
@@ -379,6 +379,11 @@ type Metrics struct {
 type DisabledMetrics struct {
 	// True if we want to stop collecting account-to-adapter metrics
 	AccountAdapterDetails bool `mapstructure:"account_adapter_details"`
+
+	// True if we don't want to collect metrics about the connections prebid
+	// server establishes with bidder servers such as the number of connections
+	// that were created or reused.
+	AdapterConnectionMetrics bool `mapstructure:"adapter_connections_metrics"`
 }
 
 func (cfg *Metrics) validate(errs configErrors) configErrors {
@@ -597,6 +602,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=184932&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLifestreet, "https://ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D")
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLogicad, "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D")
@@ -687,6 +693,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60)
 	// no metrics configured by default (metrics{host|database|username|password})
 	v.SetDefault("metrics.disabled_metrics.account_adapter_details", false)
+	v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true)
 	v.SetDefault("metrics.influxdb.host", "")
 	v.SetDefault("metrics.influxdb.database", "")
 	v.SetDefault("metrics.influxdb.username", "")
@@ -763,12 +770,8 @@ func SetupViper(v *viper.Viper, filename string) {
 	// Disabling adapters by default that require some specific config params.
 	// If you're using one of these, make sure you check out the documentation (https://github.com/prebid/prebid-server/tree/master/docs/bidders)
 	// for them and specify all the parameters they need for them to work correctly.
-	v.SetDefault("adapters.audiencenetwork.disabled", true)
-	v.SetDefault("adapters.rubicon.disabled", true)
 	v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb")
 	v.SetDefault("adapters.33across.partner_id", "")
-	v.SetDefault("adapters.dmx.endpoint", "https://dmx.districtm.io/b/v2")
-	v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction")
 	v.SetDefault("adapters.adform.endpoint", "http://adx.adform.net/adx")
 	v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1")
 	v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json")
@@ -786,6 +789,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid")
 	v.SetDefault("adapters.appnexus.endpoint", "http://ib.adnxs.com/openrtb2") // Docs: https://wiki.appnexus.com/display/supply/Incoming+Bid+Request+from+SSPs
 	v.SetDefault("adapters.appnexus.platform_id", "5")
+	v.SetDefault("adapters.audiencenetwork.disabled", true)
 	v.SetDefault("adapters.avocet.disabled", true)
 	v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display")
 	v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}")
@@ -795,6 +799,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25")
 	v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx")
 	v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
+	v.SetDefault("adapters.dmx.endpoint", "https://dmx-direct.districtm.io/b/v2")
 	v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com")
 	v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb")
 	v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1")
@@ -805,9 +810,10 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs")
 	v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932")
 	v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server")
-	v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid")
+	v.SetDefault("adapters.kubient.endpoint", "https://kssp.kbntx.ch/prebid")
 	v.SetDefault("adapters.lifestreet.endpoint", "https://prebid.s2s.lfstmedia.com/adrequest")
 	v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2")
+	v.SetDefault("adapters.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver")
 	v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}")
 	v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet")
 	v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/")
@@ -821,6 +827,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s")
 	v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp")
 	v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids")
+	v.SetDefault("adapters.rubicon.disabled", true)
 	v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json")
 	v.SetDefault("adapters.sharethrough.endpoint", "http://btlr.sharethrough.com/FGMrCMMc/v1")
 	v.SetDefault("adapters.smartadserver.endpoint", "https://ssb.smartadserver.com")
diff --git a/config/util/loggers.go b/config/util/loggers.go
index 88702e68..d9aad43a 100644
--- a/config/util/loggers.go
+++ b/config/util/loggers.go
@@ -4,18 +4,18 @@ import (
 	"math/rand"
 )
 
-type logMsg func(string, ...interface{})
+type LogMsg func(string, ...interface{})
 
 type randomGenerator func() float32
 
 // LogRandomSample will log a randam sample of the messages it is sent, based on the chance to log
 // chance = 1.0 => always log,
 // chance = 0.0 => never log
-func LogRandomSample(msg string, logger logMsg, chance float32) {
+func LogRandomSample(msg string, logger LogMsg, chance float32) {
 	logRandomSampleImpl(msg, logger, chance, rand.Float32)
 }
 
-func logRandomSampleImpl(msg string, logger logMsg, chance float32, randGenerator randomGenerator) {
+func logRandomSampleImpl(msg string, logger LogMsg, chance float32, randGenerator randomGenerator) {
 	if chance < 1.0 && randGenerator() > chance {
 		// this is the chance we don't log anything
 		return
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index e8b5d3ec..8efba5a9 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -388,6 +388,14 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope
 
 	setAmpExt(req.Site, "1")
 
+	account := httpRequest.FormValue("account")
+	if account != "" {
+		if req.Site.Publisher == nil {
+			req.Site.Publisher = &openrtb.Publisher{}
+		}
+		req.Site.Publisher.ID = account
+	}
+
 	slot := httpRequest.FormValue("slot")
 	if slot != "" {
 		req.Imp[0].TagID = slot
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 20acc2ae..86186fa8 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -39,6 +39,12 @@ import (
 
 const storedRequestTimeoutMillis = 50
 
+var (
+	dntKey      string = http.CanonicalHeaderKey("DNT")
+	dntDisabled int8   = 0
+	dntEnabled  int8   = 1
+)
+
 func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) {
 
 	if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil {
@@ -290,6 +296,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error {
 		if err := validateBidAdjustmentFactors(bidExt.Prebid.BidAdjustmentFactors, aliases); err != nil {
 			return []error{err}
 		}
+
+		if err := validateSChains(bidExt); err != nil {
+			return []error{err}
+		}
 	}
 
 	if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) {
@@ -362,6 +372,11 @@ func validateBidAdjustmentFactors(adjustmentFactors map[string]float64, aliases
 	return nil
 }
 
+func validateSChains(req *openrtb_ext.ExtRequest) error {
+	_, err := exchange.BidderToPrebidSChains(req)
+	return err
+}
+
 func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]string, index int) []error {
 	if imp.ID == "" {
 		return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)}
@@ -955,6 +970,8 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *ope
 func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValidtor iputil.IPValidator) {
 	setIPImplicitly(httpReq, bidReq, ipValidtor)
 	setUAImplicitly(httpReq, bidReq)
+	setDoNotTrackImplicitly(httpReq, bidReq)
+
 }
 
 // setAuctionTypeImplicitly sets the auction type to 1 if it wasn't on the request,
@@ -1183,6 +1200,24 @@ func setUAImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) {
 	}
 }
 
+func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) {
+	if bidReq.Device == nil || bidReq.Device.DNT == nil {
+		dnt := httpReq.Header.Get(dntKey)
+		if dnt == "0" || dnt == "1" {
+			if bidReq.Device == nil {
+				bidReq.Device = &openrtb.Device{}
+			}
+
+			switch dnt {
+			case "0":
+				bidReq.Device.DNT = &dntDisabled
+			case "1":
+				bidReq.Device.DNT = &dntEnabled
+			}
+		}
+	}
+}
+
 // parseUserID gets this user's ID for the host machine, if it exists.
 func parseUserID(cfg *config.Configuration, httpReq *http.Request) (string, bool) {
 	if hostCookie, err := httpReq.Cookie(cfg.HostCookie.CookieName); hostCookie != nil && err == nil {
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json
new file mode 100644
index 00000000..b1fae20a
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json
@@ -0,0 +1,45 @@
+{
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "device": {
+        "dnt": 1
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        }
+      }
+    }
+  }
+  
\ No newline at end of file
diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go
index 1f62d232..2ecddb83 100755
--- a/exchange/adapter_map.go
+++ b/exchange/adapter_map.go
@@ -48,6 +48,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/kubient"
 	"github.com/prebid/prebid-server/adapters/lifestreet"
 	"github.com/prebid/prebid-server/adapters/lockerdome"
+	"github.com/prebid/prebid-server/adapters/logicad"
 	"github.com/prebid/prebid-server/adapters/lunamedia"
 	"github.com/prebid/prebid-server/adapters/marsmedia"
 	"github.com/prebid/prebid-server/adapters/mgid"
@@ -133,6 +134,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 		openrtb_ext.BidderKubient:         kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint),
 		openrtb_ext.BidderLockerDome:      lockerdome.NewLockerDomeBidder(cfg.Adapters[string(openrtb_ext.BidderLockerDome)].Endpoint),
 		openrtb_ext.BidderLunaMedia:       lunamedia.NewLunaMediaBidder(cfg.Adapters[string(openrtb_ext.BidderLunaMedia)].Endpoint),
+		openrtb_ext.BidderLogicad:         logicad.NewLogicadBidder(cfg.Adapters[string(openrtb_ext.BidderLogicad)].Endpoint),
 		openrtb_ext.BidderMarsmedia:       marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint),
 		openrtb_ext.BidderMgid:            mgid.NewMgidBidder(cfg.Adapters[string(openrtb_ext.BidderMgid)].Endpoint),
 		openrtb_ext.BidderMobileFuse:      mobilefuse.NewMobileFuseBidder(cfg.Adapters[string(openrtb_ext.BidderMobileFuse)].Endpoint),
@@ -199,7 +201,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 	for name, bidder := range ortbBidders {
 		// Clean out any disabled bidders
 		if infos[string(name)].Status == adapters.StatusActive {
-			allBidders[name] = adaptBidder(adapters.EnforceBidderInfo(bidder, infos[string(name)]), client, cfg, me)
+			allBidders[name] = adaptBidder(adapters.EnforceBidderInfo(bidder, infos[string(name)]), client, cfg, me, name)
 		}
 	}
 
diff --git a/exchange/bidder.go b/exchange/bidder.go
index df9f0a3b..7c39b72b 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"net/http/httptrace"
 	"time"
 
 	"github.com/golang/glog"
@@ -87,20 +88,30 @@ type pbsOrtbSeatBid struct {
 //
 // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter"
 // (which is being phased out and replaced by Bidder for OpenRTB auctions)
-func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me pbsmetrics.MetricsEngine) adaptedBidder {
+func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me pbsmetrics.MetricsEngine, name openrtb_ext.BidderName) adaptedBidder {
 	return &bidderAdapter{
-		Bidder:      bidder,
-		Client:      client,
-		DebugConfig: cfg.Debug,
-		me:          me,
+		Bidder:     bidder,
+		BidderName: name,
+		Client:     client,
+		me:         me,
+		config: bidderAdapterConfig{
+			Debug:              cfg.Debug,
+			DisableConnMetrics: cfg.Metrics.Disabled.AdapterConnectionMetrics,
+		},
 	}
 }
 
 type bidderAdapter struct {
-	Bidder      adapters.Bidder
-	Client      *http.Client
-	DebugConfig config.Debug
-	me          pbsmetrics.MetricsEngine
+	Bidder     adapters.Bidder
+	BidderName openrtb_ext.BidderName
+	Client     *http.Client
+	me         pbsmetrics.MetricsEngine
+	config     bidderAdapterConfig
+}
+
+type bidderAdapterConfig struct {
+	Debug              config.Debug
+	DisableConnMetrics bool
 }
 
 func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currencies.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) {
@@ -312,6 +323,10 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall {
 // doRequest makes a request, handles the response, and returns the data needed by the
 // Bidder interface.
 func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData) *httpCallInfo {
+	return bidder.doRequestImpl(ctx, req, glog.Warningf)
+}
+
+func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg) *httpCallInfo {
 	httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(req.Body))
 	if err != nil {
 		return &httpCallInfo{
@@ -321,16 +336,27 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques
 	}
 	httpReq.Header = req.Headers
 
+	// If adapter connection metrics are not disabled, add the client trace
+	// to get complete connection info into our metrics
+	if !bidder.config.DisableConnMetrics {
+		ctx = bidder.addClientTrace(ctx)
+	}
 	httpResp, err := ctxhttp.Do(ctx, bidder.Client, httpReq)
 	if err != nil {
 		if err == context.DeadlineExceeded {
 			err = &errortypes.Timeout{Message: err.Error()}
-			if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); ok {
+			var corebidder adapters.Bidder = bidder.Bidder
+			// The bidder adapter normally stores an info-aware bidder (a bidder wrapper)
+			// rather than the actual bidder. So we need to unpack that first.
+			if b, ok := corebidder.(*adapters.InfoAwareBidder); ok {
+				corebidder = b.Bidder
+			}
+			if tb, ok := corebidder.(adapters.TimeoutBidder); ok {
 				// Toss the timeout notification call into a go routine, as we are out of time'
 				// and cannot delay processing. We don't do anything result, as there is not much
 				// we can do about a timeout notification failure. We do not want to get stuck in
 				// a loop of trying to report timeouts to the timeout notifications.
-				go bidder.doTimeoutNotification(tb, req)
+				go bidder.doTimeoutNotification(tb, req, logger)
 			}
 
 		}
@@ -366,7 +392,7 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques
 	}
 }
 
-func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData) {
+func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData, logger util.LogMsg) {
 	ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
 	defer cancel()
 	toReq, errL := timeoutBidder.MakeTimeoutNotification(req)
@@ -377,7 +403,7 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou
 			httpResp, err := ctxhttp.Do(ctx, bidder.Client, httpReq)
 			success := (err == nil && httpResp.StatusCode >= 200 && httpResp.StatusCode < 300)
 			bidder.me.RecordTimeoutNotice(success)
-			if bidder.DebugConfig.TimeoutNotification.Log && !(bidder.DebugConfig.TimeoutNotification.FailOnly && success) {
+			if bidder.config.Debug.TimeoutNotification.Log && !(bidder.config.Debug.TimeoutNotification.FailOnly && success) {
 				var msg string
 				if err == nil {
 					msg = fmt.Sprintf("TimeoutNotification: status:(%d) body:%s", httpResp.StatusCode, string(toReq.Body))
@@ -385,16 +411,16 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou
 					msg = fmt.Sprintf("TimeoutNotification: error:(%s) body:%s", err.Error(), string(toReq.Body))
 				}
 				// If logging is turned on, and logging is not disallowed via FailOnly
-				util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate)
+				util.LogRandomSample(msg, logger, bidder.config.Debug.TimeoutNotification.SamplingRate)
 			}
 		} else {
 			bidder.me.RecordTimeoutNotice(false)
-			if bidder.DebugConfig.TimeoutNotification.Log {
+			if bidder.config.Debug.TimeoutNotification.Log {
 				msg := fmt.Sprintf("TimeoutNotification: Failed to make timeout request: method(%s), uri(%s), error(%s)", toReq.Method, toReq.Uri, err.Error())
-				util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate)
+				util.LogRandomSample(msg, logger, bidder.config.Debug.TimeoutNotification.SamplingRate)
 			}
 		}
-	} else if bidder.DebugConfig.TimeoutNotification.Log {
+	} else if bidder.config.Debug.TimeoutNotification.Log {
 		reqJSON, err := json.Marshal(req)
 		var msg string
 		if err == nil {
@@ -402,7 +428,7 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou
 		} else {
 			msg = fmt.Sprintf("TimeoutNotification: Failed to generate timeout request: error(%s), bidder request marshal failed(%s)", errL[0].Error(), err.Error())
 		}
-		util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate)
+		util.LogRandomSample(msg, logger, bidder.config.Debug.TimeoutNotification.SamplingRate)
 	}
 
 }
@@ -412,3 +438,34 @@ type httpCallInfo struct {
 	response *adapters.ResponseData
 	err      error
 }
+
+// This function adds an httptrace.ClientTrace object to the context so, if connection with the bidder
+// endpoint is established, we can keep track of whether the connection was newly created, reused, and
+// the time from the connection request, to the connection creation.
+func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context {
+	var connStart, dnsStart time.Time
+
+	trace := &httptrace.ClientTrace{
+		// GetConn is called before a connection is created or retrieved from an idle pool
+		GetConn: func(hostPort string) {
+			connStart = time.Now()
+		},
+		// GotConn is called after a successful connection is obtained
+		GotConn: func(info httptrace.GotConnInfo) {
+			connWaitTime := time.Now().Sub(connStart)
+
+			bidder.me.RecordAdapterConnections(bidder.BidderName, info.Reused, connWaitTime)
+		},
+		// DNSStart is called when a DNS lookup begins.
+		DNSStart: func(info httptrace.DNSStartInfo) {
+			dnsStart = time.Now()
+		},
+		// DNSDone is called when a DNS lookup ends.
+		DNSDone: func(info httptrace.DNSDoneInfo) {
+			dnsLookupTime := time.Now().Sub(dnsStart)
+
+			bidder.me.RecordDNSTime(dnsLookupTime)
+		},
+	}
+	return httptrace.WithClientTrace(ctx, trace)
+}
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 174a0b3e..3f0258dd 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -104,11 +104,10 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 
 	// Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder
 	blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels)
-	cleanRequests, aliases, cleanMetrics, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig)
+	cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig)
+
+	e.me.RecordRequestPrivacy(privacyLabels)
 
-	if cleanMetrics.gdprEnforced {
-		e.me.RecordTCFReq(pbsmetrics.TCFVersionToValue(cleanMetrics.gdprTcfVersion))
-	}
 	// List of bidders we have requests for.
 	liveAdapters := listBiddersWithRequests(cleanRequests)
 
diff --git a/exchange/utils.go b/exchange/utils.go
index 96c00ec0..bc1b555e 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -19,13 +19,27 @@ import (
 	"github.com/prebid/prebid-server/privacy/lmt"
 )
 
-// cleanMetrics is a struct to export any metrics data resulting from cleanOpenRTBRequests(). It starts with just
-// the TCF version, but made a struct to facilitate future expansion
-type cleanMetrics struct {
-	// A simple flag if GDPR is being enforced on this request.
-	gdprEnforced bool
-	// a zero value means a missing or invalid GDPR string
-	gdprTcfVersion int
+func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) {
+	bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain)
+
+	if len(req.Prebid.SChains) == 0 {
+		return bidderToSChains, nil
+	}
+
+	for _, schainWrapper := range req.Prebid.SChains {
+		if schainWrapper != nil && len(schainWrapper.Bidders) > 0 {
+			for _, bidder := range schainWrapper.Bidders {
+				if _, present := bidderToSChains[bidder]; present {
+					return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+
+						"it must contain no more than one per bidder.", bidder)
+				} else {
+					bidderToSChains[bidder] = &schainWrapper.SChain
+				}
+			}
+		}
+	}
+
+	return bidderToSChains, nil
 }
 
 // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is:
@@ -40,7 +54,7 @@ func cleanOpenRTBRequests(ctx context.Context,
 	labels pbsmetrics.Labels,
 	gDPR gdpr.Permissions,
 	usersyncIfAmbiguous bool,
-	privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, cleanMetrics cleanMetrics, errs []error) {
+	privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, privacyLabels pbsmetrics.PrivacyLabels, errs []error) {
 
 	impsByBidder, errs := splitImps(orig.Imp)
 	if len(errs) > 0 {
@@ -75,13 +89,20 @@ func cleanOpenRTBRequests(ctx context.Context,
 		LMT:   lmtPolicy.ShouldEnforce(),
 	}
 
+	privacyLabels.CCPAProvided = ccpaPolicy.Value != ""
+	privacyLabels.CCPAEnforced = privacyEnforcement.CCPA
+	privacyLabels.COPPAEnforced = privacyEnforcement.COPPA
+	privacyLabels.LMTEnforced = privacyEnforcement.LMT
+
 	if gdpr == 1 {
-		cleanMetrics.gdprEnforced = true
+		privacyLabels.GDPREnforced = true
 		parsedConsent, err := vendorconsent.ParseString(consent)
 		if err == nil {
-			cleanMetrics.gdprTcfVersion = int(parsedConsent.Version())
+			version := int(parsedConsent.Version())
+			privacyLabels.GDPRTCFVersion = pbsmetrics.TCFVersionToValue(version)
 		}
 	}
+
 	// bidder level privacy policies
 	for bidder, bidReq := range requestsByBidder {
 
@@ -103,12 +124,35 @@ func cleanOpenRTBRequests(ctx context.Context,
 	return
 }
 
-func splitBidRequest(req *openrtb.BidRequest, impsByBidder map[string][]openrtb.Imp, aliases map[string]string, usersyncs IdFetcher, blabels map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels, labels pbsmetrics.Labels) (map[openrtb_ext.BidderName]*openrtb.BidRequest, []error) {
+func splitBidRequest(req *openrtb.BidRequest,
+	impsByBidder map[string][]openrtb.Imp,
+	aliases map[string]string,
+	usersyncs IdFetcher,
+	blabels map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels,
+	labels pbsmetrics.Labels) (map[openrtb_ext.BidderName]*openrtb.BidRequest, []error) {
+
 	requestsByBidder := make(map[openrtb_ext.BidderName]*openrtb.BidRequest, len(impsByBidder))
 	explicitBuyerUIDs, err := extractBuyerUIDs(req.User)
 	if err != nil {
 		return nil, []error{err}
 	}
+
+	var requestExt openrtb_ext.ExtRequest
+	var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain
+	if len(req.Ext) > 0 {
+		err := json.Unmarshal(req.Ext, &requestExt)
+		if err != nil {
+			return nil, []error{err}
+		}
+
+		sChainsByBidder, err = BidderToPrebidSChains(&requestExt)
+		if err != nil {
+			return nil, []error{err}
+		}
+	} else {
+		sChainsByBidder = make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain)
+	}
+
 	for bidder, imps := range impsByBidder {
 		reqCopy := *req
 		coreBidder := resolveBidder(bidder, aliases)
@@ -128,11 +172,57 @@ func splitBidRequest(req *openrtb.BidRequest, impsByBidder map[string][]openrtb.
 			blabels[coreBidder].CookieFlag = pbsmetrics.CookieFlagYes
 		}
 		reqCopy.Imp = imps
+
+		prepareSource(&reqCopy, bidder, sChainsByBidder)
+		prepareExt(&reqCopy, &requestExt)
+
 		requestsByBidder[openrtb_ext.BidderName(bidder)] = &reqCopy
 	}
 	return requestsByBidder, nil
 }
 
+func prepareExt(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) {
+	if len(req.Ext) == 0 {
+		return
+	}
+	extCopy := *unpackedExt
+	extCopy.Prebid.SChains = nil
+	reqExt, err := json.Marshal(extCopy)
+	if err == nil {
+		req.Ext = reqExt
+	}
+}
+
+func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) {
+	const sChainWildCard = "*"
+	var selectedSChain *openrtb_ext.ExtRequestPrebidSChainSChain
+
+	wildCardSChain := sChainsByBidder[sChainWildCard]
+	bidderSChain := sChainsByBidder[bidder]
+
+	// source should not be modified
+	if bidderSChain == nil && wildCardSChain == nil {
+		return
+	}
+
+	if bidderSChain != nil {
+		selectedSChain = bidderSChain
+	} else {
+		selectedSChain = wildCardSChain
+	}
+
+	// set source
+	var source openrtb.Source
+	schain := openrtb_ext.ExtRequestPrebidSChain{
+		SChain: *selectedSChain,
+	}
+	sourceExt, err := json.Marshal(schain)
+	if err == nil {
+		source.Ext = sourceExt
+		req.Source = &source
+	}
+}
+
 // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext.
 // This prevents a Bidder from using these values to figure out who else is involved in the Auction.
 func extractBuyerUIDs(user *openrtb.User) (map[string]string, error) {
diff --git a/go.mod b/go.mod
index 72bb9b74..00cadd31 100644
--- a/go.mod
+++ b/go.mod
@@ -22,6 +22,7 @@ require (
 	github.com/influxdata/influxdb v1.6.1 // indirect
 	github.com/julienschmidt/httprouter v1.1.0
 	github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
+	github.com/kr/pretty v0.2.0 // indirect
 	github.com/lib/pq v1.0.0
 	github.com/magiconair/properties v1.8.0
 	github.com/mattn/go-colorable v0.1.2 // indirect
diff --git a/go.sum b/go.sum
index 35b2b765..5eaf37ca 100644
--- a/go.sum
+++ b/go.sum
@@ -45,6 +45,11 @@ github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4
 github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 49d7b09d..62fb9750 100755
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -64,6 +64,7 @@ const (
 	BidderKubient          BidderName = "kubient"
 	BidderLifestreet       BidderName = "lifestreet"
 	BidderLockerDome       BidderName = "lockerdome"
+	BidderLogicad          BidderName = "logicad"
 	BidderLunaMedia        BidderName = "lunamedia"
 	BidderMarsmedia        BidderName = "marsmedia"
 	BidderMgid             BidderName = "mgid"
@@ -145,6 +146,7 @@ var BidderMap = map[string]BidderName{
 	"kubient":           BidderKubient,
 	"lifestreet":        BidderLifestreet,
 	"lockerdome":        BidderLockerDome,
+	"logicad":           BidderLogicad,
 	"lunamedia":         BidderLunaMedia,
 	"marsmedia":         BidderMarsmedia,
 	"mgid":              BidderMgid,
diff --git a/openrtb_ext/imp_logicad.go b/openrtb_ext/imp_logicad.go
new file mode 100644
index 00000000..e4e3c3b0
--- /dev/null
+++ b/openrtb_ext/imp_logicad.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpLogicad struct {
+	Tid string `json:"tid"`
+}
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index 25b5c881..86388f60 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -12,12 +12,43 @@ type ExtRequest struct {
 
 // ExtRequestPrebid defines the contract for bidrequest.ext.prebid
 type ExtRequestPrebid struct {
-	Aliases              map[string]string      `json:"aliases,omitempty"`
-	BidAdjustmentFactors map[string]float64     `json:"bidadjustmentfactors,omitempty"`
-	Cache                *ExtRequestPrebidCache `json:"cache,omitempty"`
-	StoredRequest        *ExtStoredRequest      `json:"storedrequest,omitempty"`
-	Targeting            *ExtRequestTargeting   `json:"targeting,omitempty"`
-	SupportDeals         bool                   `json:"supportdeals,omitempty"`
+	Aliases              map[string]string         `json:"aliases,omitempty"`
+	BidAdjustmentFactors map[string]float64        `json:"bidadjustmentfactors,omitempty"`
+	Cache                *ExtRequestPrebidCache    `json:"cache,omitempty"`
+	SChains              []*ExtRequestPrebidSChain `json:"schains,omitempty"`
+	StoredRequest        *ExtStoredRequest         `json:"storedrequest,omitempty"`
+	Targeting            *ExtRequestTargeting      `json:"targeting,omitempty"`
+	SupportDeals         bool                      `json:"supportdeals,omitempty"`
+}
+
+// ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains
+type ExtRequestPrebidSChain struct {
+	Bidders []string                     `json:"bidders,omitempty"`
+	SChain  ExtRequestPrebidSChainSChain `json:"schain"`
+}
+
+// ExtRequestPrebidSChainSChain defines the contract for bidrequest.ext.prebid.schains[i].schain
+type ExtRequestPrebidSChainSChain struct {
+	Complete int                                 `json:"complete"`
+	Nodes    []*ExtRequestPrebidSChainSChainNode `json:"nodes"`
+	Ver      string                              `json:"ver"`
+	Ext      json.RawMessage                     `json:"ext,omitempty"`
+}
+
+// ExtRequestPrebidSChainSChainNode defines the contract for bidrequest.ext.prebid.schains[i].schain[i].nodes
+type ExtRequestPrebidSChainSChainNode struct {
+	ASI    string          `json:"asi"`
+	SID    string          `json:"sid"`
+	RID    string          `json:"rid,omitempty"`
+	Name   string          `json:"name,omitempty"`
+	Domain string          `json:"domain,omitempty"`
+	HP     int             `json:"hp"`
+	Ext    json.RawMessage `json:"ext,omitempty"`
+}
+
+// SourceExt defines the contract for bidrequest.source.ext
+type SourceExt struct {
+	SChain ExtRequestPrebidSChainSChain `json:"schain"`
 }
 
 // ExtRequestPrebidCache defines the contract for bidrequest.ext.prebid.cache
diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go
index 520d73a6..b83f8233 100644
--- a/openrtb_ext/user.go
+++ b/openrtb_ext/user.go
@@ -43,6 +43,7 @@ type ExtUserEid struct {
 
 // ExtUserEidUid defines the contract for bidrequest.user.ext.eids[i].uids[j]
 type ExtUserEidUid struct {
-	ID  string          `json:"id"`
-	Ext json.RawMessage `json:"ext,omitempty"`
+	ID    string          `json:"id"`
+	Atype int             `json:"atype,omitempty"`
+	Ext   json.RawMessage `json:"ext,omitempty"`
 }
diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go
index 3d105dea..6a36f9e7 100644
--- a/pbsmetrics/config/metrics.go
+++ b/pbsmetrics/config/metrics.go
@@ -37,7 +37,7 @@ func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.Bidde
 	}
 	if cfg.Metrics.Prometheus.Port != 0 {
 		// Set up the Prometheus metrics.
-		returnEngine.PrometheusMetrics = prometheusmetrics.NewMetrics(cfg.Metrics.Prometheus)
+		returnEngine.PrometheusMetrics = prometheusmetrics.NewMetrics(cfg.Metrics.Prometheus, cfg.Metrics.Disabled)
 		engineList = append(engineList, returnEngine.PrometheusMetrics)
 	}
 
@@ -118,6 +118,21 @@ func (me *MultiMetricsEngine) RecordAdapterRequest(labels pbsmetrics.AdapterLabe
 	}
 }
 
+// Keeps track of created and reused connections to adapter bidders and the time from the
+// connection request, to the connection creation, or reuse from the pool across all engines
+func (me *MultiMetricsEngine) RecordAdapterConnections(bidderName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) {
+	for _, thisME := range *me {
+		thisME.RecordAdapterConnections(bidderName, connWasReused, connWaitTime)
+	}
+}
+
+// Times the DNS resolution process
+func (me *MultiMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) {
+	for _, thisME := range *me {
+		thisME.RecordDNSTime(dnsLookupTime)
+	}
+}
+
 // RecordAdapterBidReceived across all engines
 func (me *MultiMetricsEngine) RecordAdapterBidReceived(labels pbsmetrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) {
 	for _, thisME := range *me {
@@ -195,10 +210,10 @@ func (me *MultiMetricsEngine) RecordTimeoutNotice(success bool) {
 	}
 }
 
-// RecordTCFReq across all engines
-func (me *MultiMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) {
+// RecordRequestPrivacy across all engines
+func (me *MultiMetricsEngine) RecordRequestPrivacy(privacy pbsmetrics.PrivacyLabels) {
 	for _, thisME := range *me {
-		thisME.RecordTCFReq(version)
+		thisME.RecordRequestPrivacy(privacy)
 	}
 }
 
@@ -237,6 +252,14 @@ func (me *DummyMetricsEngine) RecordAdapterPanic(labels pbsmetrics.AdapterLabels
 func (me *DummyMetricsEngine) RecordAdapterRequest(labels pbsmetrics.AdapterLabels) {
 }
 
+// RecordAdapterConnections as a noop
+func (me *DummyMetricsEngine) RecordAdapterConnections(bidderName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) {
+}
+
+// RecordDNSTime as a noop
+func (me *DummyMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) {
+}
+
 // RecordAdapterBidReceived as a noop
 func (me *DummyMetricsEngine) RecordAdapterBidReceived(labels pbsmetrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) {
 }
@@ -281,6 +304,6 @@ func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType p
 func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) {
 }
 
-// RecordReq as a noop
-func (me *DummyMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) {
+// RecordRequestPrivacy as a noop
+func (me *DummyMetricsEngine) RecordRequestPrivacy(privacy pbsmetrics.PrivacyLabels) {
 }
diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go
index 73eb30a1..26f6ce07 100644
--- a/pbsmetrics/go_metrics.go
+++ b/pbsmetrics/go_metrics.go
@@ -29,6 +29,7 @@ type Metrics struct {
 	PrebidCacheRequestTimerError   metrics.Timer
 	StoredReqCacheMeter            map[CacheResult]metrics.Meter
 	StoredImpCacheMeter            map[CacheResult]metrics.Meter
+	DNSLookupTimer                 metrics.Timer
 
 	// Metrics for OpenRTB requests specifically. So we can track what % of RequestsMeter are OpenRTB
 	// and know when legacy requests have been abandoned.
@@ -53,7 +54,11 @@ type Metrics struct {
 	TimeoutNotificationFailure metrics.Meter
 
 	// TCF adaption metrics
-	TCFReqVersion map[TCFVersionValue]metrics.Meter
+	PrivacyCCPARequest       metrics.Meter
+	PrivacyCCPARequestOptOut metrics.Meter
+	PrivacyCOPPARequest      metrics.Meter
+	PrivacyLMTRequest        metrics.Meter
+	PrivacyTCFRequestVersion map[TCFVersionValue]metrics.Meter
 
 	AdapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics
 	// Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically
@@ -77,6 +82,9 @@ type AdapterMetrics struct {
 	BidsReceivedMeter metrics.Meter
 	PanicMeter        metrics.Meter
 	MarkupMetrics     map[openrtb_ext.BidType]*MarkupDeliveryMetrics
+	ConnCreated       metrics.Counter
+	ConnReused        metrics.Counter
+	ConnWaitTime      metrics.Timer
 }
 
 type MarkupDeliveryMetrics struct {
@@ -102,7 +110,7 @@ const unknownBidder openrtb_ext.BidderName = "unknown"
 // rather than loading legacy metrics that never get filled.
 // This will also eventually let us configure metrics, such as setting a limited set of metrics
 // for a production instance, and then expanding again when we need more debugging.
-func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disableMetrics config.DisabledMetrics) *Metrics {
+func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disabledMetrics config.DisabledMetrics) *Metrics {
 	blankMeter := &metrics.NilMeter{}
 	blankTimer := &metrics.NilTimer{}
 
@@ -119,6 +127,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 		SafariRequestMeter:             blankMeter,
 		SafariNoCookieMeter:            blankMeter,
 		RequestTimer:                   blankTimer,
+		DNSLookupTimer:                 blankTimer,
 		RequestsQueueTimer:             make(map[RequestType]map[bool]metrics.Timer),
 		PrebidCacheRequestTimerSuccess: blankTimer,
 		PrebidCacheRequestTimerError:   blankTimer,
@@ -141,16 +150,21 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 		TimeoutNotificationSuccess: blankMeter,
 		TimeoutNotificationFailure: blankMeter,
 
-		TCFReqVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())),
+		PrivacyCCPARequest:       blankMeter,
+		PrivacyCCPARequestOptOut: blankMeter,
+		PrivacyCOPPARequest:      blankMeter,
+		PrivacyLMTRequest:        blankMeter,
+		PrivacyTCFRequestVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())),
 
 		AdapterMetrics:  make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)),
 		accountMetrics:  make(map[string]*accountMetrics),
-		MetricsDisabled: disableMetrics,
+		MetricsDisabled: disabledMetrics,
 
 		exchanges: exchanges,
 	}
+
 	for _, a := range exchanges {
-		newMetrics.AdapterMetrics[a] = makeBlankAdapterMetrics()
+		newMetrics.AdapterMetrics[a] = makeBlankAdapterMetrics(newMetrics.MetricsDisabled)
 	}
 
 	for _, t := range RequestTypes() {
@@ -166,7 +180,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 	}
 
 	for _, v := range TCFVersions() {
-		newMetrics.TCFReqVersion[v] = blankMeter
+		newMetrics.PrivacyTCFRequestVersion[v] = blankMeter
 	}
 
 	//to minimize memory usage, queuedTimeout metric is now supported for video endpoint only
@@ -200,6 +214,7 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
 	newMetrics.AppRequestMeter = metrics.GetOrRegisterMeter("app_requests", registry)
 	newMetrics.SafariNoCookieMeter = metrics.GetOrRegisterMeter("safari_no_cookie_requests", registry)
 	newMetrics.RequestTimer = metrics.GetOrRegisterTimer("request_time", registry)
+	newMetrics.DNSLookupTimer = metrics.GetOrRegisterTimer("dns_lookup_time", registry)
 	newMetrics.PrebidCacheRequestTimerSuccess = metrics.GetOrRegisterTimer("prebid_cache_request_time.ok", registry)
 	newMetrics.PrebidCacheRequestTimerError = metrics.GetOrRegisterTimer("prebid_cache_request_time.err", registry)
 
@@ -234,15 +249,19 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
 	newMetrics.TimeoutNotificationSuccess = metrics.GetOrRegisterMeter("timeout_notification.ok", registry)
 	newMetrics.TimeoutNotificationFailure = metrics.GetOrRegisterMeter("timeout_notification.failed", registry)
 
+	newMetrics.PrivacyCCPARequest = metrics.GetOrRegisterMeter("privacy.request.ccpa.specified", registry)
+	newMetrics.PrivacyCCPARequestOptOut = metrics.GetOrRegisterMeter("privacy.request.ccpa.opt-out", registry)
+	newMetrics.PrivacyCOPPARequest = metrics.GetOrRegisterMeter("privacy.request.coppa", registry)
+	newMetrics.PrivacyLMTRequest = metrics.GetOrRegisterMeter("privacy.request.lmt", registry)
 	for _, version := range TCFVersions() {
-		newMetrics.TCFReqVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry)
+		newMetrics.PrivacyTCFRequestVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry)
 	}
 
 	return newMetrics
 }
 
 // Part of setting up blank metrics, the adapter metrics.
-func makeBlankAdapterMetrics() *AdapterMetrics {
+func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMetrics {
 	blankMeter := &metrics.NilMeter{}
 	newAdapter := &AdapterMetrics{
 		NoCookieMeter:     blankMeter,
@@ -255,6 +274,11 @@ func makeBlankAdapterMetrics() *AdapterMetrics {
 		PanicMeter:        blankMeter,
 		MarkupMetrics:     makeBlankBidMarkupMetrics(),
 	}
+	if !disabledMetrics.AdapterConnectionMetrics {
+		newAdapter.ConnCreated = metrics.NilCounter{}
+		newAdapter.ConnReused = metrics.NilCounter{}
+		newAdapter.ConnWaitTime = &metrics.NilTimer{}
+	}
 	for _, err := range AdapterErrors() {
 		newAdapter.ErrorMeters[err] = blankMeter
 	}
@@ -289,6 +313,9 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string,
 		openrtb_ext.BidTypeAudio:  makeDeliveryMetrics(registry, adapterOrAccount+"."+exchange, openrtb_ext.BidTypeAudio),
 		openrtb_ext.BidTypeNative: makeDeliveryMetrics(registry, adapterOrAccount+"."+exchange, openrtb_ext.BidTypeNative),
 	}
+	am.ConnCreated = metrics.GetOrRegisterCounter(fmt.Sprintf("%[1]s.%[2]s.connections_created", adapterOrAccount, exchange), registry)
+	am.ConnReused = metrics.GetOrRegisterCounter(fmt.Sprintf("%[1]s.%[2]s.connections_reused", adapterOrAccount, exchange), registry)
+	am.ConnWaitTime = metrics.GetOrRegisterTimer(fmt.Sprintf("%[1]s.%[2]s.connection_wait_time", adapterOrAccount, exchange), registry)
 	for err := range am.ErrorMeters {
 		am.ErrorMeters[err] = metrics.GetOrRegisterMeter(fmt.Sprintf("%s.%s.requests.%s", adapterOrAccount, exchange, err), registry)
 	}
@@ -335,7 +362,7 @@ func (me *Metrics) getAccountMetrics(id string) *accountMetrics {
 	am.adapterMetrics = make(map[openrtb_ext.BidderName]*AdapterMetrics, len(me.exchanges))
 	if !me.MetricsDisabled.AccountAdapterDetails {
 		for _, a := range me.exchanges {
-			am.adapterMetrics[a] = makeBlankAdapterMetrics()
+			am.adapterMetrics[a] = makeBlankAdapterMetrics(me.MetricsDisabled)
 			registerAdapterMetrics(me.MetricsRegistry, fmt.Sprintf("account.%s", id), string(a), am.adapterMetrics[a])
 		}
 	}
@@ -459,6 +486,34 @@ func (me *Metrics) RecordAdapterRequest(labels AdapterLabels) {
 	}
 }
 
+// Keeps track of created and reused connections to adapter bidders and the time from the
+// connection request, to the connection creation, or reuse from the pool across all engines
+func (me *Metrics) RecordAdapterConnections(adapterName openrtb_ext.BidderName,
+	connWasReused bool,
+	connWaitTime time.Duration) {
+
+	if me.MetricsDisabled.AdapterConnectionMetrics {
+		return
+	}
+
+	am, ok := me.AdapterMetrics[adapterName]
+	if !ok {
+		glog.Errorf("Trying to log adapter connection metrics for %s: adapter not found", string(adapterName))
+		return
+	}
+
+	if connWasReused {
+		am.ConnReused.Inc(1)
+	} else {
+		am.ConnCreated.Inc(1)
+	}
+	am.ConnWaitTime.Update(connWaitTime)
+}
+
+func (me *Metrics) RecordDNSTime(dnsLookupTime time.Duration) {
+	me.DNSLookupTimer.Update(dnsLookupTime)
+}
+
 // RecordAdapterBidReceived implements a part of the MetricsEngine interface.
 // This tracks how many bids from each Bidder use `adm` vs. `nurl.
 func (me *Metrics) RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) {
@@ -582,12 +637,28 @@ func (me *Metrics) RecordTimeoutNotice(success bool) {
 	return
 }
 
-func (me *Metrics) RecordTCFReq(version TCFVersionValue) {
-	met, ok := me.TCFReqVersion[version]
-	if ok {
-		met.Mark(1)
-	} else {
-		me.TCFReqVersion[TCFVersionErr].Mark(1)
+func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) {
+	if privacy.CCPAProvided {
+		me.PrivacyCCPARequest.Mark(1)
+		if privacy.CCPAEnforced {
+			me.PrivacyCCPARequestOptOut.Mark(1)
+		}
+	}
+
+	if privacy.COPPAEnforced {
+		me.PrivacyCOPPARequest.Mark(1)
+	}
+
+	if privacy.GDPREnforced {
+		if metric, ok := me.PrivacyTCFRequestVersion[privacy.GDPRTCFVersion]; ok {
+			metric.Mark(1)
+		} else {
+			me.PrivacyTCFRequestVersion[TCFVersionErr].Mark(1)
+		}
+	}
+
+	if privacy.LMTEnforced {
+		me.PrivacyLMTRequest.Mark(1)
 	}
 	return
 }
diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go
index 0e94fe71..8133bc73 100644
--- a/pbsmetrics/metrics.go
+++ b/pbsmetrics/metrics.go
@@ -41,6 +41,16 @@ type RequestLabels struct {
 	RequestStatus RequestStatus
 }
 
+// PrivacyLabels defines metrics describing the result of privacy enforcement.
+type PrivacyLabels struct {
+	CCPAEnforced   bool
+	CCPAProvided   bool
+	COPPAEnforced  bool
+	GDPREnforced   bool
+	GDPRTCFVersion TCFVersionValue
+	LMTEnforced    bool
+}
+
 // Label typecasting. Se below the type definitions for possible values
 
 // DemandSource : Demand source enumeration
@@ -257,7 +267,7 @@ const (
 	TCFVersionV2  TCFVersionValue = "v2"
 )
 
-// TCFVersions rtuens the possible values for the TCF version
+// TCFVersions returns the possible values for the TCF version
 func TCFVersions() []TCFVersionValue {
 	return []TCFVersionValue{
 		TCFVersionErr,
@@ -291,6 +301,8 @@ type MetricsEngine interface {
 	RecordLegacyImps(labels Labels, numImps int)           // RecordImps for the legacy engine
 	RecordRequestTime(labels Labels, length time.Duration) // ignores adapter. only statusOk and statusErr fom status
 	RecordAdapterRequest(labels AdapterLabels)
+	RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration)
+	RecordDNSTime(dnsLookupTime time.Duration)
 	RecordAdapterPanic(labels AdapterLabels)
 	// This records whether or not a bid of a particular type uses `adm` or `nurl`.
 	// Since the legacy endpoints don't have a bid type, it can only count bids from OpenRTB and AMP.
@@ -305,5 +317,5 @@ type MetricsEngine interface {
 	RecordPrebidCacheRequestTime(success bool, length time.Duration)
 	RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
 	RecordTimeoutNotice(sucess bool)
-	RecordTCFReq(version TCFVersionValue)
+	RecordRequestPrivacy(privacy PrivacyLabels)
 }
diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go
index a6d36a72..42a2d1b4 100644
--- a/pbsmetrics/metrics_mock.go
+++ b/pbsmetrics/metrics_mock.go
@@ -52,6 +52,16 @@ func (me *MetricsEngineMock) RecordAdapterRequest(labels AdapterLabels) {
 	me.Called(labels)
 }
 
+// RecordAdapterConnections mock
+func (me *MetricsEngineMock) RecordAdapterConnections(bidderName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) {
+	me.Called(bidderName, connWasReused, connWaitTime)
+}
+
+// RecordDNSTime mock
+func (me *MetricsEngineMock) RecordDNSTime(dnsLookupTime time.Duration) {
+	me.Called(dnsLookupTime)
+}
+
 // RecordAdapterBidReceived mock
 func (me *MetricsEngineMock) RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) {
 	me.Called(labels, bidType, hasAdm)
@@ -107,7 +117,7 @@ func (me *MetricsEngineMock) RecordTimeoutNotice(success bool) {
 	me.Called(success)
 }
 
-// RecordTCFReq mock
-func (me *MetricsEngineMock) RecordTCFReq(version TCFVersionValue) {
-	me.Called(version)
+// RecordRequestPrivacy mock
+func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) {
+	me.Called(privacy)
 }
diff --git a/pbsmetrics/prometheus/preload.go b/pbsmetrics/prometheus/preload.go
index 19f4f225..4f62a18a 100644
--- a/pbsmetrics/prometheus/preload.go
+++ b/pbsmetrics/prometheus/preload.go
@@ -8,15 +8,16 @@ import (
 func preloadLabelValues(m *Metrics) {
 	var (
 		actionValues          = actionsAsString()
-		adapterValues         = adaptersAsString()
 		adapterErrorValues    = adapterErrorsAsString()
+		adapterValues         = adaptersAsString()
 		bidTypeValues         = []string{markupDeliveryAdm, markupDeliveryNurl}
 		boolValues            = boolValuesAsString()
 		cacheResultValues     = cacheResultsAsString()
-		cookieValues          = cookieTypesAsString()
 		connectionErrorValues = []string{connectionAcceptError, connectionCloseError}
+		cookieValues          = cookieTypesAsString()
 		requestStatusValues   = requestStatusesAsString()
 		requestTypeValues     = requestTypesAsString()
+		sourceValues          = []string{sourceRequest}
 	)
 
 	preloadLabelValuesForCounter(m.connectionsError, map[string][]string{
@@ -84,6 +85,20 @@ func preloadLabelValues(m *Metrics) {
 		hasBidsLabel: boolValues,
 	})
 
+	if !m.metricsDisabled.AdapterConnectionMetrics {
+		preloadLabelValuesForCounter(m.adapterCreatedConnections, map[string][]string{
+			adapterLabel: adapterValues,
+		})
+
+		preloadLabelValuesForCounter(m.adapterReusedConnections, map[string][]string{
+			adapterLabel: adapterValues,
+		})
+
+		preloadLabelValuesForHistogram(m.adapterConnectionWaitTime, map[string][]string{
+			adapterLabel: adapterValues,
+		})
+	}
+
 	preloadLabelValuesForHistogram(m.adapterRequestsTimer, map[string][]string{
 		adapterLabel: adapterValues,
 	})
@@ -100,9 +115,22 @@ func preloadLabelValues(m *Metrics) {
 		requestStatusLabel: {requestSuccessLabel, requestRejectLabel},
 	})
 
-	preloadLabelValuesForCounter(m.tcfVersion, map[string][]string{
+	preloadLabelValuesForCounter(m.privacyCCPA, map[string][]string{
+		sourceLabel: sourceValues,
+		optOutLabel: boolValues,
+	})
+
+	preloadLabelValuesForCounter(m.privacyCOPPA, map[string][]string{
+		sourceLabel: sourceValues,
+	})
+
+	preloadLabelValuesForCounter(m.privacyLMT, map[string][]string{
+		sourceLabel: sourceValues,
+	})
+
+	preloadLabelValuesForCounter(m.privacyTCF, map[string][]string{
+		sourceLabel:  sourceValues,
 		versionLabel: tcfVersionsAsString(),
-		sourceLabel:  {string(sourceRequest)},
 	})
 }
 
diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go
index bf854746..b42399b2 100644
--- a/pbsmetrics/prometheus/prometheus.go
+++ b/pbsmetrics/prometheus/prometheus.go
@@ -29,20 +29,29 @@ type Metrics struct {
 	storedImpressionsCacheResult *prometheus.CounterVec
 	storedRequestCacheResult     *prometheus.CounterVec
 	timeoutNotifications         *prometheus.CounterVec
-	tcfVersion                   *prometheus.CounterVec
+	dnsLookupTimer               prometheus.Histogram
+	privacyCCPA                  *prometheus.CounterVec
+	privacyCOPPA                 *prometheus.CounterVec
+	privacyLMT                   *prometheus.CounterVec
+	privacyTCF                   *prometheus.CounterVec
 
 	// Adapter Metrics
-	adapterBids          *prometheus.CounterVec
-	adapterCookieSync    *prometheus.CounterVec
-	adapterErrors        *prometheus.CounterVec
-	adapterPanics        *prometheus.CounterVec
-	adapterPrices        *prometheus.HistogramVec
-	adapterRequests      *prometheus.CounterVec
-	adapterRequestsTimer *prometheus.HistogramVec
-	adapterUserSync      *prometheus.CounterVec
+	adapterBids               *prometheus.CounterVec
+	adapterCookieSync         *prometheus.CounterVec
+	adapterErrors             *prometheus.CounterVec
+	adapterPanics             *prometheus.CounterVec
+	adapterPrices             *prometheus.HistogramVec
+	adapterRequests           *prometheus.CounterVec
+	adapterRequestsTimer      *prometheus.HistogramVec
+	adapterUserSync           *prometheus.CounterVec
+	adapterReusedConnections  *prometheus.CounterVec
+	adapterCreatedConnections *prometheus.CounterVec
+	adapterConnectionWaitTime *prometheus.HistogramVec
 
 	// Account Metrics
 	accountRequests *prometheus.CounterVec
+
+	metricsDisabled config.DisabledMetrics
 }
 
 const (
@@ -60,6 +69,7 @@ const (
 	isNativeLabel        = "native"
 	isVideoLabel         = "video"
 	markupDeliveryLabel  = "delivery"
+	optOutLabel          = "opt_out"
 	privacyBlockedLabel  = "privacy_blocked"
 	requestStatusLabel   = "request_status"
 	requestTypeLabel     = "request_type"
@@ -93,14 +103,15 @@ const (
 )
 
 // NewMetrics initializes a new Prometheus metrics instance with preloaded label values.
-func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
-	requestTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1}
+func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMetrics) *Metrics {
+	standardTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1}
 	cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1}
 	priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000}
 	queuedRequestTimeBuckets := []float64{0, 1, 5, 30, 60, 120, 180, 240, 300}
 
 	metrics := Metrics{}
 	metrics.Registry = prometheus.NewRegistry()
+	metrics.metricsDisabled = disabledMetrics
 
 	metrics.connectionsClosed = newCounterWithoutLabels(cfg, metrics.Registry,
 		"connections_closed",
@@ -128,7 +139,7 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 		"impressions_requests_legacy",
 		"Count of requested impressions to Prebid Server using the legacy endpoint.")
 
-	metrics.prebidCacheWriteTimer = newHistogram(cfg, metrics.Registry,
+	metrics.prebidCacheWriteTimer = newHistogramVec(cfg, metrics.Registry,
 		"prebidcache_write_time_seconds",
 		"Seconds to write to Prebid Cache labeled by success or failure. Failure timing is limited by Prebid Server enforced timeouts.",
 		[]string{successLabel},
@@ -139,11 +150,11 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 		"Count of total requests to Prebid Server labeled by type and status.",
 		[]string{requestTypeLabel, requestStatusLabel})
 
-	metrics.requestsTimer = newHistogram(cfg, metrics.Registry,
+	metrics.requestsTimer = newHistogramVec(cfg, metrics.Registry,
 		"request_time_seconds",
 		"Seconds to resolve successful Prebid Server requests labeled by type.",
 		[]string{requestTypeLabel},
-		requestTimeBuckets)
+		standardTimeBuckets)
 
 	metrics.requestsWithoutCookie = newCounter(cfg, metrics.Registry,
 		"requests_without_cookie",
@@ -165,11 +176,31 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 		"Count of timeout notifications triggered, and if they were successfully sent.",
 		[]string{successLabel})
 
-	metrics.tcfVersion = newCounter(cfg, metrics.Registry,
+	metrics.dnsLookupTimer = newHistogram(cfg, metrics.Registry,
+		"dns_lookup_time",
+		"Seconds to resolve DNS",
+		standardTimeBuckets)
+
+	metrics.privacyCCPA = newCounter(cfg, metrics.Registry,
+		"privacy_ccpa",
+		"Count of total requests to Prebid Server where CCPA was provided by source and opt-out .",
+		[]string{sourceLabel, optOutLabel})
+
+	metrics.privacyCOPPA = newCounter(cfg, metrics.Registry,
+		"privacy_coppa",
+		"Count of total requests to Prebid Server where the COPPA flag was set by source",
+		[]string{sourceLabel})
+
+	metrics.privacyTCF = newCounter(cfg, metrics.Registry,
 		"privacy_tcf",
-		"Count of TCF versions for requests where GDPR was enforced.",
+		"Count of TCF versions for requests where GDPR was enforced by source and version.",
 		[]string{versionLabel, sourceLabel})
 
+	metrics.privacyLMT = newCounter(cfg, metrics.Registry,
+		"privacy_lmt",
+		"Count of total requests to Prebid Server where the LMT flag was set by source",
+		[]string{sourceLabel})
+
 	metrics.adapterBids = newCounter(cfg, metrics.Registry,
 		"adapter_bids",
 		"Count of bids labeled by adapter and markup delivery type (adm or nurl).",
@@ -190,7 +221,7 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 		"Count of panics labeled by adapter.",
 		[]string{adapterLabel})
 
-	metrics.adapterPrices = newHistogram(cfg, metrics.Registry,
+	metrics.adapterPrices = newHistogramVec(cfg, metrics.Registry,
 		"adapter_prices",
 		"Monetary value of the bids labeled by adapter.",
 		[]string{adapterLabel},
@@ -201,11 +232,29 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 		"Count of requests labeled by adapter, if has a cookie, and if it resulted in bids.",
 		[]string{adapterLabel, cookieLabel, hasBidsLabel})
 
-	metrics.adapterRequestsTimer = newHistogram(cfg, metrics.Registry,
+	if !metrics.metricsDisabled.AdapterConnectionMetrics {
+		metrics.adapterCreatedConnections = newCounter(cfg, metrics.Registry,
+			"adapter_connection_created",
+			"Count that keeps track of new connections when contacting adapter bidder endpoints.",
+			[]string{adapterLabel})
+
+		metrics.adapterReusedConnections = newCounter(cfg, metrics.Registry,
+			"adapter_connection_reused",
+			"Count that keeps track of reused connections when contacting adapter bidder endpoints.",
+			[]string{adapterLabel})
+
+		metrics.adapterConnectionWaitTime = newHistogramVec(cfg, metrics.Registry,
+			"adapter_connection_wait",
+			"Seconds from when the connection was requested until it is either created or reused",
+			[]string{adapterLabel},
+			standardTimeBuckets)
+	}
+
+	metrics.adapterRequestsTimer = newHistogramVec(cfg, metrics.Registry,
 		"adapter_request_time_seconds",
 		"Seconds to resolve each successful request labeled by adapter.",
 		[]string{adapterLabel},
-		requestTimeBuckets)
+		standardTimeBuckets)
 
 	metrics.adapterUserSync = newCounter(cfg, metrics.Registry,
 		"adapter_user_sync",
@@ -217,7 +266,7 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 		"Count of total requests to Prebid Server labeled by account.",
 		[]string{accountLabel})
 
-	metrics.requestsQueueTimer = newHistogram(cfg, metrics.Registry,
+	metrics.requestsQueueTimer = newHistogramVec(cfg, metrics.Registry,
 		"request_queue_time",
 		"Seconds request was waiting in queue",
 		[]string{requestTypeLabel, requestStatusLabel},
@@ -252,7 +301,7 @@ func newCounterWithoutLabels(cfg config.PrometheusMetrics, registry *prometheus.
 	return counter
 }
 
-func newHistogram(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string, buckets []float64) *prometheus.HistogramVec {
+func newHistogramVec(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, labels []string, buckets []float64) *prometheus.HistogramVec {
 	opts := prometheus.HistogramOpts{
 		Namespace: cfg.Namespace,
 		Subsystem: cfg.Subsystem,
@@ -265,6 +314,19 @@ func newHistogram(cfg config.PrometheusMetrics, registry *prometheus.Registry, n
 	return histogram
 }
 
+func newHistogram(cfg config.PrometheusMetrics, registry *prometheus.Registry, name, help string, buckets []float64) prometheus.Histogram {
+	opts := prometheus.HistogramOpts{
+		Namespace: cfg.Namespace,
+		Subsystem: cfg.Subsystem,
+		Name:      name,
+		Help:      help,
+		Buckets:   buckets,
+	}
+	histogram := prometheus.NewHistogram(opts)
+	registry.MustRegister(histogram)
+	return histogram
+}
+
 func (m *Metrics) RecordConnectionAccept(success bool) {
 	if success {
 		m.connectionsOpened.Inc()
@@ -340,6 +402,32 @@ func (m *Metrics) RecordAdapterRequest(labels pbsmetrics.AdapterLabels) {
 	}
 }
 
+// Keeps track of created and reused connections to adapter bidders and the time from the
+// connection request, to the connection creation, or reuse from the pool across all engines
+func (m *Metrics) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) {
+	if m.metricsDisabled.AdapterConnectionMetrics {
+		return
+	}
+
+	if connWasReused {
+		m.adapterReusedConnections.With(prometheus.Labels{
+			adapterLabel: string(adapterName),
+		}).Inc()
+	} else {
+		m.adapterCreatedConnections.With(prometheus.Labels{
+			adapterLabel: string(adapterName),
+		}).Inc()
+	}
+
+	m.adapterConnectionWaitTime.With(prometheus.Labels{
+		adapterLabel: string(adapterName),
+	}).Observe(connWaitTime.Seconds())
+}
+
+func (m *Metrics) RecordDNSTime(dnsLookupTime time.Duration) {
+	m.dnsLookupTimer.Observe(dnsLookupTime.Seconds())
+}
+
 func (m *Metrics) RecordAdapterPanic(labels pbsmetrics.AdapterLabels) {
 	m.adapterPanics.With(prometheus.Labels{
 		adapterLabel: string(labels.Adapter),
@@ -434,9 +522,30 @@ func (m *Metrics) RecordTimeoutNotice(success bool) {
 	}
 }
 
-func (m *Metrics) RecordTCFReq(version pbsmetrics.TCFVersionValue) {
-	m.tcfVersion.With(prometheus.Labels{
-		versionLabel: string(version),
-		sourceLabel:  sourceRequest,
-	}).Inc()
+func (m *Metrics) RecordRequestPrivacy(privacy pbsmetrics.PrivacyLabels) {
+	if privacy.CCPAProvided {
+		m.privacyCCPA.With(prometheus.Labels{
+			sourceLabel: sourceRequest,
+			optOutLabel: strconv.FormatBool(privacy.CCPAEnforced),
+		}).Inc()
+	}
+
+	if privacy.COPPAEnforced {
+		m.privacyCOPPA.With(prometheus.Labels{
+			sourceLabel: sourceRequest,
+		}).Inc()
+	}
+
+	if privacy.GDPREnforced {
+		m.privacyTCF.With(prometheus.Labels{
+			versionLabel: string(privacy.GDPRTCFVersion),
+			sourceLabel:  sourceRequest,
+		}).Inc()
+	}
+
+	if privacy.LMTEnforced {
+		m.privacyLMT.With(prometheus.Labels{
+			sourceLabel: sourceRequest,
+		}).Inc()
+	}
 }
diff --git a/static/bidder-info/adtelligent.yaml b/static/bidder-info/adtelligent.yaml
index fe791343..7a20d52b 100644
--- a/static/bidder-info/adtelligent.yaml
+++ b/static/bidder-info/adtelligent.yaml
@@ -4,6 +4,7 @@ capabilities:
   app:
     mediaTypes:
       - banner
+      - video
   site:
     mediaTypes:
       - banner
diff --git a/static/bidder-info/audienceNetwork.yaml b/static/bidder-info/audienceNetwork.yaml
index 56230bf3..324e5c6d 100644
--- a/static/bidder-info/audienceNetwork.yaml
+++ b/static/bidder-info/audienceNetwork.yaml
@@ -1,11 +1,6 @@
 maintainer:
   email: "none"
 capabilities:
-  site:
-    mediaTypes:
-      - banner
-      - video
-      - native
   app:
     mediaTypes:
       - banner
diff --git a/static/bidder-info/logicad.yaml b/static/bidder-info/logicad.yaml
new file mode 100644
index 00000000..c087516c
--- /dev/null
+++ b/static/bidder-info/logicad.yaml
@@ -0,0 +1,10 @@
+maintainer:
+  email: "prebid@so-netmedia.jp"
+capabilities:
+  site:
+    mediaTypes:
+      - banner
+  app:
+    mediaTypes:
+      - banner
+
diff --git a/static/bidder-params/logicad.json b/static/bidder-params/logicad.json
new file mode 100644
index 00000000..2a892f91
--- /dev/null
+++ b/static/bidder-params/logicad.json
@@ -0,0 +1,13 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Logicad Adapter Params",
+  "description": "A schema which validates params accepted by the Logicad adapter",
+  "type": "object",
+  "properties": {
+    "tid": {
+      "type": "string",
+      "description": "Logicad for Publishers placement ID"
+    }
+  },
+  "required": ["tid"]
+}
diff --git a/static/category-mapping/freewheel/freewheel.json b/static/category-mapping/freewheel/freewheel.json
index 1c4a4fa2..11529206 100644
--- a/static/category-mapping/freewheel/freewheel.json
+++ b/static/category-mapping/freewheel/freewheel.json
@@ -1178,5 +1178,81 @@
 	"IAB22-3": {
 		"id": "410",
 		"name": "Product"
+	},  
+	"IAB1": {
+	  "id": "392",
+	  "name": "Entertainment"
+	},
+	"IAB2": {
+	  "id": "399",
+	  "name": "Automotive"
+	},
+	"IAB3": {
+	  "id": "393",
+	  "name": "Business Services"
+	},
+	"IAB4": {
+	  "id": "405",
+	  "name": "Educational Services"
+	},
+	"IAB5": {
+	  "id": "405",
+	  "name": "Educational Services"
+	},
+	"IAB7": {
+	  "id": "406",
+	  "name": "Health Care Services"
+	},
+	"IAB8": {
+	  "id": "394",
+	  "name": "Food"
+	},
+	"IAB9": {
+	  "id": "392",
+	  "name": "Entertainment"
+	},
+	"IAB10": {
+	  "id": "434",
+	  "name": "Home Furnishings"
+	},
+	"IAB11": {
+	  "id": "398",
+	  "name": "Government/Municipal"
+	},
+	"IAB12": {
+	  "id": "438",
+	  "name": "News"
+	},
+	"IAB13": {
+	  "id": "393",
+	  "name": "Business Services"
+	},
+	"IAB16": {
+	  "id": "423",
+	  "name": "Pet Food/Supplies"
+	},
+	"IAB17": {
+	  "id": "425",
+	  "name": "Professional Sports"
+	},
+	"IAB18": {
+	  "id": "397",
+	  "name": "Apparel"
+	},
+	"IAB19": {
+	  "id": "409",
+	  "name": "Computing Product"
+	},
+	"IAB20": {
+	  "id": "395",
+	  "name": "Travel/Hotel/Airlines"
+	},
+	"IAB21": {
+	  "id": "416",
+	  "name": "Real Estate"
+	},
+	"IAB22": {
+	  "id": "403",
+	  "name": "Retail Stores/Chains"
 	}
 }
\ No newline at end of file
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index f1f643af..89540ea2 100755
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -39,6 +39,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/ix"
 	"github.com/prebid/prebid-server/adapters/lifestreet"
 	"github.com/prebid/prebid-server/adapters/lockerdome"
+	"github.com/prebid/prebid-server/adapters/logicad"
 	"github.com/prebid/prebid-server/adapters/lunamedia"
 	"github.com/prebid/prebid-server/adapters/marsmedia"
 	"github.com/prebid/prebid-server/adapters/mgid"
@@ -115,6 +116,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderLifestreet, lifestreet.NewLifestreetSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderLogicad, logicad.NewLogicadSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer)


Test patch:
diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go
index 63646f5f..2fca7d17 100644
--- a/adapters/adform/adform_test.go
+++ b/adapters/adform/adform_test.go
@@ -480,7 +480,33 @@ func getUserExt() []byte {
 		KeyV: 1,
 		Pref: 0,
 	}
+
+	eids := []openrtb_ext.ExtUserEid{
+		{
+			Source: "test.com",
+			Uids: []openrtb_ext.ExtUserEidUid{
+				{
+					ID:    "some_user_id",
+					Atype: 1,
+				},
+				{
+					ID: "other_user_id",
+				},
+			},
+		},
+		{
+			Source: "test2.org",
+			Uids: []openrtb_ext.ExtUserEidUid{
+				{
+					ID:    "other_user_id",
+					Atype: 2,
+				},
+			},
+		},
+	}
+
 	userExt := openrtb_ext.ExtUser{
+		Eids:      eids,
 		Consent:   "abc",
 		DigiTrust: &digitrust,
 	}
@@ -519,13 +545,16 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb boo
 	}
 
 	var midsWithCurrency = ""
+	var queryString = ""
 	if isOpenRtb {
 		midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZQ&bWlkPTMyMzQ1JnJjdXI9RVVS&bWlkPTMyMzQ2JnJjdXI9RVVS"
+		queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency
 	} else {
 		midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZQ&bWlkPTMyMzQ1JnJjdXI9VVNE&bWlkPTMyMzQ2JnJjdXI9VVNE" // no way to pass currency in legacy adapter
+		queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency
 	}
 
-	if ok, err := equal("CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&"+midsWithCurrency, r.URL.RawQuery, "Query string"); !ok {
+	if ok, err := equal(queryString, r.URL.RawQuery, "Query string"); !ok {
 		return err
 	}
 	if ok, err := equal("application/json;charset=utf-8", r.Header.Get("Content-Type"), "Content type"); !ok {
diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go
index e76995fc..3c795ea2 100644
--- a/adapters/adgeneration/adgeneration_test.go
+++ b/adapters/adgeneration/adgeneration_test.go
@@ -5,7 +5,9 @@ import (
 	"testing"
 
 	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestJsonSamples(t *testing.T) {
@@ -174,3 +176,64 @@ func TestCreateAd(t *testing.T) {
 		t.Errorf("%v does not match createAd.", adgVastResponse)
 	}
 }
+
+func TestMakeBids(t *testing.T) {
+	bidder := NewAdgenerationAdapter("https://d.socdm.com/adsv/v1")
+	internalRequest := &openrtb.BidRequest{
+		ID: "test-success-bid-request",
+		Imp: []openrtb.Imp{
+			{ID: "bidRequest-success-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)},
+		},
+		Device: &openrtb.Device{UA: "testUA", IP: "testIP"},
+		Site:   &openrtb.Site{Page: "https://supership.com"},
+		User:   &openrtb.User{BuyerUID: "buyerID"},
+	}
+	externalRequest := adapters.RequestData{}
+	response := adapters.ResponseData{
+		StatusCode: 200,
+		Body:       ([]byte)("{\n \"ad\": \"testAd\",\n \"cpm\": 30,\n \"creativeid\": \"Dummy_supership.jp\",\n \"h\": 250,\n \"locationid\": \"58278\",\n \"results\": [{}],\n \"dealid\": \"test-deal-id\",\n \"w\": 300\n }"),
+	}
+	// default Currency InternalRequest
+	defaultCurBidderResponse, errs := bidder.MakeBids(internalRequest, &externalRequest, &response)
+	if len(errs) > 0 {
+		t.Errorf("MakeBids return errors. errors: %v", errs)
+	}
+	checkBidResponse(t, defaultCurBidderResponse, bidder.defaultCurrency)
+
+	// Specified Currency InternalRequest
+	usdCur := "USD"
+	internalRequest.Cur = []string{usdCur}
+	specifiedCurBidderResponse, errs := bidder.MakeBids(internalRequest, &externalRequest, &response)
+	if len(errs) > 0 {
+		t.Errorf("MakeBids return errors. errors: %v", errs)
+	}
+	checkBidResponse(t, specifiedCurBidderResponse, usdCur)
+
+}
+
+func checkBidResponse(t *testing.T, bidderResponse *adapters.BidderResponse, expectedCurrency string) {
+	if bidderResponse == nil {
+		t.Errorf("actual bidResponse is nil.")
+	}
+
+	// AdM is assured by TestCreateAd and JSON tests
+	var expectedAdM string = "testAd"
+	var expectedID string = "58278"
+	var expectedImpID = "bidRequest-success-test"
+	var expectedPrice float64 = 30.0
+	var expectedW uint64 = 300
+	var expectedH uint64 = 250
+	var expectedCrID string = "Dummy_supership.jp"
+	var extectedDealID string = "test-deal-id"
+
+	assert.Equal(t, expectedCurrency, bidderResponse.Currency)
+	assert.Equal(t, 1, len(bidderResponse.Bids))
+	assert.Equal(t, expectedID, bidderResponse.Bids[0].Bid.ID)
+	assert.Equal(t, expectedImpID, bidderResponse.Bids[0].Bid.ImpID)
+	assert.Equal(t, expectedAdM, bidderResponse.Bids[0].Bid.AdM)
+	assert.Equal(t, expectedPrice, bidderResponse.Bids[0].Bid.Price)
+	assert.Equal(t, expectedW, bidderResponse.Bids[0].Bid.W)
+	assert.Equal(t, expectedH, bidderResponse.Bids[0].Bid.H)
+	assert.Equal(t, expectedCrID, bidderResponse.Bids[0].Bid.CrID)
+	assert.Equal(t, extectedDealID, bidderResponse.Bids[0].Bid.DealID)
+}
diff --git a/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json
index d23a510b..10bf1c4a 100644
--- a/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json
+++ b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json
@@ -52,7 +52,7 @@
                 "tmax": 500
             },
             "expectedRequest":{
-                "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0&currency=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
+                "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.1&currency=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
                 "headers": {
                     "Accept": [
                         "application/json"
diff --git a/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json
index cf8635bb..bc469a1e 100644
--- a/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json
+++ b/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json
@@ -52,7 +52,7 @@
                 "tmax": 500
             },
             "expectedRequest":{
-                "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0&currency=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
+                "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.1&currency=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
                 "headers": {
                     "Accept": [
                         "application/json"
diff --git a/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json
index f5dc7fe0..6ac92d9a 100644
--- a/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json
+++ b/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json
@@ -52,7 +52,7 @@
                 "tmax": 500
             },
             "expectedRequest":{
-                "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0&currency=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
+                "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.1&currency=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
                 "headers": {
                     "Accept": [
                         "application/json"
diff --git a/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json
index 399f85a5..a0abb66d 100644
--- a/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json
+++ b/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json
@@ -52,7 +52,7 @@
                 "tmax": 500
             },
             "expectedRequest":{
-                "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0&currency=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
+                "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.1&currency=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
                 "headers": {
                     "Accept": [
                         "application/json"
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json
deleted file mode 100644
index 01bab3df..00000000
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json
+++ /dev/null
@@ -1,132 +0,0 @@
-{
-    "mockBidRequest": {
-        "id": "test-req-id",
-        "imp": [
-            {
-                "id": "test-imp-id",
-                "banner": {
-                    "format": [
-                        {
-                            "w": 300,
-                            "h": 250
-                        }
-                    ],
-                    "w": 300,
-                    "h": 250
-                },
-                "ext": {
-                    "bidder": {
-                        "publisherid": "123",
-                        "placementid": "456"
-                    }
-                }
-            }
-        ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
-        },
-        "device": {
-            "ip": "152.193.6.74"
-        },
-        "user": {
-            "id": "db089de9-a62e-4861-a881-0ff15e052516",
-            "buyeruid": "v4_bidder_token"
-        },
-        "tmax": 500
-    },
-    "httpcalls": [
-        {
-            "expectedRequest": {
-                "uri": "https://an.facebook.com/placementbid.ortb",
-                "headers": {
-                    "Accept": [
-                        "application/json"
-                    ],
-                    "Content-Type": [
-                        "application/json;charset=utf-8"
-                    ],
-                    "X-Fb-Pool-Routing-Token": [
-                        "v4_bidder_token"
-                    ]
-                },
-                "body": {
-                    "id": "test-imp-id",
-                    "imp": [
-                        {
-                            "id": "test-imp-id",
-                            "banner": {
-                                "w": -1,
-                                "h": 250
-                            },
-                            "tagid": "123_456"
-                        }
-                    ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
-                        "publisher": {
-                            "id": "123"
-                        }
-                    },
-                    "device": {
-                        "ip": "152.193.6.74"
-                    },
-                    "user": {
-                        "id": "db089de9-a62e-4861-a881-0ff15e052516",
-                        "buyeruid": "v4_bidder_token"
-                    },
-                    "tmax": 500,
-                    "ext": {
-                        "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174",
-                        "platformid": "test-platform-id"
-                    }
-                }
-            },
-            "mockResponse": {
-                "status": 200,
-                "body": {
-                    "id": "test-imp-id",
-                    "seatbid": [
-                        {
-                            "bid": [
-                                {
-                                    "id": "987",
-                                    "impid": "test-imp-id",
-                                    "price": 1.000000,
-                                    "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}",
-                                    "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0",
-                                    "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0",
-                                    "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}"
-                                }
-                            ]
-                        }
-                    ],
-                    "bidid": "654",
-                    "cur": "USD"
-                }
-            }
-        }
-    ],
-    "expectedBidResponses": [
-        {
-            "currency": "USD",
-            "bids": [
-                {
-                    "bid": {
-                        "id": "987",
-                        "impid": "test-imp-id",
-                        "price": 1,
-                        "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}",
-                        "adid": "987",
-                        "crid": "987",
-                        "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0",
-                        "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0",
-                        "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}"
-                    },
-                    "type": "banner"
-                }
-            ]
-        }
-    ]
-}
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json
index 9f563f11..573032c8 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json
@@ -23,9 +23,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -64,9 +64,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json
index 16bed344..08639bee 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json
@@ -16,9 +16,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -56,9 +56,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json
index 5ece0f08..35bdf9a4 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json
@@ -21,9 +21,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -66,9 +66,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json
index 5469fefb..450e0d9e 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json
@@ -24,9 +24,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -64,9 +64,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json
index f145f5fe..c33807bd 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json
@@ -18,9 +18,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -55,9 +55,9 @@
                     },
                     "tagid": "123_456"
                 }],
-                "site": {
-                    "domain": "prebid.org",
-                    "page": "prebid.org",
+                "app": {
+                    "id": "app-abc",
+                    "bundle": "com.prebid",
                     "publisher": {
                         "id": "123"
                     }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json
index fa9fd913..b229d41a 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json
@@ -22,9 +22,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json
index ad19d94c..68ca8044 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json
@@ -20,9 +20,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json
index b57c9001..50212155 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json
@@ -18,9 +18,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -55,9 +55,9 @@
                     },
                     "tagid": "123_456"
                 }],
-                "site": {
-                    "domain": "prebid.org",
-                    "page": "prebid.org",
+                "app": {
+                    "id": "app-abc",
+                    "bundle": "com.prebid",
                     "publisher": {
                         "id": "123"
                     }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json
index 23227aab..832b16dc 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json
@@ -18,9 +18,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -55,9 +55,9 @@
                     },
                     "tagid": "123_456"
                 }],
-                "site": {
-                    "domain": "prebid.org",
-                    "page": "prebid.org",
+                "app": {
+                    "id": "app-abc",
+                    "bundle": "com.prebid",
                     "publisher": {
                         "id": "123"
                     }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json
index 016e8de0..0793f990 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json
@@ -20,9 +20,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json
index 231c2826..682c33e4 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json
@@ -41,9 +41,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -81,9 +81,9 @@
                             "tagid": "pub1_plmt1"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "pub1"
                         }
@@ -152,9 +152,9 @@
                             "tagid": "pub2_plmt2"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "pub2"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json
index 45b35e05..642e4958 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json
@@ -16,9 +16,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -56,9 +56,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json
index 7420f7e8..fccdf71c 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json
@@ -2,9 +2,9 @@
     "mockBidRequest": {
         "id": "test-req-id",
         "imp": [],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json
index 964dcb48..72b4fbac 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json
@@ -26,9 +26,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json
index a9c3c23d..f13b70e1 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json
@@ -25,9 +25,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json
index c50f3d36..a80a1e09 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json
@@ -25,9 +25,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json
index 7ff88861..f0a11905 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json
@@ -14,9 +14,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -51,9 +51,9 @@
                     },
                     "tagid": "123_456"
                 }],
-                "site": {
-                    "domain": "prebid.org",
-                    "page": "prebid.org",
+                "app": {
+                    "id": "app-abc",
+                    "bundle": "com.prebid",
                     "publisher": {
                         "id": "123"
                     }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json
new file mode 100644
index 00000000..9155352a
--- /dev/null
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json
@@ -0,0 +1,38 @@
+{
+    "mockBidRequest": {
+        "id": "test-req-id",
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }],
+                "w": 300,
+                "h": 250
+            },
+            "ext": {
+                "bidder": {
+                    "publisherid": "123",
+                    "placementid": "456"
+                }
+            }
+        }],
+        "site": {
+            "domain": "prebid.org",
+            "page": "prebid.org"
+        },
+        "device": {
+            "ip": "152.193.6.74"
+        },
+        "user": {
+            "id": "db089de9-a62e-4861-a881-0ff15e052516",
+            "buyeruid": "v4_bidder_token"
+        },
+        "tmax": 500
+    },
+    "expectedMakeRequestsErrors": [{
+        "value": "Site impressions are not supported.",
+        "comparison": "literal"
+    }]
+}
\ No newline at end of file
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json
index 34c1eccc..45c34192 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json
@@ -21,9 +21,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
@@ -50,9 +50,9 @@
               "tagid": "123_456"
             }
           ],
-          "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org",
+          "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid",
             "publisher": {
                 "id": "123"
             }
diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go
index 7f567d61..912f1222 100644
--- a/adapters/audienceNetwork/facebook_test.go
+++ b/adapters/audienceNetwork/facebook_test.go
@@ -61,23 +61,6 @@ func TestMakeTimeoutNoticeApp(t *testing.T) {
 	assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.")
 }
 
-func TestMakeTimeoutNoticeSite(t *testing.T) {
-	req := adapters.RequestData{
-		Body: []byte(`{"id":"1234","imp":[{"id":"1234"}],"site":{"publisher":{"id":"5678"}}}`),
-	}
-	fba := NewFacebookBidder("test-platform-id", "test-app-secret")
-
-	tb, ok := fba.(adapters.TimeoutBidder)
-	if !ok {
-		t.Error("Facebook adapter is not a TimeoutAdapter")
-	}
-
-	toReq, err := tb.MakeTimeoutNotification(&req)
-	assert.Nil(t, err, "Facebook MakeTimeoutNotification() return an error %v", err)
-	expectedUri := "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=5678&auction=1234&ortb_loss_code=2"
-	assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.")
-}
-
 func TestMakeTimeoutNoticeBadRequest(t *testing.T) {
 	req := adapters.RequestData{
 		Body: []byte(`{"imp":[{{"id":"1234"}}`),
diff --git a/adapters/logicad/logicad_test.go b/adapters/logicad/logicad_test.go
new file mode 100644
index 00000000..adf20e4e
--- /dev/null
+++ b/adapters/logicad/logicad_test.go
@@ -0,0 +1,10 @@
+package logicad
+
+import (
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"testing"
+)
+
+func TestJsonSamples(t *testing.T) {
+	adapterstest.RunJSONBidderTest(t, "logicadtest", NewLogicadBidder("https://localhost/adrequest/prebidserver"))
+}
diff --git a/adapters/logicad/logicadtest/exemplary/banner.json b/adapters/logicad/logicadtest/exemplary/banner.json
new file mode 100644
index 00000000..f782cc2b
--- /dev/null
+++ b/adapters/logicad/logicadtest/exemplary/banner.json
@@ -0,0 +1,92 @@
+{
+  "mockBidRequest": {
+    "id": "testid",
+    "imp": [
+      {
+        "id": "testimpid",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tid": "testtid"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://localhost/adrequest/prebidserver",
+        "body": {
+          "id": "testid",
+          "imp": [
+            {
+              "id": "testimpid",
+              "tagid": "testtid",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 320,
+                    "h": 50
+                  }
+                ]
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "crid": "123",
+                  "adid": "456",
+                  "price": 0.12,
+                  "id": "testid",
+                  "impid": "testimpid",
+                  "cid": "789"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "crid": "123",
+            "adid": "456",
+            "price": 0.12,
+            "id": "testid",
+            "impid": "testimpid",
+            "cid": "789"
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/logicad/logicadtest/params/race/banner.json b/adapters/logicad/logicadtest/params/race/banner.json
new file mode 100644
index 00000000..7cb3de5a
--- /dev/null
+++ b/adapters/logicad/logicadtest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+  "tid": "testtid"
+}
\ No newline at end of file
diff --git a/adapters/logicad/logicadtest/supplemental/checkImp.json b/adapters/logicad/logicadtest/supplemental/checkImp.json
new file mode 100644
index 00000000..62c6e3e8
--- /dev/null
+++ b/adapters/logicad/logicadtest/supplemental/checkImp.json
@@ -0,0 +1,15 @@
+{
+  "mockBidRequest": {
+    "id": "testid",
+    "site": {
+      "id": "test",
+      "domain": "test.com"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "No impression in the bid request",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/logicad/logicadtest/supplemental/ext.json b/adapters/logicad/logicadtest/supplemental/ext.json
new file mode 100644
index 00000000..ad358920
--- /dev/null
+++ b/adapters/logicad/logicadtest/supplemental/ext.json
@@ -0,0 +1,31 @@
+{
+  "mockBidRequest": {
+    "id": "testid",
+    "imp": [
+      {
+        "id": "testimpid",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "tid": "testtid"
+        }
+      }
+    ]
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "unexpected end of JSON input",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/logicad/logicadtest/supplemental/missingtid.json b/adapters/logicad/logicadtest/supplemental/missingtid.json
new file mode 100644
index 00000000..5ed84cef
--- /dev/null
+++ b/adapters/logicad/logicadtest/supplemental/missingtid.json
@@ -0,0 +1,33 @@
+{
+  "mockBidRequest": {
+    "id": "testid",
+    "imp": [
+      {
+        "id": "testimpid",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tid": ""
+          }
+        }
+      }
+    ]
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "No tid value provided",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/logicad/logicadtest/supplemental/multiImpSameTid.json b/adapters/logicad/logicadtest/supplemental/multiImpSameTid.json
new file mode 100644
index 00000000..848733cd
--- /dev/null
+++ b/adapters/logicad/logicadtest/supplemental/multiImpSameTid.json
@@ -0,0 +1,112 @@
+{
+  "mockBidRequest": {
+    "id": "testid",
+    "imp": [
+      {
+        "id": "testimpid",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tid": "testtid"
+          }
+        }
+      },
+      {
+        "id": "testimpid",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tid": "testtid"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://localhost/adrequest/prebidserver",
+        "body": {
+          "id": "testid",
+          "imp": [
+            {
+              "id": "testimpid",
+              "tagid": "testtid",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ]
+              }
+            },
+            {
+              "id": "testimpid",
+              "tagid": "testtid",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ]
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "crid": "123",
+                  "adid": "456",
+                  "price": 0.12,
+                  "id": "testid",
+                  "impid": "testimpid",
+                  "cid": "789"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "crid": "123",
+            "adid": "456",
+            "price": 0.12,
+            "id": "testid",
+            "impid": "testimpid",
+            "cid": "789"
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/logicad/logicadtest/supplemental/responseCode.json b/adapters/logicad/logicadtest/supplemental/responseCode.json
new file mode 100644
index 00000000..471993ad
--- /dev/null
+++ b/adapters/logicad/logicadtest/supplemental/responseCode.json
@@ -0,0 +1,72 @@
+{
+  "mockBidRequest": {
+    "id": "testid",
+    "site": {
+      "id": "test"
+    },
+    "imp": [
+      {
+        "id": "testimpid",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tid": "testtid"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://localhost/adrequest/prebidserver",
+        "body": {
+          "id": "testid",
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 320,
+                    "h": 50
+                  }
+                ]
+              },
+              "id": "testimpid",
+              "tagid": "testtid"
+            }
+          ],
+          "site": {
+            "id": "test"
+          }
+        }
+      },
+      "mockResponse": {
+        "body": {
+          "seatbid": []
+        }
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected http status code: 0",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/logicad/logicadtest/supplemental/responseNoBid.json b/adapters/logicad/logicadtest/supplemental/responseNoBid.json
new file mode 100644
index 00000000..6ddab2ab
--- /dev/null
+++ b/adapters/logicad/logicadtest/supplemental/responseNoBid.json
@@ -0,0 +1,66 @@
+{
+  "mockBidRequest": {
+    "id": "testid",
+    "imp": [
+      {
+        "id": "testimpid",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tid": "testtid"
+          }
+        }
+      }
+    ],
+    "site": {
+      "id": "test"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://localhost/adrequest/prebidserver",
+        "body": {
+          "id": "testid",
+          "imp": [
+            {
+              "id": "testimpid",
+              "tagid": "testtid",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 320,
+                    "h": 50
+                  }
+                ]
+              }
+            }
+          ],
+          "site": {
+            "id": "test"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 204,
+        "body": {}
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
diff --git a/adapters/logicad/logicadtest/supplemental/responsebid.json b/adapters/logicad/logicadtest/supplemental/responsebid.json
new file mode 100644
index 00000000..59d1c2ac
--- /dev/null
+++ b/adapters/logicad/logicadtest/supplemental/responsebid.json
@@ -0,0 +1,73 @@
+{
+  "mockBidRequest": {
+    "id": "testid",
+    "site": {
+      "id": "test"
+    },
+    "imp": [
+      {
+        "id": "testimpid",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tid": "testtid"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://localhost/adrequest/prebidserver",
+        "body": {
+          "id": "testid",
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 320,
+                    "h": 50
+                  }
+                ]
+              },
+              "id": "testimpid",
+              "tagid": "testtid"
+            }
+          ],
+          "site": {
+            "id": "test"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "seatbid": []
+        }
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Invalid SeatBids count: 0",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/logicad/logicadtest/supplemental/site.json b/adapters/logicad/logicadtest/supplemental/site.json
new file mode 100644
index 00000000..c747413f
--- /dev/null
+++ b/adapters/logicad/logicadtest/supplemental/site.json
@@ -0,0 +1,98 @@
+{
+  "mockBidRequest": {
+    "id": "testid",
+    "site": {
+      "id": "test"
+    },
+    "imp": [
+      {
+        "id": "testimpid",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tid": "testtid"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://localhost/adrequest/prebidserver",
+        "body": {
+          "id": "testid",
+          "imp": [
+            {
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 320,
+                    "h": 50
+                  }
+                ]
+              },
+              "id": "testimpid",
+              "tagid": "testtid"
+            }
+          ],
+          "site": {
+            "id": "test"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "crid": "123",
+                  "adid": "456",
+                  "price": 0.12,
+                  "id": "testid",
+                  "impid": "testimpid",
+                  "cid": "789"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "crid": "123",
+            "adid": "456",
+            "price": 0.12,
+            "id": "testid",
+            "impid": "testimpid",
+            "cid": "789"
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/logicad/params_test.go b/adapters/logicad/params_test.go
new file mode 100644
index 00000000..eb344528
--- /dev/null
+++ b/adapters/logicad/params_test.go
@@ -0,0 +1,45 @@
+package logicad
+
+import (
+	"encoding/json"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"testing"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderLogicad, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected LunaMedia params: %s", validParam)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderLogicad, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"tid": "testtid"}`,
+}
+
+var invalidParams = []string{
+	`nil`,
+	``,
+	`[]`,
+	`true`,
+	`{"tid": 42}`,
+}
diff --git a/adapters/logicad/usersync_test.go b/adapters/logicad/usersync_test.go
new file mode 100644
index 00000000..89d6207d
--- /dev/null
+++ b/adapters/logicad/usersync_test.go
@@ -0,0 +1,31 @@
+package logicad
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLogicadSyncer(t *testing.T) {
+	syncURL := "https://localhost/cookiesender?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru=localhost%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID"
+	syncURLTemplate := template.Must(
+		template.New("sync-template").Parse(syncURL),
+	)
+
+	syncer := NewLogicadSyncer(syncURLTemplate)
+	syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+		GDPR: gdpr.Policy{
+			Signal:  "1",
+			Consent: "A",
+		},
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, "https://localhost/cookiesender?r=true&gdpr=1&gdpr_consent=A&ru=localhost%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%24UID", syncInfo.URL)
+	assert.Equal(t, "redirect", syncInfo.Type)
+	assert.EqualValues(t, 0, syncer.GDPRVendorID())
+	assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go
index be086f5a..5dd236d9 100644
--- a/adapters/pubmatic/pubmatic_test.go
+++ b/adapters/pubmatic/pubmatic_test.go
@@ -674,18 +674,18 @@ func TestPubmaticSampleRequest(t *testing.T) {
 }
 
 func TestGetBidTypeVideo(t *testing.T) {
-	extJSON := `{"BidType":1}`
-	extrm := json.RawMessage(extJSON)
-	actualBidTypeValue := getBidType(extrm)
+	pubmaticExt := new(pubmaticBidExt)
+	pubmaticExt.BidType = new(int)
+	*pubmaticExt.BidType = 1
+	actualBidTypeValue := getBidType(pubmaticExt)
 	if actualBidTypeValue != openrtb_ext.BidTypeVideo {
 		t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeVideo, actualBidTypeValue)
 	}
 }
 
 func TestGetBidTypeForMissingBidTypeExt(t *testing.T) {
-	extJSON := `{}`
-	extrm := json.RawMessage(extJSON)
-	actualBidTypeValue := getBidType(extrm)
+	pubmaticExt := pubmaticBidExt{}
+	actualBidTypeValue := getBidType(&pubmaticExt)
 	// banner is the default bid type when no bidType key is present in the bid.ext
 	if actualBidTypeValue != "banner" {
 		t.Errorf("Expected Bid Type value was: banner, actual value is: %v", actualBidTypeValue)
@@ -693,27 +693,30 @@ func TestGetBidTypeForMissingBidTypeExt(t *testing.T) {
 }
 
 func TestGetBidTypeBanner(t *testing.T) {
-	extJSON := `{"BidType":0}`
-	extrm := json.RawMessage(extJSON)
-	actualBidTypeValue := getBidType(extrm)
+	pubmaticExt := new(pubmaticBidExt)
+	pubmaticExt.BidType = new(int)
+	*pubmaticExt.BidType = 0
+	actualBidTypeValue := getBidType(pubmaticExt)
 	if actualBidTypeValue != openrtb_ext.BidTypeBanner {
 		t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeBanner, actualBidTypeValue)
 	}
 }
 
 func TestGetBidTypeNative(t *testing.T) {
-	extJSON := `{"BidType":2}`
-	extrm := json.RawMessage(extJSON)
-	actualBidTypeValue := getBidType(extrm)
+	pubmaticExt := new(pubmaticBidExt)
+	pubmaticExt.BidType = new(int)
+	*pubmaticExt.BidType = 2
+	actualBidTypeValue := getBidType(pubmaticExt)
 	if actualBidTypeValue != openrtb_ext.BidTypeNative {
 		t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeNative, actualBidTypeValue)
 	}
 }
 
 func TestGetBidTypeForUnsupportedCode(t *testing.T) {
-	extJSON := `{"BidType":99}`
-	extrm := json.RawMessage(extJSON)
-	actualBidTypeValue := getBidType(extrm)
+	pubmaticExt := new(pubmaticBidExt)
+	pubmaticExt.BidType = new(int)
+	*pubmaticExt.BidType = 99
+	actualBidTypeValue := getBidType(pubmaticExt)
 	if actualBidTypeValue != openrtb_ext.BidTypeBanner {
 		t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeBanner, actualBidTypeValue)
 	}
diff --git a/adapters/pubmatic/pubmatictest/exemplary/video.json b/adapters/pubmatic/pubmatictest/exemplary/video.json
index 25ed366e..4c874535 100644
--- a/adapters/pubmatic/pubmatictest/exemplary/video.json
+++ b/adapters/pubmatic/pubmatictest/exemplary/video.json
@@ -112,10 +112,14 @@
                   "h": 250,
                   "w": 300,
                   "dealid":"test deal",
+                  "cat" : ["IAB-1", "IAB-2"], 
                   "ext": {
                     "dspid": 6,
                     "deal_channel": 1,
-                    "BidType": 1
+                    "BidType": 1,
+                    "video" : {
+                      "duration" : 5  
+                    }
                   }
                 }]
               }
@@ -139,6 +143,9 @@
               "adid": "29681110",
               "adm": "some-test-ad",
               "adomain": ["pubmatic.com"],
+              "cat": [
+                "IAB-1"
+              ],
               "crid": "29681110",
               "w": 300,
               "h": 250,
@@ -146,12 +153,18 @@
               "ext": {
                 "dspid": 6,
                 "deal_channel": 1,
-                "BidType": 1
+                "BidType": 1,
+                "video" : {
+                  "duration" : 5  
+                }
               }
             },
-            "type": "video"
+            "type": "video",
+            "video" :{
+              "duration" : 5
+            }
           }
         ]
       }
     ]
-  }
\ No newline at end of file
+  }
diff --git a/config/config_test.go b/config/config_test.go
index 3456694d..4774d9d6 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -33,6 +33,7 @@ func TestDefaults(t *testing.T) {
 	cmpBools(t, "account_required", cfg.AccountRequired, false)
 	cmpInts(t, "metrics.influxdb.collection_rate_seconds", cfg.Metrics.Influxdb.MetricSendInterval, 20)
 	cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, false)
+	cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true)
 	cmpStrings(t, "certificates_file", cfg.PemCertsFile, "")
 }
 
@@ -89,6 +90,7 @@ metrics:
     metric_send_interval: 30
   disabled_metrics:
     account_adapter_details: true
+    adapter_connections_metrics: true
 datacache:
   type: postgres
   filename: /usr/db/db.db
@@ -294,6 +296,7 @@ func TestFullConfig(t *testing.T) {
 	cmpStrings(t, "adapters.rhythmone.usersync_url", cfg.Adapters[string(openrtb_ext.BidderRhythmone)].UserSyncURL, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Fprebid-server.prebid.org%2F%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D")
 	cmpBools(t, "account_required", cfg.AccountRequired, true)
 	cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true)
+	cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true)
 	cmpStrings(t, "certificates_file", cfg.PemCertsFile, "/etc/ssl/cert.pem")
 	cmpStrings(t, "request_validation.ipv4_private_networks", cfg.RequestValidation.IPv4PrivateNetworks[0], "1.1.1.0/24")
 	cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16")
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 731fd55e..692d3fb0 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -755,8 +755,9 @@ func TestQueryParamOverrides(t *testing.T) {
 	curl := "http://example.com"
 	slot := "1234"
 	timeout := int64(500)
+	account := "12345"
 
-	request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&debug=1&curl=%s&slot=%s&timeout=%d", requestID, curl, slot, timeout), nil)
+	request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&debug=1&curl=%s&slot=%s&timeout=%d&account=%s", requestID, curl, slot, timeout, account), nil)
 	recorder := httptest.NewRecorder()
 	endpoint(recorder, request, nil)
 
@@ -784,6 +785,10 @@ func TestQueryParamOverrides(t *testing.T) {
 	if resolvedRequest.Site == nil || resolvedRequest.Site.Page != curl {
 		t.Errorf("Expected Site.Page to equal curl (%s), got: %s", curl, resolvedRequest.Site.Page)
 	}
+
+	if resolvedRequest.Site == nil || resolvedRequest.Site.Publisher == nil || resolvedRequest.Site.Publisher.ID != account {
+		t.Errorf("Expected Site.Publisher.ID to equal (%s), got: %s", account, resolvedRequest.Site.Publisher.ID)
+	}
 }
 
 func TestOverrideDimensions(t *testing.T) {
@@ -876,6 +881,7 @@ type formatOverrideSpec struct {
 	overrideWidth  uint64
 	overrideHeight uint64
 	multisize      string
+	account        string
 	expect         []openrtb.Format
 }
 
@@ -897,7 +903,7 @@ func (s formatOverrideSpec) execute(t *testing.T) {
 		openrtb_ext.BidderMap,
 	)
 
-	url := fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&debug=1&w=%d&h=%d&ow=%d&oh=%d&ms=%s", s.width, s.height, s.overrideWidth, s.overrideHeight, s.multisize)
+	url := fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&debug=1&w=%d&h=%d&ow=%d&oh=%d&ms=%s&account=%s", s.width, s.height, s.overrideWidth, s.overrideHeight, s.multisize, s.account)
 	request := httptest.NewRequest("GET", url, nil)
 	recorder := httptest.NewRecorder()
 	endpoint(recorder, request, nil)
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 97f0038a..957760c6 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -604,6 +604,189 @@ func TestImplicitIPsEndToEnd(t *testing.T) {
 	}
 }
 
+func TestImplicitDNT(t *testing.T) {
+	var (
+		disabled int8 = 0
+		enabled  int8 = 1
+	)
+	testCases := []struct {
+		description     string
+		dntHeader       string
+		request         openrtb.BidRequest
+		expectedRequest openrtb.BidRequest
+	}{
+		{
+			description:     "Device Missing - Not Set In Header",
+			dntHeader:       "",
+			request:         openrtb.BidRequest{},
+			expectedRequest: openrtb.BidRequest{},
+		},
+		{
+			description: "Device Missing - Set To 0 In Header",
+			dntHeader:   "0",
+			request:     openrtb.BidRequest{},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &disabled,
+				},
+			},
+		},
+		{
+			description: "Device Missing - Set To 1 In Header",
+			dntHeader:   "1",
+			request:     openrtb.BidRequest{},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+		{
+			description: "Not Set In Request - Not Set In Header",
+			dntHeader:   "",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{},
+			},
+		},
+		{
+			description: "Not Set In Request - Set To 0 In Header",
+			dntHeader:   "0",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &disabled,
+				},
+			},
+		},
+		{
+			description: "Not Set In Request - Set To 1 In Header",
+			dntHeader:   "1",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+		{
+			description: "Set In Request - Not Set In Header",
+			dntHeader:   "",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+		{
+			description: "Set In Request - Set To 0 In Header",
+			dntHeader:   "0",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+		{
+			description: "Set In Request - Set To 1 In Header",
+			dntHeader:   "1",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", nil)
+		httpReq.Header.Set("DNT", test.dntHeader)
+		setDoNotTrackImplicitly(httpReq, &test.request)
+		assert.Equal(t, test.expectedRequest, test.request)
+	}
+}
+
+func TestImplicitDNTEndToEnd(t *testing.T) {
+	var (
+		disabled int8 = 0
+		enabled  int8 = 1
+	)
+	testCases := []struct {
+		description string
+		reqJSONFile string
+		dntHeader   string
+		expectedDNT *int8
+	}{
+		{
+			description: "Not Set In Request - Not Set In Header",
+			reqJSONFile: "site.json",
+			dntHeader:   "",
+			expectedDNT: nil,
+		},
+		{
+			description: "Not Set In Request - Set To 0 In Header",
+			reqJSONFile: "site.json",
+			dntHeader:   "0",
+			expectedDNT: &disabled,
+		},
+		{
+			description: "Not Set In Request - Set To 1 In Header",
+			reqJSONFile: "site.json",
+			dntHeader:   "1",
+			expectedDNT: &enabled,
+		},
+		{
+			description: "Set In Request - Not Set In Header",
+			reqJSONFile: "site-has-dnt.json",
+			dntHeader:   "",
+			expectedDNT: &enabled, // Hardcoded value in test file.
+		},
+		{
+			description: "Set In Request - Not Overwritten By Header",
+			reqJSONFile: "site-has-dnt.json",
+			dntHeader:   "0",
+			expectedDNT: &enabled, // Hardcoded value in test file.
+		},
+	}
+
+	metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
+	for _, test := range testCases {
+		exchange := &nobidExchange{}
+		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+
+		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
+		httpReq.Header.Set("DNT", test.dntHeader)
+
+		endpoint(httptest.NewRecorder(), httpReq, nil)
+
+		result := exchange.gotRequest
+		if !assert.NotEmpty(t, result, test.description+"Request received by the exchange.") {
+			t.FailNow()
+		}
+		assert.Equal(t, test.expectedDNT, result.Device.DNT, test.description+":dnt")
+	}
+}
 func TestImplicitSecure(t *testing.T) {
 	httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
 	httpReq.Header.Set(http.CanonicalHeaderKey("X-Forwarded-Proto"), "https")
@@ -1039,6 +1222,53 @@ func TestCCPAInvalid(t *testing.T) {
 	assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request")
 }
 
+func TestSChainInvalid(t *testing.T) {
+	deps := &endpointDeps{
+		&nobidExchange{},
+		newParamsValidator(t),
+		&mockStoredReqFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		&config.Configuration{},
+		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
+		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+		map[string]string{},
+		false,
+		[]byte{},
+		openrtb_ext.BidderMap,
+		nil,
+		nil,
+		hardcodedResponseIPValidator{response: true},
+	}
+
+	ui := uint64(1)
+	req := openrtb.BidRequest{
+		ID: "someID",
+		Imp: []openrtb.Imp{
+			{
+				ID: "imp-ID",
+				Banner: &openrtb.Banner{
+					W: &ui,
+					H: &ui,
+				},
+				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
+			},
+		},
+		Site: &openrtb.Site{
+			ID: "myID",
+		},
+		Regs: &openrtb.Regs{
+			Ext: json.RawMessage(`{"us_privacy":"abcd"}`),
+		},
+		Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
+	}
+
+	errL := deps.validateRequest(&req)
+
+	expectedError := fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.")
+	assert.ElementsMatch(t, errL, []error{expectedError})
+}
+
 func TestSanitizeRequest(t *testing.T) {
 	testCases := []struct {
 		description  string
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index b776715a..1a27b72a 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -1,22 +1,30 @@
 package exchange
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"net/http/httptrace"
+	"strings"
 	"testing"
 	"time"
 
+	"github.com/golang/glog"
 	"github.com/mxmCherry/openrtb"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currencies"
 	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/prebid/prebid-server/pbsmetrics"
+	metricsConf "github.com/prebid/prebid-server/pbsmetrics/config"
 	metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 
 	nativeRequests "github.com/mxmCherry/openrtb/native/request"
 	nativeResponse "github.com/mxmCherry/openrtb/native/response"
@@ -66,7 +74,7 @@ func TestSingleBidder(t *testing.T) {
 		},
 		bidResponse: mockBidderResponse,
 	}
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
 	currencyConverter := currencies.NewRateConverterDefault()
 	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 
@@ -154,7 +162,7 @@ func TestMultiBidder(t *testing.T) {
 			}},
 		bidResponse: mockBidderResponse,
 	}
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
 	currencyConverter := currencies.NewRateConverterDefault()
 	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 
@@ -192,8 +200,10 @@ func TestBidderTimeout(t *testing.T) {
 	defer server.Close()
 
 	bidder := &bidderAdapter{
-		Bidder: &mixedMultiBidder{},
-		Client: server.Client(),
+		Bidder:     &mixedMultiBidder{},
+		BidderName: openrtb_ext.BidderAppnexus,
+		Client:     server.Client(),
+		me:         &metricsConf.DummyMetricsEngine{},
 	}
 
 	callInfo := bidder.doRequest(ctx, &adapters.RequestData{
@@ -233,8 +243,10 @@ func TestConnectionClose(t *testing.T) {
 	server = httptest.NewServer(handler)
 
 	bidder := &bidderAdapter{
-		Bidder: &mixedMultiBidder{},
-		Client: server.Client(),
+		Bidder:     &mixedMultiBidder{},
+		Client:     server.Client(),
+		BidderName: openrtb_ext.BidderAppnexus,
+		me:         &metricsConf.DummyMetricsEngine{},
 	}
 
 	callInfo := bidder.doRequest(context.Background(), &adapters.RequestData{
@@ -512,7 +524,7 @@ func TestMultiCurrencies(t *testing.T) {
 		)
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
 		currencyConverter := currencies.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -661,7 +673,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
 		currencyConverter := currencies.NewRateConverterDefault()
 		seatBid, errs := bidder.requestBid(
 			context.Background(),
@@ -827,7 +839,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
 		currencyConverter := currencies.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -943,7 +955,7 @@ func TestServerCallDebugging(t *testing.T) {
 			Headers: http.Header{},
 		},
 	}
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
 	currencyConverter := currencies.NewRateConverterDefault()
 
 	bids, _ := bidder.requestBid(
@@ -1055,7 +1067,7 @@ func TestMobileNativeTypes(t *testing.T) {
 			},
 			bidResponse: tc.mockBidderResponse,
 		}
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
 		currencyConverter := currencies.NewRateConverterDefault()
 
 		seatBids, _ := bidder.requestBid(
@@ -1076,7 +1088,7 @@ func TestMobileNativeTypes(t *testing.T) {
 }
 
 func TestErrorReporting(t *testing.T) {
-	bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
+	bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
 	currencyConverter := currencies.NewRateConverterDefault()
 	bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 	if bids != nil {
@@ -1231,14 +1243,90 @@ func TestSetAssetTypes(t *testing.T) {
 	}
 }
 
+func TestCallRecordAdapterConnections(t *testing.T) {
+	// Setup mock server
+	respStatus := 200
+	respBody := "{\"bid\":false}"
+	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	defer server.Close()
+
+	// declare requestBid parameters
+	bidAdjustment := 2.0
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte("{\"key\":\"val\"}"),
+			Headers: http.Header{},
+		},
+		bidResponse: &adapters.BidderResponse{},
+	}
+
+	// setup a mock metrics engine and its expectation
+	metrics := &pbsmetrics.MetricsEngineMock{}
+	expectedAdapterName := openrtb_ext.BidderAppnexus
+	compareConnWaitTime := func(dur time.Duration) bool { return dur.Nanoseconds() > 0 }
+
+	metrics.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once()
+
+	// Run requestBid using an http.Client with a mock handler
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus)
+	_, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencies.NewRateConverterDefault().Rates(), &adapters.ExtraRequestInfo{})
+
+	// Assert no errors
+	assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
+
+	// Assert RecordAdapterConnections() was called with the parameters we expected
+	metrics.AssertExpectations(t)
+}
+
+type DNSDoneTripper struct{}
+
+func (DNSDoneTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+	//Access the httptrace.ClientTrace
+	trace := httptrace.ContextClientTrace(req.Context())
+
+	//Force DNSDone call defined in exchange/bidder.go
+	trace.DNSDone(httptrace.DNSDoneInfo{})
+
+	resp := &http.Response{
+		StatusCode: 200,
+		Header:     map[string][]string{"Location": {"http://www.example.com/"}},
+		Body:       ioutil.NopCloser(strings.NewReader("postBody")),
+	}
+	return resp, nil
+}
+
+func TestCallRecordRecordDNSTime(t *testing.T) {
+	// setup a mock metrics engine and its expectation
+	metricsMock := &pbsmetrics.MetricsEngineMock{}
+	metricsMock.Mock.On("RecordDNSTime", mock.Anything).Return()
+
+	// Instantiate the bidder that will send the request. We'll make sure to use an
+	// http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{})
+	// gets called
+	bidder := &bidderAdapter{
+		Bidder: &mixedMultiBidder{},
+		Client: &http.Client{Transport: DNSDoneTripper{}},
+		me:     metricsMock,
+	}
+
+	// Run test
+	bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"})
+
+	// Tried one or another, none seem to work without panicking
+	metricsMock.AssertExpectations(t)
+}
+
 func TestTimeoutNotificationOff(t *testing.T) {
 	respBody := "{\"bid\":false}"
 	respStatus := 200
 	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
 	defer server.Close()
 
-	bidderImpl := &notifingBidder{
-		notiRequest: adapters.RequestData{
+	bidderImpl := &notifyingBidder{
+		notifyRequest: adapters.RequestData{
 			Method:  "GET",
 			Uri:     server.URL + "/notify/me",
 			Body:    nil,
@@ -1246,47 +1334,93 @@ func TestTimeoutNotificationOff(t *testing.T) {
 		},
 	}
 	bidder := &bidderAdapter{
-		Bidder:      bidderImpl,
-		Client:      server.Client(),
-		DebugConfig: config.Debug{},
-		me:          &metricsConfig.DummyMetricsEngine{},
+		Bidder: bidderImpl,
+		Client: server.Client(),
+		config: bidderAdapterConfig{Debug: config.Debug{}},
+		me:     &metricsConf.DummyMetricsEngine{},
 	}
 	if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok {
 		t.Error("Failed to cast bidder to a TimeoutBidder")
 	} else {
-		bidder.doTimeoutNotification(tb, &adapters.RequestData{})
+		bidder.doTimeoutNotification(tb, &adapters.RequestData{}, glog.Warningf)
 	}
 }
 
 func TestTimeoutNotificationOn(t *testing.T) {
-	respBody := "{\"bid\":false}"
-	respStatus := 200
-	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	// Expire context immediately to force timeout handler.
+	ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now())
+	cancelFunc()
+
+	// Notification logic is hardcoded for 200ms. We need to wait for a little longer than that.
+	server := httptest.NewServer(mockSlowHandler(205*time.Millisecond, 200, `{"bid":false}`))
 	defer server.Close()
 
-	bidderImpl := &notifingBidder{
-		notiRequest: adapters.RequestData{
+	bidder := &notifyingBidder{
+		notifyRequest: adapters.RequestData{
 			Method:  "GET",
 			Uri:     server.URL + "/notify/me",
 			Body:    nil,
 			Headers: http.Header{},
 		},
 	}
-	bidder := &bidderAdapter{
-		Bidder: bidderImpl,
+
+	// Wrap with BidderInfo to mimic exchange.go flow.
+	bidderWrappedWithInfo := wrapWithBidderInfo(bidder)
+
+	bidderAdapter := &bidderAdapter{
+		Bidder: bidderWrappedWithInfo,
 		Client: server.Client(),
-		DebugConfig: config.Debug{
-			TimeoutNotification: config.TimeoutNotification{
-				Log: true,
+		config: bidderAdapterConfig{
+			Debug: config.Debug{
+				TimeoutNotification: config.TimeoutNotification{
+					Log:          true,
+					SamplingRate: 1.0,
+				},
 			},
 		},
-		me: &metricsConfig.DummyMetricsEngine{},
+		me: &metricsConf.DummyMetricsEngine{},
 	}
-	if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok {
-		t.Error("Failed to cast bidder to a TimeoutBidder")
-	} else {
-		bidder.doTimeoutNotification(tb, &adapters.RequestData{})
+
+	// Unwrap To Mimic exchange.go Casting Code
+	var coreBidder adapters.Bidder = bidderAdapter.Bidder
+	if b, ok := coreBidder.(*adapters.InfoAwareBidder); ok {
+		coreBidder = b.Bidder
+	}
+	if _, ok := coreBidder.(adapters.TimeoutBidder); !ok {
+		t.Fatal("Failed to cast bidder to a TimeoutBidder")
+	}
+
+	bidRequest := adapters.RequestData{
+		Method: "POST",
+		Uri:    server.URL,
+		Body:   []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`),
+	}
+
+	var loggerBuffer bytes.Buffer
+	logger := func(msg string, args ...interface{}) {
+		loggerBuffer.WriteString(fmt.Sprintf(fmt.Sprintln(msg), args...))
 	}
+
+	bidderAdapter.doRequestImpl(ctx, &bidRequest, logger)
+
+	// Wait a little longer than the 205ms mock server sleep.
+	time.Sleep(210 * time.Millisecond)
+
+	logExpected := "TimeoutNotification: error:(context deadline exceeded) body:\n"
+	logActual := loggerBuffer.String()
+	assert.EqualValues(t, logExpected, logActual)
+}
+
+func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder {
+	bidderInfo := adapters.BidderInfo{
+		Status: adapters.StatusActive,
+		Capabilities: &adapters.CapabilitiesInfo{
+			App: &adapters.PlatformInfo{
+				MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner},
+			},
+		},
+	}
+	return adapters.EnforceBidderInfo(bidder, bidderInfo)
 }
 
 type goodSingleBidder struct {
@@ -1363,18 +1497,19 @@ func (bidder *bidRejector) MakeBids(internalRequest *openrtb.BidRequest, externa
 	return nil, []error{errors.New("Can't make a response.")}
 }
 
-type notifingBidder struct {
-	notiRequest adapters.RequestData
+type notifyingBidder struct {
+	requests      []*adapters.RequestData
+	notifyRequest adapters.RequestData
 }
 
-func (bidder *notifingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
-	return nil, nil
+func (bidder *notifyingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	return bidder.requests, nil
 }
 
-func (bidder *notifingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
 	return nil, nil
 }
 
-func (bidder *notifingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) {
-	return &bidder.notiRequest, nil
+func (bidder *notifyingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) {
+	return &bidder.notifyRequest, nil
 }
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 161b24fd..96f740de 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -1830,6 +1830,15 @@ func mockHandler(statusCode int, getBody string, postBody string) http.Handler {
 	})
 }
 
+func mockSlowHandler(delay time.Duration, statusCode int, body string) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		time.Sleep(delay)
+
+		w.WriteHeader(statusCode)
+		w.Write([]byte(body))
+	})
+}
+
 type wellBehavedCache struct{}
 
 func (c *wellBehavedCache) GetExtCacheData() (string, string) {
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 72de1d42..16955e97 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -134,7 +134,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb.Bid, mockServerU
 		adapterMap[bidder] = adaptBidder(&mockTargetingBidder{
 			mockServerURL: mockServerURL,
 			bids:          bids,
-		}, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
+		}, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
 	}
 	return adapterMap
 }
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index e50d0f77..608e6a17 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -15,7 +15,9 @@ import (
 // permissionsMock mocks the Permissions interface for tests
 //
 // It only allows appnexus for GDPR consent
-type permissionsMock struct{}
+type permissionsMock struct {
+	personalInfoAllowed bool
+}
 
 func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
 	return true, nil
@@ -26,10 +28,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 }
 
 func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
-	if bidder == "appnexus" {
-		return true, true, nil
-	}
-	return false, false, nil
+	return p.personalInfoAllowed, p.personalInfoAllowed, nil
 }
 
 func (p *permissionsMock) AMPException() bool {
@@ -80,7 +79,7 @@ func TestCleanOpenRTBRequests(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
+		reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig)
 		if test.hasError {
 			assert.NotNil(t, err, "Error shouldn't be nil")
 		} else {
@@ -92,26 +91,48 @@ func TestCleanOpenRTBRequests(t *testing.T) {
 
 func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 	testCases := []struct {
-		description     string
-		enforceCCPA     bool
-		expectDataScrub bool
+		description         string
+		ccpaConsent         string
+		enforceCCPA         bool
+		expectDataScrub     bool
+		expectPrivacyLabels pbsmetrics.PrivacyLabels
 	}{
 		{
-			description:     "Feature Flag Enabled",
+			description:     "Feature Flag Enabled - Opt Out",
+			ccpaConsent:     "1-Y-",
 			enforceCCPA:     true,
 			expectDataScrub: true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				CCPAProvided: true,
+				CCPAEnforced: true,
+			},
+		},
+		{
+			description:     "Feature Flag Enabled - Opt In",
+			ccpaConsent:     "1-N-",
+			enforceCCPA:     true,
+			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				CCPAProvided: true,
+				CCPAEnforced: false,
+			},
 		},
 		{
 			description:     "Feature Flag Disabled",
+			ccpaConsent:     "1-Y-",
 			enforceCCPA:     false,
 			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				CCPAProvided: false,
+				CCPAEnforced: false,
+			},
 		},
 	}
 
 	for _, test := range testCases {
 		req := newBidRequest(t)
 		req.Regs = &openrtb.Regs{
-			Ext: json.RawMessage(`{"us_privacy":"1-Y-"}`),
+			Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`),
 		}
 
 		privacyConfig := config.Privacy{
@@ -120,11 +141,10 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 			},
 		}
 
-		results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
+		results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig)
 		result := results["appnexus"]
 
 		assert.Nil(t, errs)
-
 		if test.expectDataScrub {
 			assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
@@ -132,6 +152,137 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 			assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
 		}
+		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
+	}
+}
+
+func TestCleanOpenRTBRequestsCOPPA(t *testing.T) {
+	testCases := []struct {
+		description         string
+		coppa               int8
+		expectDataScrub     bool
+		expectPrivacyLabels pbsmetrics.PrivacyLabels
+	}{
+		{
+			description:     "Enabled",
+			coppa:           1,
+			expectDataScrub: true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				COPPAEnforced: true,
+			},
+		},
+		{
+			description:     "Disabled",
+			coppa:           0,
+			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				COPPAEnforced: false,
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		req := newBidRequest(t)
+		req.Regs = &openrtb.Regs{COPPA: test.coppa}
+
+		results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{})
+		result := results["appnexus"]
+
+		assert.Nil(t, errs)
+		if test.expectDataScrub {
+			assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
+			assert.Equal(t, result.User.Yob, int64(0), test.description+":User.Yob")
+		} else {
+			assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
+			assert.NotEqual(t, result.User.Yob, int64(0), test.description+":User.Yob")
+		}
+		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
+	}
+}
+
+func TestCleanOpenRTBRequestsSChain(t *testing.T) {
+	testCases := []struct {
+		description  string
+		inSourceExt  json.RawMessage
+		inExt        json.RawMessage
+		outSourceExt json.RawMessage
+		outExt       json.RawMessage
+		hasError     bool
+	}{
+		{
+			description:  "Empty root ext and source ext",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(``),
+			outSourceExt: json.RawMessage(``),
+			outExt:       json.RawMessage(``),
+			hasError:     false,
+		},
+		{
+			description:  "No schains in root ext and empty source ext",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[]}}`),
+			outSourceExt: json.RawMessage(``),
+			outExt:       json.RawMessage(`{"prebid":{}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use source schain -- no bidder schain or wildcard schain in ext.prebid.schains",
+			inSourceExt:  json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[{"bidders":["bidder1"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`),
+			outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`),
+			outExt:       json.RawMessage(`{"prebid":{}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use schain for bidder in ext.prebid.schains",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`),
+			outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`),
+			outExt:       json.RawMessage(`{"prebid":{}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use wildcard schain in ext.prebid.schains",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`),
+			outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`),
+			outExt:       json.RawMessage(`{"prebid":{}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use schain for bidder in ext.prebid.schains instead of wildcard",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"},"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"wildcard.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}} ]}}`),
+			outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`),
+			outExt:       json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"}}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use source schain -- multiple (two) bidder schains in ext.prebid.schains",
+			inSourceExt:  json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
+			outSourceExt: nil,
+			outExt:       nil,
+			hasError:     true,
+		},
+	}
+
+	for _, test := range testCases {
+		req := newBidRequest(t)
+		req.Source.Ext = test.inSourceExt
+		req.Ext = test.inExt
+
+		results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, config.Privacy{})
+		result := results["appnexus"]
+
+		if test.hasError == true {
+			assert.NotNil(t, errs)
+			assert.Nil(t, result)
+		} else {
+			assert.Nil(t, errs)
+			assert.Equal(t, test.outSourceExt, result.Source.Ext, test.description+":Source.Ext")
+			assert.Equal(t, test.outExt, result.Ext, test.description+":Ext")
+		}
 	}
 }
 
@@ -141,34 +292,47 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 		disabled int8 = 0
 	)
 	testCases := []struct {
-		description     string
-		lmt             *int8
-		enforceLMT      bool
-		expectDataScrub bool
+		description         string
+		lmt                 *int8
+		enforceLMT          bool
+		expectDataScrub     bool
+		expectPrivacyLabels pbsmetrics.PrivacyLabels
 	}{
 		{
 			description:     "Feature Flag Enabled - OpenTRB Enabled",
 			lmt:             &enabled,
 			enforceLMT:      true,
 			expectDataScrub: true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				LMTEnforced: true,
+			},
 		},
 		{
 			description:     "Feature Flag Disabled - OpenTRB Enabled",
 			lmt:             &enabled,
 			enforceLMT:      false,
 			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				LMTEnforced: false,
+			},
 		},
 		{
 			description:     "Feature Flag Enabled - OpenTRB Disabled",
 			lmt:             &disabled,
 			enforceLMT:      true,
 			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				LMTEnforced: false,
+			},
 		},
 		{
 			description:     "Feature Flag Disabled - OpenTRB Disabled",
 			lmt:             &disabled,
 			enforceLMT:      false,
 			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				LMTEnforced: false,
+			},
 		},
 	}
 
@@ -182,11 +346,10 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 			},
 		}
 
-		results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
+		results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig)
 		result := results["appnexus"]
 
 		assert.Nil(t, errs)
-
 		if test.expectDataScrub {
 			assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
@@ -194,6 +357,88 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 			assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
 		}
+		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
+	}
+}
+
+func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
+	testCases := []struct {
+		description         string
+		gdpr                string
+		gdprConsent         string
+		gdprScrub           bool
+		enforceGDPR         bool
+		expectPrivacyLabels pbsmetrics.PrivacyLabels
+	}{
+		{
+			description: "Enforce - TCF Invalid",
+			gdpr:        "1",
+			gdprConsent: "malformed",
+			gdprScrub:   false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				GDPREnforced:   true,
+				GDPRTCFVersion: "",
+			},
+		},
+		{
+			description: "Enforce - TCF 1",
+			gdpr:        "1",
+			gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprScrub:   true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				GDPREnforced:   true,
+				GDPRTCFVersion: pbsmetrics.TCFVersionV1,
+			},
+		},
+		{
+			description: "Enforce - TCF 2",
+			gdpr:        "1",
+			gdprConsent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
+			gdprScrub:   true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				GDPREnforced:   true,
+				GDPRTCFVersion: pbsmetrics.TCFVersionV2,
+			},
+		},
+		{
+			description: "Not Enforce - TCF 1",
+			gdpr:        "0",
+			gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprScrub:   false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				GDPREnforced:   false,
+				GDPRTCFVersion: "",
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		req := newBidRequest(t)
+		req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`)
+		req.Regs = &openrtb.Regs{
+			Ext: json.RawMessage(`{"gdpr":` + test.gdpr + `}`),
+		}
+
+		privacyConfig := config.Privacy{
+			GDPR: config.GDPR{
+				TCF2: config.TCF2{
+					Enabled: true,
+				},
+			},
+		}
+
+		results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: !test.gdprScrub}, true, privacyConfig)
+		result := results["appnexus"]
+
+		assert.Nil(t, errs)
+		if test.gdprScrub {
+			assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
+			assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+		} else {
+			assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
+			assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+		}
+		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
 	}
 }
 
@@ -266,6 +511,7 @@ func newBidRequest(t *testing.T) *openrtb.BidRequest {
 		User: &openrtb.User{
 			ID:       "our-id",
 			BuyerUID: "their-id",
+			Yob:      1982,
 			Ext:      json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`),
 		},
 		Imp: []openrtb.Imp{{
@@ -302,5 +548,99 @@ func TestRandomizeList(t *testing.T) {
 	if len(adapters) != 1 {
 		t.Errorf("RandomizeList, expected a list of 1, found %d", len(adapters))
 	}
+}
+
+func TestBidderToPrebidChains(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: []*openrtb_ext.ExtRequestPrebidSChain{
+				{
+					Bidders: []string{"Bidder1", "Bidder2"},
+					SChain: openrtb_ext.ExtRequestPrebidSChainSChain{
+						Complete: 1,
+						Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
+							{
+								ASI:    "asi1",
+								SID:    "sid1",
+								Name:   "name1",
+								RID:    "rid1",
+								Domain: "domain1",
+								HP:     1,
+							},
+							{
+								ASI:    "asi2",
+								SID:    "sid2",
+								Name:   "name2",
+								RID:    "rid2",
+								Domain: "domain2",
+								HP:     2,
+							},
+						},
+						Ver: "version1",
+					},
+				},
+				{
+					Bidders: []string{"Bidder3", "Bidder4"},
+					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
+				},
+			},
+		},
+	}
+
+	output, err := BidderToPrebidSChains(&input)
+
+	assert.Nil(t, err)
+	assert.Equal(t, len(output), 4)
+	assert.Same(t, output["Bidder1"], &input.Prebid.SChains[0].SChain)
+	assert.Same(t, output["Bidder2"], &input.Prebid.SChains[0].SChain)
+	assert.Same(t, output["Bidder3"], &input.Prebid.SChains[1].SChain)
+	assert.Same(t, output["Bidder4"], &input.Prebid.SChains[1].SChain)
+}
+
+func TestBidderToPrebidChainsDiscardMultipleChainsForBidder(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: []*openrtb_ext.ExtRequestPrebidSChain{
+				{
+					Bidders: []string{"Bidder1"},
+					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
+				},
+				{
+					Bidders: []string{"Bidder1", "Bidder2"},
+					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
+				},
+			},
+		},
+	}
+
+	output, err := BidderToPrebidSChains(&input)
+
+	assert.NotNil(t, err)
+	assert.Nil(t, output)
+}
+
+func TestBidderToPrebidChainsNilSChains(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: nil,
+		},
+	}
+
+	output, err := BidderToPrebidSChains(&input)
+
+	assert.Nil(t, err)
+	assert.Equal(t, len(output), 0)
+}
+
+func TestBidderToPrebidChainsZeroLengthSChains(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: []*openrtb_ext.ExtRequestPrebidSChain{},
+		},
+	}
+
+	output, err := BidderToPrebidSChains(&input)
 
+	assert.Nil(t, err)
+	assert.Equal(t, len(output), 0)
 }
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index f05f25e8..05b2fb6d 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -276,7 +276,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) {
 		},
 	}
 
-	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8
+	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8
 	// PI needs all purposes to succeed
 	testDefs := []tcf2TestDef{
 		{
diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go
index 6d9eaf9f..f6769916 100644
--- a/pbsmetrics/go_metrics_test.go
+++ b/pbsmetrics/go_metrics_test.go
@@ -2,6 +2,7 @@ package pbsmetrics
 
 import (
 	"testing"
+	"time"
 
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -56,10 +57,14 @@ func TestNewMetrics(t *testing.T) {
 
 	ensureContains(t, registry, "timeout_notification.ok", m.TimeoutNotificationSuccess)
 	ensureContains(t, registry, "timeout_notification.failed", m.TimeoutNotificationFailure)
-	ensureContains(t, registry, "privacy.request.tcf.v1", m.TCFReqVersion[TCFVersionV1])
-	ensureContains(t, registry, "privacy.request.tcf.v2", m.TCFReqVersion[TCFVersionV2])
-	ensureContains(t, registry, "privacy.request.tcf.err", m.TCFReqVersion[TCFVersionErr])
 
+	ensureContains(t, registry, "privacy.request.ccpa.specified", m.PrivacyCCPARequest)
+	ensureContains(t, registry, "privacy.request.ccpa.opt-out", m.PrivacyCCPARequestOptOut)
+	ensureContains(t, registry, "privacy.request.coppa", m.PrivacyCOPPARequest)
+	ensureContains(t, registry, "privacy.request.lmt", m.PrivacyLMTRequest)
+	ensureContains(t, registry, "privacy.request.tcf.v1", m.PrivacyTCFRequestVersion[TCFVersionV1])
+	ensureContains(t, registry, "privacy.request.tcf.v2", m.PrivacyTCFRequestVersion[TCFVersionV2])
+	ensureContains(t, registry, "privacy.request.tcf.err", m.PrivacyTCFRequestVersion[TCFVersionErr])
 }
 
 func TestRecordBidType(t *testing.T) {
@@ -111,6 +116,10 @@ func ensureContainsAdapterMetrics(t *testing.T, registry metrics.Registry, name
 	ensureContains(t, registry, name+".request_time", adapterMetrics.RequestTimer)
 	ensureContains(t, registry, name+".prices", adapterMetrics.PriceHistogram)
 	ensureContainsBidTypeMetrics(t, registry, name, adapterMetrics.MarkupMetrics)
+
+	ensureContains(t, registry, name+".connections_created", adapterMetrics.ConnCreated)
+	ensureContains(t, registry, name+".connections_reused", adapterMetrics.ConnReused)
+	ensureContains(t, registry, name+".connection_wait_time", adapterMetrics.ConnWaitTime)
 }
 
 func TestRecordBidTypeDisabledConfig(t *testing.T) {
@@ -175,6 +184,140 @@ func TestRecordBidTypeDisabledConfig(t *testing.T) {
 	}
 }
 
+func TestRecordDNSTime(t *testing.T) {
+	testCases := []struct {
+		description         string
+		inDnsLookupDuration time.Duration
+		outExpDuration      time.Duration
+	}{
+		{
+			description:         "Five second DNS lookup time",
+			inDnsLookupDuration: time.Second * 5,
+			outExpDuration:      time.Second * 5,
+		},
+		{
+			description:         "Zero DNS lookup time",
+			inDnsLookupDuration: time.Duration(0),
+			outExpDuration:      time.Duration(0),
+		},
+	}
+	for _, test := range testCases {
+		registry := metrics.NewRegistry()
+		m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true})
+
+		m.RecordDNSTime(test.inDnsLookupDuration)
+
+		assert.Equal(t, test.outExpDuration.Nanoseconds(), m.DNSLookupTimer.Sum(), test.description)
+	}
+}
+
+func TestRecordAdapterConnections(t *testing.T) {
+	var fakeBidder openrtb_ext.BidderName = "fooAdvertising"
+
+	type testIn struct {
+		adapterName         openrtb_ext.BidderName
+		connWasReused       bool
+		connWait            time.Duration
+		connMetricsDisabled bool
+	}
+
+	type testOut struct {
+		expectedConnReusedCount  int64
+		expectedConnCreatedCount int64
+		expectedConnWaitTime     time.Duration
+	}
+
+	testCases := []struct {
+		description string
+		in          testIn
+		out         testOut
+	}{
+		{
+			description: "Successful, new connection created, has connection wait",
+			in: testIn{
+				adapterName:         openrtb_ext.BidderAppnexus,
+				connWasReused:       false,
+				connWait:            time.Second * 5,
+				connMetricsDisabled: false,
+			},
+			out: testOut{
+				expectedConnReusedCount:  0,
+				expectedConnCreatedCount: 1,
+				expectedConnWaitTime:     time.Second * 5,
+			},
+		},
+		{
+			description: "Successful, new connection created, has connection wait",
+			in: testIn{
+				adapterName:         openrtb_ext.BidderAppnexus,
+				connWasReused:       false,
+				connWait:            time.Second * 4,
+				connMetricsDisabled: false,
+			},
+			out: testOut{
+				expectedConnCreatedCount: 1,
+				expectedConnWaitTime:     time.Second * 4,
+			},
+		},
+		{
+			description: "Successful, was reused, no connection wait",
+			in: testIn{
+				adapterName:         openrtb_ext.BidderAppnexus,
+				connWasReused:       true,
+				connMetricsDisabled: false,
+			},
+			out: testOut{
+				expectedConnReusedCount: 1,
+				expectedConnWaitTime:    0,
+			},
+		},
+		{
+			description: "Successful, was reused, has connection wait",
+			in: testIn{
+				adapterName:         openrtb_ext.BidderAppnexus,
+				connWasReused:       true,
+				connWait:            time.Second * 5,
+				connMetricsDisabled: false,
+			},
+			out: testOut{
+				expectedConnReusedCount: 1,
+				expectedConnWaitTime:    time.Second * 5,
+			},
+		},
+		{
+			description: "Fake bidder, nothing gets updated",
+			in: testIn{
+				adapterName:         fakeBidder,
+				connWasReused:       false,
+				connWait:            0,
+				connMetricsDisabled: false,
+			},
+			out: testOut{},
+		},
+		{
+			description: "Adapter connection metrics are disabled, nothing gets updated",
+			in: testIn{
+				adapterName:         openrtb_ext.BidderAppnexus,
+				connWasReused:       false,
+				connWait:            time.Second * 5,
+				connMetricsDisabled: true,
+			},
+			out: testOut{},
+		},
+	}
+
+	for i, test := range testCases {
+		registry := metrics.NewRegistry()
+		m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterConnectionMetrics: test.in.connMetricsDisabled})
+
+		m.RecordAdapterConnections(test.in.adapterName, test.in.connWasReused, test.in.connWait)
+
+		assert.Equal(t, test.out.expectedConnReusedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnReused.Count(), "Test [%d] incorrect number of reused connections to adapter", i)
+		assert.Equal(t, test.out.expectedConnCreatedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnCreated.Count(), "Test [%d] incorrect number of new connections to adapter created", i)
+		assert.Equal(t, test.out.expectedConnWaitTime.Nanoseconds(), m.AdapterMetrics[openrtb_ext.BidderAppnexus].ConnWaitTime.Sum(), "Test [%d] incorrect wait time in connection to adapter", i)
+	}
+}
+
 func TestNewMetricsWithDisabledConfig(t *testing.T) {
 	registry := metrics.NewRegistry()
 	m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true})
@@ -202,6 +345,61 @@ func TestRecordPrebidCacheRequestTimeWithNotSuccess(t *testing.T) {
 	assert.Equal(t, m.PrebidCacheRequestTimerError.Count(), int64(1))
 }
 
+func TestRecordRequestPrivacy(t *testing.T) {
+	registry := metrics.NewRegistry()
+	m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true})
+
+	// CCPA
+	m.RecordRequestPrivacy(PrivacyLabels{
+		CCPAEnforced: true,
+		CCPAProvided: true,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		CCPAEnforced: true,
+		CCPAProvided: false,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		CCPAEnforced: false,
+		CCPAProvided: true,
+	})
+
+	// COPPA
+	m.RecordRequestPrivacy(PrivacyLabels{
+		COPPAEnforced: true,
+	})
+
+	// LMT
+	m.RecordRequestPrivacy(PrivacyLabels{
+		LMTEnforced: true,
+	})
+
+	// GDPR
+	m.RecordRequestPrivacy(PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: TCFVersionErr,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: TCFVersionV1,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: TCFVersionV2,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: TCFVersionV1,
+	})
+
+	assert.Equal(t, m.PrivacyCCPARequest.Count(), int64(2), "CCPA")
+	assert.Equal(t, m.PrivacyCCPARequestOptOut.Count(), int64(1), "CCPA Opt Out")
+	assert.Equal(t, m.PrivacyCOPPARequest.Count(), int64(1), "COPPA")
+	assert.Equal(t, m.PrivacyLMTRequest.Count(), int64(1), "LMT")
+	assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionErr].Count(), int64(1), "TCF Err")
+	assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV1].Count(), int64(2), "TCF V1")
+	assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2")
+}
+
 func ensureContainsBidTypeMetrics(t *testing.T, registry metrics.Registry, prefix string, mdm map[openrtb_ext.BidType]*MarkupDeliveryMetrics) {
 	ensureContains(t, registry, prefix+".banner.adm_bids_received", mdm[openrtb_ext.BidTypeBanner].AdmMeter)
 	ensureContains(t, registry, prefix+".banner.nurl_bids_received", mdm[openrtb_ext.BidTypeBanner].NurlMeter)
diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go
index 03daff0d..b6153b16 100644
--- a/pbsmetrics/prometheus/prometheus_test.go
+++ b/pbsmetrics/prometheus/prometheus_test.go
@@ -1,6 +1,7 @@
 package prometheusmetrics
 
 import (
+	"fmt"
 	"testing"
 	"time"
 
@@ -17,7 +18,7 @@ func createMetricsForTesting() *Metrics {
 		Port:      8080,
 		Namespace: "prebid",
 		Subsystem: "server",
-	})
+	}, config.DisabledMetrics{})
 }
 
 func TestMetricCountGatekeeping(t *testing.T) {
@@ -61,7 +62,7 @@ func TestMetricCountGatekeeping(t *testing.T) {
 	// Verify Per-Adapter Cardinality
 	// - This assertion provides a warning for newly added adapter metrics. Threre are 40+ adapters which makes the
 	//   cost of new per-adapter metrics rather expensive. Thought should be given when adding new per-adapter metrics.
-	assert.True(t, perAdapterCardinalityCount <= 22, "Per-Adapter Cardinality")
+	assert.True(t, perAdapterCardinalityCount <= 27, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount)
 }
 
 func TestConnectionMetrics(t *testing.T) {
@@ -944,33 +945,265 @@ func TestTimeoutNotifications(t *testing.T) {
 
 }
 
-func TestTCFMetrics(t *testing.T) {
+func TestRecordDNSTime(t *testing.T) {
+	type testIn struct {
+		dnsLookupDuration time.Duration
+	}
+	type testOut struct {
+		expDuration float64
+		expCount    uint64
+	}
+	testCases := []struct {
+		description string
+		in          testIn
+		out         testOut
+	}{
+		{
+			description: "Five second DNS lookup time",
+			in: testIn{
+				dnsLookupDuration: time.Second * 5,
+			},
+			out: testOut{
+				expDuration: 5,
+				expCount:    1,
+			},
+		},
+		{
+			description: "Zero DNS lookup time",
+			in:          testIn{},
+			out: testOut{
+				expDuration: 0,
+				expCount:    1,
+			},
+		},
+	}
+	for i, test := range testCases {
+		pm := createMetricsForTesting()
+		pm.RecordDNSTime(test.in.dnsLookupDuration)
+
+		m := dto.Metric{}
+		pm.dnsLookupTimer.Write(&m)
+		histogram := *m.GetHistogram()
+
+		assert.Equal(t, test.out.expCount, histogram.GetSampleCount(), "[%d] Incorrect number of histogram entries. Desc: %s\n", i, test.description)
+		assert.Equal(t, test.out.expDuration, histogram.GetSampleSum(), "[%d] Incorrect number of histogram cumulative values. Desc: %s\n", i, test.description)
+	}
+}
+
+func TestRecordAdapterConnections(t *testing.T) {
+
+	type testIn struct {
+		adapterName   openrtb_ext.BidderName
+		connWasReused bool
+		connWait      time.Duration
+	}
+
+	type testOut struct {
+		expectedConnReusedCount  int64
+		expectedConnCreatedCount int64
+		expectedConnWaitCount    uint64
+		expectedConnWaitTime     float64
+	}
+
+	testCases := []struct {
+		description string
+		in          testIn
+		out         testOut
+	}{
+		{
+			description: "[1] Successful, new connection created, was idle, has connection wait",
+			in: testIn{
+				adapterName:   openrtb_ext.BidderAppnexus,
+				connWasReused: false,
+				connWait:      time.Second * 5,
+			},
+			out: testOut{
+				expectedConnReusedCount:  0,
+				expectedConnCreatedCount: 1,
+				expectedConnWaitCount:    1,
+				expectedConnWaitTime:     5,
+			},
+		},
+		{
+			description: "[2] Successful, new connection created, not idle, has connection wait",
+			in: testIn{
+				adapterName:   openrtb_ext.BidderAppnexus,
+				connWasReused: false,
+				connWait:      time.Second * 4,
+			},
+			out: testOut{
+				expectedConnReusedCount:  0,
+				expectedConnCreatedCount: 1,
+				expectedConnWaitCount:    1,
+				expectedConnWaitTime:     4,
+			},
+		},
+		{
+			description: "[3] Successful, was reused, was idle, no connection wait",
+			in: testIn{
+				adapterName:   openrtb_ext.BidderAppnexus,
+				connWasReused: true,
+			},
+			out: testOut{
+				expectedConnReusedCount:  1,
+				expectedConnCreatedCount: 0,
+				expectedConnWaitCount:    1,
+				expectedConnWaitTime:     0,
+			},
+		},
+		{
+			description: "[4] Successful, was reused, not idle, has connection wait",
+			in: testIn{
+				adapterName:   openrtb_ext.BidderAppnexus,
+				connWasReused: true,
+				connWait:      time.Second * 5,
+			},
+			out: testOut{
+				expectedConnReusedCount:  1,
+				expectedConnCreatedCount: 0,
+				expectedConnWaitCount:    1,
+				expectedConnWaitTime:     5,
+			},
+		},
+	}
+
+	for i, test := range testCases {
+		m := createMetricsForTesting()
+		assertDesciptions := []string{
+			fmt.Sprintf("[%d] Metric: adapterReusedConnections; Desc: %s", i+1, test.description),
+			fmt.Sprintf("[%d] Metric: adapterCreatedConnections; Desc: %s", i+1, test.description),
+			fmt.Sprintf("[%d] Metric: adapterWaitConnectionCount; Desc: %s", i+1, test.description),
+			fmt.Sprintf("[%d] Metric: adapterWaitConnectionTime; Desc: %s", i+1, test.description),
+		}
+
+		m.RecordAdapterConnections(test.in.adapterName, test.in.connWasReused, test.in.connWait)
+
+		// Assert number of reused connections
+		assertCounterVecValue(t,
+			assertDesciptions[0],
+			"adapter_connection_reused",
+			m.adapterReusedConnections,
+			float64(test.out.expectedConnReusedCount),
+			prometheus.Labels{adapterLabel: string(test.in.adapterName)})
+
+		// Assert number of new created connections
+		assertCounterVecValue(t,
+			assertDesciptions[1],
+			"adapter_connection_created",
+			m.adapterCreatedConnections,
+			float64(test.out.expectedConnCreatedCount),
+			prometheus.Labels{adapterLabel: string(test.in.adapterName)})
+
+		// Assert connection wait time
+		histogram := getHistogramFromHistogramVec(m.adapterConnectionWaitTime, adapterLabel, string(test.in.adapterName))
+		assert.Equal(t, test.out.expectedConnWaitCount, histogram.GetSampleCount(), assertDesciptions[2])
+		assert.Equal(t, test.out.expectedConnWaitTime, histogram.GetSampleSum(), assertDesciptions[3])
+	}
+}
+
+func TestDisableAdapterConnections(t *testing.T) {
+	prometheusMetrics := NewMetrics(config.PrometheusMetrics{
+		Port:      8080,
+		Namespace: "prebid",
+		Subsystem: "server",
+	}, config.DisabledMetrics{AdapterConnectionMetrics: true})
+
+	// Assert counter vector was not initialized
+	assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil")
+	assert.Nil(t, prometheusMetrics.adapterCreatedConnections, "Counter Vector adapterCreatedConnections should be nil")
+	assert.Nil(t, prometheusMetrics.adapterConnectionWaitTime, "Counter Vector adapterConnectionWaitTime should be nil")
+}
+
+func TestRecordRequestPrivacy(t *testing.T) {
 	m := createMetricsForTesting()
 
-	m.RecordTCFReq(pbsmetrics.TCFVersionToValue(0))
-	m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1))
-	m.RecordTCFReq(pbsmetrics.TCFVersionToValue(2))
-	m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1))
+	// CCPA
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		CCPAEnforced: true,
+		CCPAProvided: true,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		CCPAEnforced: true,
+		CCPAProvided: false,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		CCPAEnforced: false,
+		CCPAProvided: true,
+	})
+
+	// COPPA
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		COPPAEnforced: true,
+	})
+
+	// LMT
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		LMTEnforced: true,
+	})
+
+	// GDPR
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: pbsmetrics.TCFVersionErr,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: pbsmetrics.TCFVersionV1,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: pbsmetrics.TCFVersionV2,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: pbsmetrics.TCFVersionV1,
+	})
+
+	assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA,
+		float64(1),
+		prometheus.Labels{
+			sourceLabel: sourceRequest,
+			optOutLabel: "true",
+		})
 
-	assertCounterVecValue(t, "", "privacy_tcf:err", m.tcfVersion,
+	assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA,
+		float64(1),
+		prometheus.Labels{
+			sourceLabel: sourceRequest,
+			optOutLabel: "false",
+		})
+
+	assertCounterVecValue(t, "", "privacy_coppa", m.privacyCOPPA,
+		float64(1),
+		prometheus.Labels{
+			sourceLabel: sourceRequest,
+		})
+
+	assertCounterVecValue(t, "", "privacy_lmt", m.privacyLMT,
+		float64(1),
+		prometheus.Labels{
+			sourceLabel: sourceRequest,
+		})
+
+	assertCounterVecValue(t, "", "privacy_tcf:err", m.privacyTCF,
 		float64(1),
 		prometheus.Labels{
-			versionLabel: "err",
 			sourceLabel:  sourceRequest,
+			versionLabel: "err",
 		})
 
-	assertCounterVecValue(t, "", "privacy_tcf:v1", m.tcfVersion,
+	assertCounterVecValue(t, "", "privacy_tcf:v1", m.privacyTCF,
 		float64(2),
 		prometheus.Labels{
-			versionLabel: "v1",
 			sourceLabel:  sourceRequest,
+			versionLabel: "v1",
 		})
 
-	assertCounterVecValue(t, "", "privacy_tcf:v2", m.tcfVersion,
+	assertCounterVecValue(t, "", "privacy_tcf:v2", m.privacyTCF,
 		float64(1),
 		prometheus.Labels{
-			versionLabel: "v2",
 			sourceLabel:  sourceRequest,
+			versionLabel: "v2",
 		})
 }
 
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index b23541ea..32ab2e73 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -48,6 +48,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderIx):               syncConfig,
 			string(openrtb_ext.BidderLifestreet):       syncConfig,
 			string(openrtb_ext.BidderLockerDome):       syncConfig,
+			string(openrtb_ext.BidderLogicad):          syncConfig,
 			string(openrtb_ext.BidderLunaMedia):        syncConfig,
 			string(openrtb_ext.BidderMarsmedia):        syncConfig,
 			string(openrtb_ext.BidderMgid):             syncConfig,