Repository: prebid/prebid-server
Pull request #2928 (2023-07-17 12:21:58)

New adapter: Vox
Good day,

This is a new adapter called Vox. Main idea is to just pass OpenRTB request as is to our backend and let our backend respond with valid OpenRTB response.

gargcreation1992: @Gradi Please do not worry about the semgrep checks failing on PRs. We are checking on our end on the cause and working on the fix. Thank you for your understanding.
onkarhanumante30: > @Gradi Please do not worry about the semgrep checks failing on PRs. We are checking on our end on the cause and working on the fix. Thank you for your understanding.

The issues with semgrep checks have been resolved, and they should now function correctly when new commits are pushed to the pull request.
onkarvhanumante: > @Gradi Please do not worry about the semgrep checks failing on PRs. We are checking on our end on the cause and working on the fix. Thank you for your understanding.

The issues with semgrep checks have been resolved, and they should now function correctly when new commits are pushed to the pull request.
onkarvhanumante: @Gradi  looks Gradi:voxintegration branch is not in sync with new changes on prebid master branch. Could you sync your branch and fork repo with updated master
Gradi: @onkarvhanumante Done. Merged branch 'prebid:master' into voxintegration 

Patch:
diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go
index 21830deb..3f91d740 100644
--- a/adapters/adnuntius/adnuntius.go
+++ b/adapters/adnuntius/adnuntius.go
@@ -27,31 +27,35 @@ type adnAdunit struct {
 	AuId       string    `json:"auId"`
 	TargetId   string    `json:"targetId"`
 	Dimensions [][]int64 `json:"dimensions,omitempty"`
+	MaxDeals   int       `json:"maxDeals,omitempty"`
 }
 
 type extDeviceAdnuntius struct {
 	NoCookies bool `json:"noCookies,omitempty"`
 }
 
+type Ad struct {
+	Bid struct {
+		Amount   float64
+		Currency string
+	}
+	DealID          string `json:"dealId,omitempty"`
+	AdId            string
+	CreativeWidth   string
+	CreativeHeight  string
+	CreativeId      string
+	LineItemId      string
+	Html            string
+	DestinationUrls map[string]string
+}
+
 type AdUnit struct {
 	AuId       string
 	TargetId   string
 	Html       string
 	ResponseId string
-	Ads        []struct {
-		Bid struct {
-			Amount   float64
-			Currency string
-		}
-		DealID          string `json:"dealId,omitempty"`
-		AdId            string
-		CreativeWidth   string
-		CreativeHeight  string
-		CreativeId      string
-		LineItemId      string
-		Html            string
-		DestinationUrls map[string]string
-	}
+	Ads        []Ad
+	Deals      []Ad `json:"deals,omitempty"`
 }
 
 type AdnResponse struct {
@@ -213,13 +217,17 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters
 			network = adnuntiusExt.Network
 		}
 
+		adUnit := adnAdunit{
+			AuId:       adnuntiusExt.Auid,
+			TargetId:   fmt.Sprintf("%s-%s", adnuntiusExt.Auid, imp.ID),
+			Dimensions: getImpSizes(imp),
+		}
+		if adnuntiusExt.MaxDeals > 0 {
+			adUnit.MaxDeals = adnuntiusExt.MaxDeals
+		}
 		networkAdunitMap[network] = append(
 			networkAdunitMap[network],
-			adnAdunit{
-				AuId:       adnuntiusExt.Auid,
-				TargetId:   fmt.Sprintf("%s-%s", adnuntiusExt.Auid, imp.ID),
-				Dimensions: getImpSizes(imp),
-			})
+			adUnit)
 	}
 
 	endpoint, err := makeEndpointUrl(ortbRequest, a, noCookies)
@@ -320,6 +328,46 @@ func getGDPR(request *openrtb2.BidRequest) (string, string, error) {
 	return gdpr, consent, nil
 }
 
+func generateAdResponse(ad Ad, impId string, html string, request *openrtb2.BidRequest) (*openrtb2.Bid, []error) {
+
+	creativeWidth, widthErr := strconv.ParseInt(ad.CreativeWidth, 10, 64)
+	if widthErr != nil {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Value of width: %s is not a string", ad.CreativeWidth),
+		}}
+	}
+
+	creativeHeight, heightErr := strconv.ParseInt(ad.CreativeHeight, 10, 64)
+	if heightErr != nil {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Value of height: %s is not a string", ad.CreativeHeight),
+		}}
+	}
+
+	adDomain := []string{}
+	for _, url := range ad.DestinationUrls {
+		domainArray := strings.Split(url, "/")
+		domain := strings.Replace(domainArray[2], "www.", "", -1)
+		adDomain = append(adDomain, domain)
+	}
+
+	bid := openrtb2.Bid{
+		ID:      ad.AdId,
+		ImpID:   impId,
+		W:       creativeWidth,
+		H:       creativeHeight,
+		AdID:    ad.AdId,
+		DealID:  ad.DealID,
+		CID:     ad.LineItemId,
+		CrID:    ad.CreativeId,
+		Price:   ad.Bid.Amount * 1000,
+		AdM:     html,
+		ADomain: adDomain,
+	}
+	return &bid, nil
+
+}
+
 func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) (*adapters.BidderResponse, []error) {
 	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(adnResponse.AdUnits))
 	var currency string
@@ -344,48 +392,34 @@ func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest)
 		if len(adunit.Ads) > 0 {
 
 			ad := adunit.Ads[0]
-
 			currency = ad.Bid.Currency
 
-			creativeWidth, widthErr := strconv.ParseInt(ad.CreativeWidth, 10, 64)
-			if widthErr != nil {
+			adBid, err := generateAdResponse(ad, request.Imp[i].ID, adunit.Html, request)
+			if err != nil {
 				return nil, []error{&errortypes.BadInput{
-					Message: fmt.Sprintf("Value of width: %s is not a string", ad.CreativeWidth),
+					Message: fmt.Sprintf("Error at ad generation"),
 				}}
 			}
 
-			creativeHeight, heightErr := strconv.ParseInt(ad.CreativeHeight, 10, 64)
-			if heightErr != nil {
-				return nil, []error{&errortypes.BadInput{
-					Message: fmt.Sprintf("Value of height: %s is not a string", ad.CreativeHeight),
-				}}
-			}
-
-			adDomain := []string{}
-			for _, url := range ad.DestinationUrls {
-				domainArray := strings.Split(url, "/")
-				domain := strings.Replace(domainArray[2], "www.", "", -1)
-				adDomain = append(adDomain, domain)
-			}
-
-			bid := openrtb2.Bid{
-				ID:      ad.AdId,
-				ImpID:   request.Imp[i].ID,
-				W:       creativeWidth,
-				H:       creativeHeight,
-				AdID:    ad.AdId,
-				DealID:  ad.DealID,
-				CID:     ad.LineItemId,
-				CrID:    ad.CreativeId,
-				Price:   ad.Bid.Amount * 1000,
-				AdM:     adunit.Html,
-				ADomain: adDomain,
-			}
-
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
-				Bid:     &bid,
+				Bid:     adBid,
 				BidType: "banner",
 			})
+
+			for _, deal := range adunit.Deals {
+				dealBid, err := generateAdResponse(deal, request.Imp[i].ID, deal.Html, request)
+				if err != nil {
+					return nil, []error{&errortypes.BadInput{
+						Message: fmt.Sprintf("Error at ad generation"),
+					}}
+				}
+
+				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+					Bid:     dealBid,
+					BidType: "banner",
+				})
+			}
+
 		}
 
 	}
diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go
index f46fc591..c13dbe8a 100644
--- a/adapters/mobilefuse/mobilefuse.go
+++ b/adapters/mobilefuse/mobilefuse.go
@@ -27,6 +27,10 @@ type BidExt struct {
 	Mf ExtMf `json:"mf"`
 }
 
+type ExtSkadn struct {
+	Skadn json.RawMessage `json:"skadn"`
+}
+
 // Builder builds a new instance of the MobileFuse adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
@@ -79,7 +83,7 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb2.BidRequest,
 
 	for _, seatbid := range incomingBidResponse.SeatBid {
 		for i := range seatbid.Bid {
-			bidType := adapter.getBidType(seatbid.Bid[i])
+			bidType := getBidType(seatbid.Bid[i])
 			seatbid.Bid[i].Ext = nil
 
 			outgoingBidResponse.Bids = append(outgoingBidResponse.Bids, &adapters.TypedBid{
@@ -95,22 +99,18 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb2.BidRequest,
 func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (*adapters.RequestData, []error) {
 	var errs []error
 
-	mobileFuseExtension, errs := adapter.getFirstMobileFuseExtension(bidRequest)
-
+	mobileFuseExtension, errs := getFirstMobileFuseExtension(bidRequest)
 	if errs != nil {
 		return nil, errs
 	}
 
 	endpoint, err := adapter.getEndpoint(mobileFuseExtension)
-
 	if err != nil {
 		return nil, append(errs, err)
 	}
 
-	validImps := adapter.getValidImps(bidRequest, mobileFuseExtension)
-
-	if len(validImps) == 0 {
-		err := fmt.Errorf("No valid imps")
+	validImps, err := getValidImps(bidRequest, mobileFuseExtension)
+	if err != nil {
 		errs = append(errs, err)
 		return nil, errs
 	}
@@ -118,7 +118,6 @@ func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (
 	mobileFuseBidRequest := *bidRequest
 	mobileFuseBidRequest.Imp = validImps
 	body, err := json.Marshal(mobileFuseBidRequest)
-
 	if err != nil {
 		return nil, append(errs, err)
 	}
@@ -135,7 +134,7 @@ func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (
 	}, errs
 }
 
-func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) {
+func getFirstMobileFuseExtension(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) {
 	var mobileFuseImpExtension openrtb_ext.ExtImpMobileFuse
 	var errs []error
 
@@ -162,13 +161,23 @@ func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb2.
 	return &mobileFuseImpExtension, errs
 }
 
+func getMobileFuseExtensionForImp(imp *openrtb2.Imp, mobileFuseImpExtension *openrtb_ext.ExtImpMobileFuse) error {
+	var bidder_imp_extension adapters.ExtImpBidder
+
+	err := json.Unmarshal(imp.Ext, &bidder_imp_extension)
+	if err != nil {
+		return err
+	}
+
+	return json.Unmarshal(bidder_imp_extension.Bidder, &mobileFuseImpExtension)
+}
+
 func (adapter *MobileFuseAdapter) getEndpoint(ext *openrtb_ext.ExtImpMobileFuse) (string, error) {
 	publisher_id := strconv.Itoa(ext.PublisherId)
 
-	url, errs := macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{PublisherID: publisher_id})
-
-	if errs != nil {
-		return "", errs
+	url, err := macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{PublisherID: publisher_id})
+	if err != nil {
+		return "", err
 	}
 
 	if ext.TagidSrc == "ext" {
@@ -178,23 +187,45 @@ func (adapter *MobileFuseAdapter) getEndpoint(ext *openrtb_ext.ExtImpMobileFuse)
 	return url, nil
 }
 
-func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb2.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) []openrtb2.Imp {
+func getValidImps(bidRequest *openrtb2.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) ([]openrtb2.Imp, error) {
 	var validImps []openrtb2.Imp
 
 	for _, imp := range bidRequest.Imp {
 		if imp.Banner != nil || imp.Video != nil || imp.Native != nil {
+			err := getMobileFuseExtensionForImp(&imp, ext)
+			if err != nil {
+				return nil, err
+			}
+
 			imp.TagID = strconv.Itoa(ext.PlacementId)
-			imp.Ext = nil
-			validImps = append(validImps, imp)
 
-			break
+			var extSkadn ExtSkadn
+			err = json.Unmarshal(imp.Ext, &extSkadn)
+			if err != nil {
+				return nil, err
+			}
+
+			if extSkadn.Skadn != nil {
+				imp.Ext, err = json.Marshal(map[string]json.RawMessage{"skadn": extSkadn.Skadn})
+				if err != nil {
+					return nil, err
+				}
+			} else {
+				imp.Ext = nil
+			}
+
+			validImps = append(validImps, imp)
 		}
 	}
 
-	return validImps
+	if len(validImps) == 0 {
+		return nil, fmt.Errorf("No valid imps")
+	}
+
+	return validImps, nil
 }
 
-func (adapter *MobileFuseAdapter) getBidType(bid openrtb2.Bid) openrtb_ext.BidType {
+func getBidType(bid openrtb2.Bid) openrtb_ext.BidType {
 	if bid.Ext != nil {
 		var bidExt BidExt
 		err := json.Unmarshal(bid.Ext, &bidExt)
diff --git a/adapters/orbidder/orbidder.go b/adapters/orbidder/orbidder.go
index 43c3574b..b4f1a16c 100644
--- a/adapters/orbidder/orbidder.go
+++ b/adapters/orbidder/orbidder.go
@@ -125,19 +125,51 @@ func (rcv OrbidderAdapter) MakeBids(_ *openrtb2.BidRequest, _ *adapters.RequestD
 		return nil, []error{err}
 	}
 
+	var bidErrs []error
 	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
 	for _, seatBid := range bidResp.SeatBid {
-		for _, bid := range seatBid.Bid {
+		for i := range seatBid.Bid {
+			// later we have to add the bid as a pointer,
+			// because of this we need a variable that only exists at this loop iteration.
+			// otherwise there will be issues with multibid and pointer behavior.
+			bid := seatBid.Bid[i]
+			bidType, err := getBidType(bid)
+			if err != nil {
+				// could not determinate media type, append an error and continue with the next bid.
+				bidErrs = append(bidErrs, err)
+				continue
+			}
+
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
 				Bid:     &bid,
-				BidType: openrtb_ext.BidTypeBanner,
+				BidType: bidType,
 			})
 		}
 	}
 	if bidResp.Cur != "" {
 		bidResponse.Currency = bidResp.Cur
 	}
-	return bidResponse, nil
+
+	return bidResponse, bidErrs
+}
+
+func getBidType(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+
+	// determinate media type by bid response field mtype
+	switch bid.MType {
+	case openrtb2.MarkupBanner:
+		return openrtb_ext.BidTypeBanner, nil
+	case openrtb2.MarkupVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	case openrtb2.MarkupAudio:
+		return openrtb_ext.BidTypeAudio, nil
+	case openrtb2.MarkupNative:
+		return openrtb_ext.BidTypeNative, nil
+	}
+
+	return "", &errortypes.BadInput{
+		Message: fmt.Sprintf("Could not define media type for impression: %s", bid.ImpID),
+	}
 }
 
 // Builder builds a new instance of the Orbidder adapter for the given bidder with the given config.
diff --git a/adapters/rtbhouse/rtbhouse.go b/adapters/rtbhouse/rtbhouse.go
index c8afb0c4..dc67df9a 100644
--- a/adapters/rtbhouse/rtbhouse.go
+++ b/adapters/rtbhouse/rtbhouse.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
+	"strings"
 
 	"github.com/prebid/openrtb/v19/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
@@ -12,6 +13,15 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
+const (
+	BidderCurrency string = "USD"
+)
+
+// RTBHouseAdapter implements the Bidder interface.
+type RTBHouseAdapter struct {
+	endpoint string
+}
+
 // Builder builds a new instance of the RTBHouse adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	bidder := &RTBHouseAdapter{
@@ -20,11 +30,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co
 	return bidder, nil
 }
 
-// RTBHouseAdapter implements the Bidder interface.
-type RTBHouseAdapter struct {
-	endpoint string
-}
-
 // MakeRequests prepares the HTTP requests which should be made to fetch bids.
 func (adapter *RTBHouseAdapter) MakeRequests(
 	openRTBRequest *openrtb2.BidRequest,
@@ -33,7 +38,48 @@ func (adapter *RTBHouseAdapter) MakeRequests(
 	requestsToBidder []*adapters.RequestData,
 	errs []error,
 ) {
-	openRTBRequestJSON, err := json.Marshal(openRTBRequest)
+
+	reqCopy := *openRTBRequest
+	reqCopy.Imp = []openrtb2.Imp{}
+	for _, imp := range openRTBRequest.Imp {
+		var bidFloorCur = imp.BidFloorCur
+		var bidFloor = imp.BidFloor
+		if bidFloorCur == "" && bidFloor == 0 {
+			rtbhouseExt, err := getImpressionExt(imp)
+			if err != nil {
+				return nil, []error{err}
+			}
+			if rtbhouseExt.BidFloor > 0 && len(reqCopy.Cur) > 0 {
+				bidFloorCur = reqCopy.Cur[0]
+				bidFloor = rtbhouseExt.BidFloor
+			}
+		}
+
+		// Check if imp comes with bid floor amount defined in a foreign currency
+		if bidFloor > 0 && bidFloorCur != "" && strings.ToUpper(bidFloorCur) != BidderCurrency {
+			// Convert to US dollars
+			convertedValue, err := reqInfo.ConvertCurrency(bidFloor, bidFloorCur, BidderCurrency)
+			if err != nil {
+				return nil, []error{err}
+			}
+
+			bidFloorCur = BidderCurrency
+			bidFloor = convertedValue
+		}
+
+		if bidFloor > 0 && bidFloorCur == BidderCurrency {
+			// Update after conversion. All imp elements inside request.Imp are shallow copies
+			// therefore, their non-pointer values are not shared memory and are safe to modify.
+			imp.BidFloorCur = bidFloorCur
+			imp.BidFloor = bidFloor
+		}
+
+		// Set the CUR of bid to BIDDER_CURRENCY after converting all floors
+		reqCopy.Cur = []string{BidderCurrency}
+		reqCopy.Imp = append(reqCopy.Imp, imp)
+	}
+
+	openRTBRequestJSON, err := json.Marshal(reqCopy)
 	if err != nil {
 		errs = append(errs, err)
 		return nil, errs
@@ -52,6 +98,24 @@ func (adapter *RTBHouseAdapter) MakeRequests(
 	return requestsToBidder, errs
 }
 
+func getImpressionExt(imp openrtb2.Imp) (*openrtb_ext.ExtImpRTBHouse, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Bidder extension not provided or can't be unmarshalled",
+		}
+	}
+
+	var rtbhouseExt openrtb_ext.ExtImpRTBHouse
+	if err := json.Unmarshal(bidderExt.Bidder, &rtbhouseExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Error while unmarshaling bidder extension",
+		}
+	}
+
+	return &rtbhouseExt, nil
+}
+
 const unexpectedStatusCodeFormat = "" +
 	"Unexpected status code: %d. Run with request.debug = 1 for more info"
 
@@ -97,6 +161,8 @@ func (adapter *RTBHouseAdapter) MakeBids(
 		}
 	}
 
+	bidderResponse.Currency = BidderCurrency
+
 	return bidderResponse, nil
 
 }
diff --git a/adapters/vox/vox.go b/adapters/vox/vox.go
new file mode 100644
index 00000000..0b56fcbf
--- /dev/null
+++ b/adapters/vox/vox.go
@@ -0,0 +1,86 @@
+package vox
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/prebid/openrtb/v19/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	requestJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	requestData := &adapters.RequestData{
+		Method: "POST",
+		Uri:    a.endpoint,
+		Body:   requestJSON,
+	}
+
+	return []*adapters.RequestData{requestData}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if adapters.IsResponseStatusCodeNoContent(responseData) {
+		return nil, nil
+	}
+
+	if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
+		return nil, []error{err}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	bidResponse.Currency = response.Cur
+
+	for _, seatBid := range response.SeatBid {
+		for i, bid := range seatBid.Bid {
+			bidType, err := getMediaTypeForBid(bid)
+			if err != nil {
+				return nil, []error{err}
+			}
+
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: bidType,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+
+	return bidResponse, nil
+}
+
+func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+	switch bid.MType {
+	case openrtb2.MarkupBanner:
+		return openrtb_ext.BidTypeBanner, nil
+	case openrtb2.MarkupVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	case openrtb2.MarkupAudio:
+		return openrtb_ext.BidTypeAudio, nil
+	case openrtb2.MarkupNative:
+		return openrtb_ext.BidTypeNative, nil
+	default:
+		return "", fmt.Errorf("Unable to fetch mediaType in multi-format: %s", bid.ImpID)
+	}
+}
diff --git a/config/bidderinfo.go b/config/bidderinfo.go
index 6f62488c..b4d79001 100644
--- a/config/bidderinfo.go
+++ b/config/bidderinfo.go
@@ -23,6 +23,7 @@ type BidderInfos map[string]BidderInfo
 
 // BidderInfo specifies all configuration for a bidder except for enabled status, endpoint, and extra information.
 type BidderInfo struct {
+	AliasOf          string `yaml:"aliasOf" mapstructure:"aliasOf"`
 	Disabled         bool   `yaml:"disabled" mapstructure:"disabled"`
 	Endpoint         string `yaml:"endpoint" mapstructure:"endpoint"`
 	ExtraAdapterInfo string `yaml:"extra_info" mapstructure:"extra_info"`
@@ -266,20 +267,35 @@ func (infos BidderInfos) validate(errs []error) []error {
 		if bidder.IsEnabled() {
 			errs = validateAdapterEndpoint(bidder.Endpoint, bidderName, errs)
 
-			validateInfoErr := validateInfo(bidder, bidderName)
-			if validateInfoErr != nil {
-				errs = append(errs, validateInfoErr)
+			if err := validateInfo(bidder, bidderName); err != nil {
+				errs = append(errs, err)
 			}
 
-			validateSyncerErr := validateSyncer(bidder)
-			if validateSyncerErr != nil {
-				errs = append(errs, validateSyncerErr)
+			if err := validateSyncer(bidder); err != nil {
+				errs = append(errs, err)
+			}
+
+			if err := validateAliases(bidder, infos, bidderName); err != nil {
+				errs = append(errs, err)
 			}
 		}
 	}
 	return errs
 }
 
+func validateAliases(bidder BidderInfo, infos BidderInfos, bidderName string) error {
+	if len(bidder.AliasOf) > 0 {
+		if parentBidder, ok := infos[bidder.AliasOf]; ok {
+			if len(parentBidder.AliasOf) > 0 {
+				return fmt.Errorf("bidder: %s cannot be an alias of an alias: %s", bidder.AliasOf, bidderName)
+			}
+		} else {
+			return fmt.Errorf("bidder: %s not found for an alias: %s", bidder.AliasOf, bidderName)
+		}
+	}
+	return nil
+}
+
 var testEndpointTemplateParams = macros.EndpointTemplateParams{
 	Host:        "anyHost",
 	PublisherID: "anyPublisherID",
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 9324b15d..0d5c35f9 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -69,6 +69,7 @@ func NewAmpEndpoint(
 	bidderMap map[string]openrtb_ext.BidderName,
 	storedRespFetcher stored_requests.Fetcher,
 	hookExecutionPlanBuilder hooks.ExecutionPlanBuilder,
+	tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed,
 ) (httprouter.Handle, error) {
 
 	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil {
@@ -100,7 +101,9 @@ func NewAmpEndpoint(
 		nil,
 		ipValidator,
 		storedRespFetcher,
-		hookExecutionPlanBuilder}).AmpAuction), nil
+		hookExecutionPlanBuilder,
+		tmaxAdjustments,
+	}).AmpAuction), nil
 
 }
 
@@ -251,9 +254,15 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		QueryParams:                r.URL.Query(),
 		TCF2Config:                 tcf2Config,
 		Activities:                 activities,
+		TmaxAdjustments:            deps.tmaxAdjustments,
 	}
 
 	auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
+	defer func() {
+		if !auctionRequest.BidderResponseStartTime.IsZero() {
+			deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime))
+		}
+	}()
 	var response *openrtb2.BidResponse
 	if auctionResponse != nil {
 		response = auctionResponse.BidResponse
@@ -287,6 +296,9 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	}
 
 	labels, ao = sendAmpResponse(w, hookExecutor, auctionResponse, reqWrapper, account, labels, ao, errL)
+	if len(ao.Errors) == 0 && !auctionRequest.BidderResponseStartTime.IsZero() {
+		deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime))
+	}
 }
 
 func rejectAmpRequest(
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 9a0d0d07..8e01552c 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -92,6 +92,7 @@ func NewEndpoint(
 	bidderMap map[string]openrtb_ext.BidderName,
 	storedRespFetcher stored_requests.Fetcher,
 	hookExecutionPlanBuilder hooks.ExecutionPlanBuilder,
+	tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed,
 ) (httprouter.Handle, error) {
 	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil {
 		return nil, errors.New("NewEndpoint requires non-nil arguments.")
@@ -122,7 +123,8 @@ func NewEndpoint(
 		nil,
 		ipValidator,
 		storedRespFetcher,
-		hookExecutionPlanBuilder}).Auction), nil
+		hookExecutionPlanBuilder,
+		tmaxAdjustments}).Auction), nil
 }
 
 type endpointDeps struct {
@@ -144,6 +146,7 @@ type endpointDeps struct {
 	privateNetworkIPValidator iputil.IPValidator
 	storedRespFetcher         stored_requests.Fetcher
 	hookExecutionPlanBuilder  hooks.ExecutionPlanBuilder
+	tmaxAdjustments           *exchange.TmaxAdjustmentsPreprocessed
 }
 
 func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@@ -250,8 +253,14 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		HookExecutor:               hookExecutor,
 		TCF2Config:                 tcf2Config,
 		Activities:                 activities,
+		TmaxAdjustments:            deps.tmaxAdjustments,
 	}
 	auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
+	defer func() {
+		if !auctionRequest.BidderResponseStartTime.IsZero() {
+			deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime))
+		}
+	}()
 	ao.RequestWrapper = req
 	ao.Account = account
 	var response *openrtb2.BidResponse
@@ -283,6 +292,9 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		glog.Errorf("Error setting seat non-bid: %v", err)
 	}
 	labels, ao = sendAuctionResponse(w, hookExecutor, response, req.BidRequest, account, labels, ao)
+	if len(ao.Errors) == 0 && !auctionRequest.BidderResponseStartTime.IsZero() {
+		deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime))
+	}
 }
 
 // setSeatNonBidRaw is transitional function for setting SeatNonBid inside bidResponse.Ext
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 872ea2bd..a2a1e03e 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -56,6 +56,7 @@ func NewVideoEndpoint(
 	defReqJSON []byte,
 	bidderMap map[string]openrtb_ext.BidderName,
 	cache prebid_cache_client.Client,
+	tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed,
 ) (httprouter.Handle, error) {
 
 	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil {
@@ -89,7 +90,8 @@ func NewVideoEndpoint(
 		videoEndpointRegexp,
 		ipValidator,
 		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{}}).VideoAuctionEndpoint), nil
+		hooks.EmptyPlanBuilder{},
+		tmaxAdjustments}).VideoAuctionEndpoint), nil
 }
 
 /*
@@ -311,9 +313,15 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		GlobalPrivacyControlHeader: secGPC,
 		PubID:                      labels.PubID,
 		HookExecutor:               hookexecution.EmptyHookExecutor{},
+		TmaxAdjustments:            deps.tmaxAdjustments,
 	}
 
 	auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, &debugLog)
+	defer func() {
+		if !auctionRequest.BidderResponseStartTime.IsZero() {
+			deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime))
+		}
+	}()
 	vo.RequestWrapper = bidReqWrapper
 	var response *openrtb2.BidResponse
 	if auctionResponse != nil {
@@ -367,6 +375,10 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		return
 	}
 
+	if len(vo.Errors) == 0 && !auctionRequest.BidderResponseStartTime.IsZero() {
+		deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime))
+	}
+
 	w.Header().Set("Content-Type", "application/json")
 	w.Write(resp)
 
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index f8344525..6d0a4d80 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -169,6 +169,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/vidoomy"
 	"github.com/prebid/prebid-server/adapters/visiblemeasures"
 	"github.com/prebid/prebid-server/adapters/visx"
+	"github.com/prebid/prebid-server/adapters/vox"
 	"github.com/prebid/prebid-server/adapters/vrtcal"
 	"github.com/prebid/prebid-server/adapters/xeworks"
 	"github.com/prebid/prebid-server/adapters/yahooAds"
@@ -369,6 +370,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderViewdeos:          adtelligent.Builder,
 		openrtb_ext.BidderVisibleMeasures:   visiblemeasures.Builder,
 		openrtb_ext.BidderVisx:              visx.Builder,
+		openrtb_ext.BidderVox:               vox.Builder,
 		openrtb_ext.BidderVrtcal:            vrtcal.Builder,
 		openrtb_ext.BidderXeworks:           xeworks.Builder,
 		openrtb_ext.BidderYahooAds:          yahooAds.Builder,
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index ad1866a6..9551ab3e 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -33,6 +33,10 @@ func buildBidders(infos config.BidderInfos, builders map[openrtb_ext.BidderName]
 	var errs []error
 
 	for bidder, info := range infos {
+		if len(info.AliasOf) > 0 {
+			errs = append(errs, fmt.Errorf("This feature is currently under development"))
+			continue
+		}
 		bidderName, bidderNameFound := openrtb_ext.NormalizeBidderName(bidder)
 		if !bidderNameFound {
 			errs = append(errs, fmt.Errorf("%v: unknown bidder", bidder))
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 27012ff4..5d9d5cba 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -67,6 +67,7 @@ type bidRequestOptions struct {
 	headerDebugAllowed  bool
 	addCallSignHeader   bool
 	bidAdjustments      map[string]float64
+	tmaxAdjustments     *TmaxAdjustmentsPreprocessed
 }
 
 type extraBidderRespInfo struct {
@@ -143,6 +144,11 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 	//check if real request exists for this bidder or it only has stored responses
 	dataLen := 0
 	if len(bidderRequest.BidRequest.Imp) > 0 {
+		// Reducing the amount of time bidders have to compensate for the processing time used by PBS to fetch a stored request (if needed), validate the OpenRTB request and split it into multiple requests sanitized for each bidder
+		// As well as for the time needed by PBS to prepare the auction response
+		if bidRequestOptions.tmaxAdjustments != nil && bidRequestOptions.tmaxAdjustments.IsEnforced {
+			bidderRequest.BidRequest.TMax = getBidderTmax(&bidderTmaxCtx{ctx}, bidderRequest.BidRequest.TMax, *bidRequestOptions.tmaxAdjustments)
+		}
 		reqData, errs = bidder.Bidder.MakeRequests(bidderRequest.BidRequest, reqInfo)
 
 		if len(reqData) == 0 {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index e1a9868d..1436f3ea 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -6,7 +6,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/prebid/prebid-server/privacy"
 	"math/rand"
 	"net/url"
 	"runtime/debug"
@@ -15,6 +14,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/prebid/prebid-server/privacy"
+
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/adservertargeting"
 	"github.com/prebid/prebid-server/bidadjustment"
@@ -205,11 +206,13 @@ type AuctionRequest struct {
 	// map of imp id to stored response
 	StoredAuctionResponses stored_responses.ImpsWithBidResponses
 	// map of imp id to bidder to stored response
-	StoredBidResponses    stored_responses.ImpBidderStoredResp
-	BidderImpReplaceImpID stored_responses.BidderImpReplaceImpID
-	PubID                 string
-	HookExecutor          hookexecution.StageExecutor
-	QueryParams           url.Values
+	StoredBidResponses      stored_responses.ImpBidderStoredResp
+	BidderImpReplaceImpID   stored_responses.BidderImpReplaceImpID
+	PubID                   string
+	HookExecutor            hookexecution.StageExecutor
+	QueryParams             url.Values
+	BidderResponseStartTime time.Time
+	TmaxAdjustments         *TmaxAdjustmentsPreprocessed
 }
 
 // BidderRequest holds the bidder specific request and all other
@@ -367,9 +370,10 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 			alternateBidderCodes = *r.Account.AlternateBidderCodes
 		}
 		var extraRespInfo extraAuctionResponseInfo
-		adapterBids, adapterExtra, extraRespInfo = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules)
+		adapterBids, adapterExtra, extraRespInfo = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules, r.TmaxAdjustments)
 		fledge = extraRespInfo.fledge
 		anyBidsReturned = extraRespInfo.bidsFound
+		r.BidderResponseStartTime = extraRespInfo.bidderResponseStartTime
 	}
 
 	var (
@@ -658,7 +662,8 @@ func (e *exchange) getAllBids(
 	experiment *openrtb_ext.Experiment,
 	hookExecutor hookexecution.StageExecutor,
 	pbsRequestStartTime time.Time,
-	bidAdjustmentRules map[string][]openrtb_ext.Adjustment) (
+	bidAdjustmentRules map[string][]openrtb_ext.Adjustment,
+	tmaxAdjustments *TmaxAdjustmentsPreprocessed) (
 	map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
 	map[openrtb_ext.BidderName]*seatResponseExtra,
 	extraAuctionResponseInfo) {
@@ -697,6 +702,7 @@ func (e *exchange) getAllBids(
 				headerDebugAllowed:  headerDebugAllowed,
 				addCallSignHeader:   isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]),
 				bidAdjustments:      bidAdjustments,
+				tmaxAdjustments:     tmaxAdjustments,
 			}
 			seatBids, extraBidderRespInfo, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, bidAdjustmentRules)
 			brw.bidderResponseStartTime = extraBidderRespInfo.respProcessingStartTime
diff --git a/exchange/tmax_adjustments.go b/exchange/tmax_adjustments.go
new file mode 100644
index 00000000..86934ecc
--- /dev/null
+++ b/exchange/tmax_adjustments.go
@@ -0,0 +1,57 @@
+package exchange
+
+import (
+	"context"
+	"time"
+
+	"github.com/prebid/prebid-server/config"
+)
+
+type TmaxAdjustmentsPreprocessed struct {
+	BidderNetworkLatencyBuffer     uint
+	PBSResponsePreparationDuration uint
+	BidderResponseDurationMin      uint
+
+	IsEnforced bool
+}
+
+func ProcessTMaxAdjustments(adjustmentsConfig config.TmaxAdjustments) *TmaxAdjustmentsPreprocessed {
+	if !adjustmentsConfig.Enabled {
+		return nil
+	}
+
+	isEnforced := adjustmentsConfig.BidderResponseDurationMin != 0 &&
+		(adjustmentsConfig.BidderNetworkLatencyBuffer != 0 || adjustmentsConfig.PBSResponsePreparationDuration != 0)
+
+	tmax := &TmaxAdjustmentsPreprocessed{
+		BidderNetworkLatencyBuffer:     adjustmentsConfig.BidderNetworkLatencyBuffer,
+		PBSResponsePreparationDuration: adjustmentsConfig.PBSResponsePreparationDuration,
+		BidderResponseDurationMin:      adjustmentsConfig.BidderResponseDurationMin,
+		IsEnforced:                     isEnforced,
+	}
+
+	return tmax
+}
+
+type bidderTmaxContext interface {
+	Deadline() (deadline time.Time, ok bool)
+	RemainingDurationMS(deadline time.Time) int64
+}
+type bidderTmaxCtx struct{ ctx context.Context }
+
+func (b *bidderTmaxCtx) RemainingDurationMS(deadline time.Time) int64 {
+	return time.Until(deadline).Milliseconds()
+}
+func (b *bidderTmaxCtx) Deadline() (deadline time.Time, ok bool) {
+	deadline, ok = b.ctx.Deadline()
+	return
+}
+
+func getBidderTmax(ctx bidderTmaxContext, requestTmaxMS int64, tmaxAdjustments TmaxAdjustmentsPreprocessed) int64 {
+	if tmaxAdjustments.IsEnforced {
+		if deadline, ok := ctx.Deadline(); ok {
+			return ctx.RemainingDurationMS(deadline) - int64(tmaxAdjustments.BidderNetworkLatencyBuffer) - int64(tmaxAdjustments.PBSResponsePreparationDuration)
+		}
+	}
+	return requestTmaxMS
+}
diff --git a/gdpr/consent_parser.go b/gdpr/consent_parser.go
new file mode 100644
index 00000000..3a354886
--- /dev/null
+++ b/gdpr/consent_parser.go
@@ -0,0 +1,67 @@
+package gdpr
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/prebid/go-gdpr/api"
+	"github.com/prebid/go-gdpr/vendorconsent"
+	tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2"
+)
+
+// parsedConsent represents a parsed consent string containing notable version information and a convenient
+// metadata object that allows easy examination of encoded purpose and vendor information
+type parsedConsent struct {
+	encodingVersion uint8
+	listVersion     uint16
+	specVersion     uint16
+	consentMeta     tcf2.ConsentMetadata
+}
+
+// parseConsent parses and validates the specified consent string returning an instance of parsedConsent
+func parseConsent(consent string) (*parsedConsent, error) {
+	pc, err := vendorconsent.ParseString(consent)
+	if err != nil {
+		return nil, &ErrorMalformedConsent{
+			Consent: consent,
+			Cause:   err,
+		}
+	}
+	if err = validateVersions(pc); err != nil {
+		return nil, &ErrorMalformedConsent{
+			Consent: consent,
+			Cause:   err,
+		}
+	}
+	cm, ok := pc.(tcf2.ConsentMetadata)
+	if !ok {
+		err = errors.New("Unable to access TCF2 parsed consent")
+		return nil, err
+	}
+	return &parsedConsent{
+		encodingVersion: pc.Version(),
+		listVersion:     pc.VendorListVersion(),
+		specVersion:     getSpecVersion(pc.TCFPolicyVersion()),
+		consentMeta:     cm,
+	}, nil
+}
+
+// validateVersions ensures that certain version fields in the consent string contain valid values.
+// An error is returned if at least one of them is invalid
+func validateVersions(pc api.VendorConsents) (err error) {
+	version := pc.Version()
+	if version != 2 {
+		return fmt.Errorf("invalid encoding format version: %d", version)
+	}
+	return
+}
+
+// getSpecVersion looks at the TCF policy version and determines the corresponding GVL specification
+// version that should be used to calculate legal basis. A zero value is returned if the policy version
+// is invalid
+func getSpecVersion(policyVersion uint8) uint16 {
+	if policyVersion >= 4 {
+		return 3
+	}
+	return 2
+}
diff --git a/gdpr/full_enforcement.go b/gdpr/full_enforcement.go
index b86f7a3f..eefa28d5 100644
--- a/gdpr/full_enforcement.go
+++ b/gdpr/full_enforcement.go
@@ -67,6 +67,9 @@ func (fe *FullEnforcement) applyEnforceOverrides(overrides Overrides) (enforcePu
 // must declare the purpose as either consent or flex and the user must consent in accordance with
 // the purpose configs.
 func (fe *FullEnforcement) consentEstablished(consent tcf2.ConsentMetadata, vi VendorInfo, enforcePurpose bool, enforceVendors bool) bool {
+	if vi.vendor == nil {
+		return false
+	}
 	if !vi.vendor.Purpose(fe.cfg.PurposeID) {
 		return false
 	}
@@ -84,6 +87,9 @@ func (fe *FullEnforcement) consentEstablished(consent tcf2.ConsentMetadata, vi V
 // established, the vendor must declare the purpose as either legit interest or flex and the user
 // must have been provided notice for the legit interest basis in accordance with the purpose configs.
 func (fe *FullEnforcement) legitInterestEstablished(consent tcf2.ConsentMetadata, vi VendorInfo, enforcePurpose bool, enforceVendors bool) bool {
+	if vi.vendor == nil {
+		return false
+	}
 	if !vi.vendor.LegitimateInterest(fe.cfg.PurposeID) {
 		return false
 	}
diff --git a/gdpr/impl.go b/gdpr/impl.go
index 4869a84d..cc88a1fd 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -2,12 +2,9 @@ package gdpr
 
 import (
 	"context"
-	"errors"
-	"fmt"
 
 	"github.com/prebid/go-gdpr/api"
 	"github.com/prebid/go-gdpr/consentconstants"
-	"github.com/prebid/go-gdpr/vendorconsent"
 	tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
@@ -63,53 +60,27 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCo
 	if _, ok := p.nonStandardPublishers[p.publisherID]; ok {
 		return AllowAll, nil
 	}
-
 	if p.gdprSignal != SignalYes {
 		return AllowAll, nil
 	}
-
 	if p.consent == "" {
 		return p.defaultPermissions(), nil
 	}
-
-	// note the bidder here is guaranteed to be enabled
-	vendorID, vendorFound := p.resolveVendorID(bidderCoreName, bidder)
-	basicEnforcementVendors := p.cfg.BasicEnforcementVendors()
-	_, weakVendorEnforcement := basicEnforcementVendors[string(bidder)]
-
-	if !vendorFound && !weakVendorEnforcement {
-		return DenyAll, nil
-	}
-
-	parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, p.consent)
+	pc, err := parseConsent(p.consent)
 	if err != nil {
 		return p.defaultPermissions(), err
 	}
-
-	// vendor will be nil if not a valid TCF2 consent string
-	if vendor == nil {
-		if weakVendorEnforcement && parsedConsent.Version() == 2 {
-			vendor = vendorTrue{}
-		} else {
-			return p.defaultPermissions(), nil
-		}
-	}
-
-	if !p.cfg.IsEnabled() {
-		return AllowBidRequestOnly, nil
-	}
-
-	consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata)
-	if !ok {
-		err = fmt.Errorf("Unable to access TCF2 parsed consent")
+	vendorID, _ := p.resolveVendorID(bidderCoreName, bidder)
+	vendor, err := p.getVendor(ctx, vendorID, *pc)
+	if err != nil {
 		return p.defaultPermissions(), err
 	}
-
 	vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}
+
 	permissions = AuctionPermissions{}
-	permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, consentMeta, vendorInfo)
-	permissions.PassGeo = p.allowGeo(bidderCoreName, consentMeta, vendor)
-	permissions.PassID = p.allowID(bidderCoreName, consentMeta, vendorInfo)
+	permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, pc.consentMeta, vendorInfo)
+	permissions.PassGeo = p.allowGeo(bidderCoreName, pc.consentMeta, vendor)
+	permissions.PassID = p.allowID(bidderCoreName, pc.consentMeta, vendorInfo)
 
 	return permissions, nil
 }
@@ -148,34 +119,28 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, bidder
 	if p.consent == "" {
 		return false, nil
 	}
-
-	parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, p.consent)
+	pc, err := parseConsent(p.consent)
 	if err != nil {
 		return false, err
 	}
-
-	if vendor == nil {
+	vendor, err := p.getVendor(ctx, vendorID, *pc)
+	if err != nil {
 		return false, nil
 	}
+	vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}
 
 	if !p.cfg.PurposeEnforced(consentconstants.Purpose(1)) {
 		return true, nil
 	}
-	consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata)
-	if !ok {
-		err := errors.New("Unable to access TCF2 parsed consent")
-		return false, err
-	}
 
-	if p.cfg.PurposeOneTreatmentEnabled() && consentMeta.PurposeOneTreatment() {
+	if p.cfg.PurposeOneTreatmentEnabled() && pc.consentMeta.PurposeOneTreatment() {
 		return p.cfg.PurposeOneTreatmentAccessAllowed(), nil
 	}
 
 	purpose := consentconstants.Purpose(1)
 	enforcer := p.purposeEnforcerBuilder(purpose, bidder)
 
-	vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}
-	if enforcer.LegalBasis(vendorInfo, bidder, consentMeta, Overrides{blockVendorExceptions: !vendorException}) {
+	if enforcer.LegalBasis(vendorInfo, bidder, pc.consentMeta, Overrides{blockVendorExceptions: !vendorException}) {
 		return true, nil
 	}
 	return false, nil
@@ -205,7 +170,7 @@ func (p *permissionsImpl) allowGeo(bidder openrtb_ext.BidderName, consentMeta tc
 
 	basicEnforcementVendors := p.cfg.BasicEnforcementVendors()
 	_, weakVendorEnforcement := basicEnforcementVendors[string(bidder)]
-	return consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialFeature(1) || weakVendorEnforcement)
+	return consentMeta.SpecialFeatureOptIn(1) && ((vendor != nil && vendor.SpecialFeature(1)) || weakVendorEnforcement)
 }
 
 // allowID computes the pass user ID activity legal basis for a given bidder using the enforcement algorithms
@@ -229,53 +194,13 @@ func (p *permissionsImpl) allowID(bidder openrtb_ext.BidderName, consentMeta tcf
 	return false
 }
 
-// parseVendor parses the consent string and fetches the specified vendor's information from the GVL
-func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent api.VendorConsents, vendor api.Vendor, err error) {
-	parsedConsent, err = vendorconsent.ParseString(consent)
+// getVendor retrieves the GVL vendor information for a particular bidder
+func (p *permissionsImpl) getVendor(ctx context.Context, vendorID uint16, pc parsedConsent) (api.Vendor, error) {
+	vendorList, err := p.fetchVendorList(ctx, pc.specVersion, pc.listVersion)
 	if err != nil {
-		err = &ErrorMalformedConsent{
-			Consent: consent,
-			Cause:   err,
-		}
-		return
-	}
-
-	version := parsedConsent.Version()
-	if version != 2 {
-		return
-	}
-
-	policyVersion := parsedConsent.TCFPolicyVersion()
-	specVersion, err := getSpecVersion(policyVersion)
-	if err != nil {
-		err = &ErrorMalformedConsent{
-			Consent: consent,
-			Cause:   err,
-		}
-		return
-	}
-
-	vendorList, err := p.fetchVendorList(ctx, uint16(specVersion), parsedConsent.VendorListVersion())
-	if err != nil {
-		return
-	}
-
-	vendor = vendorList.Vendor(vendorID)
-	return
-}
-
-// getSpecVersion looks at the TCF policy version and determines the corresponding GVL specification
-// version that should be used to calculate legal basis
-func getSpecVersion(policyVersion uint8) (uint16, error) {
-	var specVersion uint16 = 3
-
-	if policyVersion > 4 {
-		return 0, fmt.Errorf("invalid TCF policy version %d", policyVersion)
+		return nil, err
 	}
-	if policyVersion < 4 {
-		specVersion = 2
-	}
-	return specVersion, nil
+	return vendorList.Vendor(vendorID), nil
 }
 
 // AllowHostCookies represents a GDPR permissions policy with host cookie syncing always allowed
@@ -300,25 +225,3 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B
 func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) {
 	return AllowAll, nil
 }
-
-// vendorTrue claims everything.
-type vendorTrue struct{}
-
-func (v vendorTrue) Purpose(purposeID consentconstants.Purpose) bool {
-	return true
-}
-func (v vendorTrue) PurposeStrict(purposeID consentconstants.Purpose) bool {
-	return true
-}
-func (v vendorTrue) LegitimateInterest(purposeID consentconstants.Purpose) bool {
-	return true
-}
-func (v vendorTrue) LegitimateInterestStrict(purposeID consentconstants.Purpose) bool {
-	return true
-}
-func (v vendorTrue) SpecialFeature(featureID consentconstants.SpecialFeature) (hasSpecialFeature bool) {
-	return true
-}
-func (v vendorTrue) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpecialPurpose bool) {
-	return true
-}
diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go
index eb387789..f9d0c4d9 100644
--- a/metrics/prometheus/prometheus.go
+++ b/metrics/prometheus/prometheus.go
@@ -178,7 +178,7 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet
 	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}
-	overheadTimeBuckets := []float64{0.00005, 0.0001, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.05}
+	overheadTimeBuckets := []float64{0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1}
 
 	metrics := Metrics{}
 	reg := prometheus.NewRegistry()
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 978af5e0..a43861dd 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -269,6 +269,7 @@ const (
 	BidderViewdeos          BidderName = "viewdeos"
 	BidderVisibleMeasures   BidderName = "visiblemeasures"
 	BidderVisx              BidderName = "visx"
+	BidderVox               BidderName = "vox"
 	BidderVrtcal            BidderName = "vrtcal"
 	BidderXeworks           BidderName = "xeworks"
 	BidderYahooAds          BidderName = "yahooAds"
@@ -468,6 +469,7 @@ func CoreBidderNames() []BidderName {
 		BidderViewdeos,
 		BidderVisibleMeasures,
 		BidderVisx,
+		BidderVox,
 		BidderVrtcal,
 		BidderXeworks,
 		BidderYahooAds,
diff --git a/openrtb_ext/imp_adnuntius.go b/openrtb_ext/imp_adnuntius.go
index b1e41e80..3759ad50 100644
--- a/openrtb_ext/imp_adnuntius.go
+++ b/openrtb_ext/imp_adnuntius.go
@@ -3,5 +3,6 @@ package openrtb_ext
 type ImpExtAdnunitus struct {
 	Auid      string `json:"auId"`
 	Network   string `json:"network"`
-	NoCookies bool   `json:noCookies`
+	NoCookies bool   `json:"noCookies'"`
+	MaxDeals  int    `json:"maxDeals"`
 }
diff --git a/openrtb_ext/imp_rtbhouse.go b/openrtb_ext/imp_rtbhouse.go
new file mode 100644
index 00000000..747ea158
--- /dev/null
+++ b/openrtb_ext/imp_rtbhouse.go
@@ -0,0 +1,10 @@
+package openrtb_ext
+
+// ExtImpRTBHouse defines the contract for bidrequest.imp[i].ext.prebid.bidder.rtbhouse
+type ExtImpRTBHouse struct {
+	PublisherId string `json:"publisherId"`
+	Region      string `json:"region"`
+
+	BidFloor float64 `json:"bidfloor,omitempty"`
+	Channel  string  `json:"channel,omitempty"`
+}
diff --git a/openrtb_ext/imp_vox.go b/openrtb_ext/imp_vox.go
new file mode 100644
index 00000000..a6152507
--- /dev/null
+++ b/openrtb_ext/imp_vox.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+type ImpExtVox struct {
+	PlacementID  string   `json:"placementId"`
+	ImageUrl     string   `json:"imageUrl"`
+	DisplaySizes []string `json:"displaySizes"`
+}
diff --git a/router/router.go b/router/router.go
index 8fc99589..12bfbf59 100644
--- a/router/router.go
+++ b/router/router.go
@@ -214,21 +214,22 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R
 		glog.Fatalf("Failed to create ads cert signer: %v", err)
 	}
 
+	tmaxAdjustments := exchange.ProcessTMaxAdjustments(cfg.TmaxAdjustments)
 	planBuilder := hooks.NewExecutionPlanBuilder(cfg.Hooks, repo)
 	macroReplacer := macros.NewStringIndexBasedReplacer()
 	theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner, macroReplacer)
 	var uuidGenerator uuidutil.UUIDRandomGenerator
-	openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder)
+	openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments)
 	if err != nil {
 		glog.Fatalf("Failed to create the openrtb2 endpoint handler. %v", err)
 	}
 
-	ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder)
+	ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments)
 	if err != nil {
 		glog.Fatalf("Failed to create the amp endpoint handler. %v", err)
 	}
 
-	videoEndpoint, err := openrtb2.NewVideoEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, cacheClient)
+	videoEndpoint, err := openrtb2.NewVideoEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, cacheClient, tmaxAdjustments)
 	if err != nil {
 		glog.Fatalf("Failed to create the video endpoint handler. %v", err)
 	}
diff --git a/server/server.go b/server/server.go
index 31764cc4..9282f0fc 100644
--- a/server/server.go
+++ b/server/server.go
@@ -20,7 +20,7 @@ import (
 
 // Listen blocks forever, serving PBS requests on the given port. This will block forever, until the process is shut down.
 func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.Handler, metrics *metricsconfig.DetailedMetricsEngine) (err error) {
-	stopSignals := make(chan os.Signal)
+	stopSignals := make(chan os.Signal, 1)
 	signal.Notify(stopSignals, syscall.SIGTERM, syscall.SIGINT)
 
 	// Run the servers. Fan any process-stopper signals out to each server for graceful shutdowns.
diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml
index ad3562de..72bdd21d 100644
--- a/static/bidder-info/inmobi.yaml
+++ b/static/bidder-info/inmobi.yaml
@@ -1,6 +1,6 @@
 endpoint: "https://api.w.inmobi.com/showad/openrtb/bidder/prebid"
 maintainer:
-  email: "technology-irv@inmobi.com"
+  email: "prebid-support@inmobi.com"
 gvlVendorID: 333
 capabilities:
   app:
diff --git a/static/bidder-info/orbidder.yaml b/static/bidder-info/orbidder.yaml
index b7891af3..705d35a5 100644
--- a/static/bidder-info/orbidder.yaml
+++ b/static/bidder-info/orbidder.yaml
@@ -6,3 +6,7 @@ capabilities:
   app:
     mediaTypes:
       - banner
+  site:
+    mediaTypes:
+      - banner
+      - native
diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml
index 6a116448..ad2fbfcb 100644
--- a/static/bidder-info/rtbhouse.yaml
+++ b/static/bidder-info/rtbhouse.yaml
@@ -1,6 +1,7 @@
 endpoint: "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids"
 maintainer:
   email: "prebid@rtbhouse.com"
+endpointCompression: gzip
 gvlVendorID: 16
 capabilities:
   site:
diff --git a/static/bidder-info/vox.yaml b/static/bidder-info/vox.yaml
new file mode 100644
index 00000000..e2b5c902
--- /dev/null
+++ b/static/bidder-info/vox.yaml
@@ -0,0 +1,19 @@
+endpoint: "https://ssp.hybrid.ai/prebid/server/v1/auction"
+maintainer:
+  email: prebid@hybrid.ai
+endpointCompression: gzip
+gvlVendorID: 206
+modifyingVastXmlAllowed: true
+capabilities:
+  site:
+    mediaTypes:
+      - banner
+      - video
+  app:
+    mediaTypes:
+      - banner
+      - video
+userSync:
+  redirect:
+    url: "https://ssp.hybrid.ai/prebid/server/v1/userSync?consent={{.GDPRConsent}}&redirect={{.RedirectURL}}"
+    userMacro: $UID
\ No newline at end of file
diff --git a/static/bidder-params/rtbhouse.json b/static/bidder-params/rtbhouse.json
index 00732bed..727f51bc 100644
--- a/static/bidder-params/rtbhouse.json
+++ b/static/bidder-params/rtbhouse.json
@@ -7,6 +7,18 @@
         "publisherId": {
             "type": "string",
             "description": "The publisher’s ID provided by RTB House"
+        },
+        "region": {
+            "type": "string",
+            "description": "The publisher’s assigned datacenter region"
+        },
+        "bidfloor": {
+            "type": "number",
+            "description": "(optional) Minimum bid price in CPM that will be considered"
+        },
+        "channel": {
+            "type": "string",
+            "description": "Inventory channel identifier, limited to 50 characters"
         }
     },
     "required": ["publisherId"]
diff --git a/static/bidder-params/vox.json b/static/bidder-params/vox.json
new file mode 100644
index 00000000..2ae65e99
--- /dev/null
+++ b/static/bidder-params/vox.json
@@ -0,0 +1,27 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Vox Adapter Params",
+  "description": "A schema which validates params accepted by the Vox adapter",
+  "type": "object",
+
+  "properties": {
+    "placementId": {
+      "type": "string",
+      "description": "Placement ID"
+    },
+    "imageUrl": {
+      "type": "string",
+      "description": "An URL of image on which banner should be put(InImage Ad format)."
+    },
+    "displaySizes": {
+      "type": "array",
+      "items": {
+        "type": "string",
+        "description": "<Width>x<Height>(Example: 123x90, 720x100)"
+      },
+      "description": "Display banner sizes which could be shown on top of image."
+    }
+  },
+
+  "required": ["placementId"]
+}
\ No newline at end of file


Test patch:
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json
index 1f213a43..1987fb9d 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json
@@ -81,7 +81,7 @@
 	],
 	"expectedMakeBidsErrors": [
 		{
-			"value": "json: cannot unmarshal number into Go struct field .AdUnits.Ads.CreativeHeight of type string",
+			"value": "json: cannot unmarshal number into Go struct field Ad.AdUnits.Ads.CreativeHeight of type string",
 			"comparison": "literal"
 		}
 	]
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json b/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json
new file mode 100644
index 00000000..1d4c5bf0
--- /dev/null
+++ b/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json
@@ -0,0 +1,168 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"site": {
+			"page": "prebid.org"
+		},
+		"imp": [
+			{
+				"id": "test-imp-id",
+				"banner": {
+					"format": [
+						{
+							"w": 300,
+							"h": 250
+						},
+						{
+							"w": 300,
+							"h": 600
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"auId": "123",
+						"maxDeals": 2
+					}
+				}
+			}
+		]
+	},
+	
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://whatever.url?format=json&tzo=0",
+				"body": {
+					"adUnits": [
+						{
+							"auId": "123",
+							"targetId": "123-test-imp-id",
+							"dimensions": [[300,250],[300,600]],
+							"maxDeals": 2
+						}
+					],
+					"context": "prebid.org",
+					"metaData": {}
+				}
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"adUnits": [
+						{
+							"auId": "0000000000000123",
+							"targetId": "123-test-imp-id",
+							"html": "<ADCODE>",
+							"matchedAdCount": 1,
+							"responseId": "adn-rsp-815135818",
+							"deals": [
+								{
+									"destinationUrlEsc": "",
+									"clickUrl": "https://click.url",
+									"urls": {
+										"url": "https://delivery.adnuntius.com/c/eKOchNsBJzE.net"
+									},
+									"destinationUrls": {
+										"url": "http://www.google.com"
+									},
+									"cpm": {
+										"amount": 1.0,
+										"currency": "NOK"
+									},
+									"bid": {
+										"amount": 0.001,
+										"currency": "NOK"
+									},
+									"grossBid": {
+										"amount": 0.001,
+										"currency": "NOK"
+									},
+									"netBid": {
+										"amount": 0.001,
+										"currency": "NOK"
+									},
+									"cost": {
+										"amount": 0.001,
+										"currency": "NOK"
+									},
+									"dealId": "deal_123",
+									"adId": "adn-id-116330612",
+									"creativeWidth": "300",
+									"creativeHeight": "250",
+									"creativeId": "dl0knc1lnks9jgvx",
+									"lineItemId": "6l5w2d29kz3vkprq",
+									"layoutId": "adnuntius_image_layout_1",
+									"layoutName": "Image",
+									"layoutExternalReference": "",
+									"html": "<ADCODE>"
+								}
+							],
+							"ads": [
+								{
+									"destinationUrls": {
+										"url": "http://www.google.com"
+									},
+									"bid": {
+										"amount": 0.001,
+										"currency": "NOK"
+									},
+									"adId": "adn-id-1559784095",
+									"creativeWidth": "300",
+									"creativeHeight": "250",
+									"creativeId": "jn9hpzvlsf8cpdmm",
+									"lineItemId": "q7y9qm5b0xt9htrv"
+								}
+							]
+						}
+					],
+					"metaData": {
+						"usi": "5dlpmw0d00btldjdvk1lp8rl",
+						"sessionId": "e4ada7251c93291a871f8e4319cc8fe5"
+					},
+					"duplicateFilter": "AAAAAwAAAAAAJ33PAAAAAAAhrK4AAAAAACYNPAAAAAA=",
+					"segments": [],
+					"keywords": []
+				}
+			}
+		}
+	],
+	"expectedBidResponses": [
+		{
+			"bids": [
+				{
+					"bid": {
+						"id": "adn-id-1559784095",
+						"impid": "test-imp-id",
+						"price": 1,
+						"adm": "<ADCODE>",
+						"adid": "adn-id-1559784095",
+						"adomain": ["google.com"],
+						"cid": "q7y9qm5b0xt9htrv",
+						"crid": "jn9hpzvlsf8cpdmm",
+						"w": 300,
+						"h": 250
+					},
+					"type": "banner"
+				},
+        {
+					"bid": {
+						"id": "adn-id-116330612",
+						"impid": "test-imp-id",
+						"price": 1,
+						"adm": "<ADCODE>",
+						"adid": "adn-id-116330612",
+						"adomain": ["google.com"],
+						"cid": "6l5w2d29kz3vkprq",
+						"crid": "dl0knc1lnks9jgvx",
+						"dealid": "deal_123",
+						"w": 300,
+						"h": 250
+					},
+					"type": "banner"
+				}
+			],
+			"currency": "NOK"
+		}
+	]
+}
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json
index 1fd8a83f..4f109942 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json
@@ -81,7 +81,7 @@
 	],
 	"expectedMakeBidsErrors": [
 		{
-			"value": "json: cannot unmarshal number into Go struct field .AdUnits.Ads.CreativeWidth of type string",
+			"value": "json: cannot unmarshal number into Go struct field Ad.AdUnits.Ads.CreativeWidth of type string",
 			"comparison": "literal"
 		}
 	]
diff --git a/adapters/bidder_test.go b/adapters/bidder_test.go
index f0b833ce..d6373bde 100644
--- a/adapters/bidder_test.go
+++ b/adapters/bidder_test.go
@@ -52,12 +52,12 @@ type mockConversions struct {
 	mock.Mock
 }
 
-func (m mockConversions) GetRate(from string, to string) (float64, error) {
+func (m *mockConversions) GetRate(from string, to string) (float64, error) {
 	args := m.Called(from, to)
 	return args.Get(0).(float64), args.Error(1)
 }
 
-func (m mockConversions) GetRates() *map[string]map[string]float64 {
+func (m *mockConversions) GetRates() *map[string]map[string]float64 {
 	args := m.Called()
 	return args.Get(0).(*map[string]map[string]float64)
 }
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps-multi-format.json b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps-multi-format.json
new file mode 100644
index 00000000..c31cb1ce
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps-multi-format.json
@@ -0,0 +1,154 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 320,
+                            "h": 50
+                        }
+                    ]
+                },
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 320,
+                    "h": 480
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 123456,
+                        "pub_id": 1234
+                    }
+                }
+            },
+            {
+                "id": "2",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 50
+                        }
+                    ]
+                },
+                "native": {
+                    "ver": "1.2",
+                    "request": "{\"native\":{\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"url\":\"...\"}},{\"id\":1,\"required\":1,\"title\":{\"text\":\"custom title\"}}]}}"
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 234567,
+                        "pub_id": 1234
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx.mobilefuse.com/openrtb?pub_id=1234",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 320,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 320,
+                                "h": 480
+                            },
+                            "tagid": "123456"
+                        },
+                        {
+                            "id": "2",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "native": {
+                                "ver": "1.2",
+                                "request": "{\"native\":{\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"url\":\"...\"}},{\"id\":1,\"required\":1,\"title\":{\"text\":\"custom title\"}}]}}"
+                            },
+                            "tagid": "234567"
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "seat": "mobilefuse",
+                            "bid": [
+                                {
+                                    "id": "test-bid-id",
+                                    "impid": "1",
+                                    "price": 1.50,
+                                    "adm": "some-test-ad",
+                                    "crid": "test-crid",
+                                    "h": 50,
+                                    "w": 320,
+                                    "ext": {
+                                        "mf": {
+                                            "media_type": "banner"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-bid-id",
+                        "impid": "1",
+                        "price": 1.50,
+                        "adm": "some-test-ad",
+                        "crid": "test-crid",
+                        "w": 320,
+                        "h": 50
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json
index 568e51af..15ba4f96 100644
--- a/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json
@@ -57,6 +57,18 @@
                                 ]
                             },
                             "tagid": "123456"
+                        },
+                        {
+                            "id": "2",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "tagid": "234567"
                         }
                     ]
                 }
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/skadn.json b/adapters/mobilefuse/mobilefusetest/exemplary/skadn.json
new file mode 100644
index 00000000..e8fd66b1
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/skadn.json
@@ -0,0 +1,78 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                },
+                "ext": {
+                    "skadn": {
+                        "versions": [
+                            "2.0",
+                            "2.1"
+                        ],
+                        "sourceapp": "11111",
+                        "skadnetids": [
+                            "424m5254lk.skadnetwork",
+                            "4fzdc2evr5.skadnetwork"
+                        ]
+                    },
+                    "bidder": {
+                        "placement_id": 999999,
+                        "pub_id": 1111,
+                        "tagid_src": "ext"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx.mobilefuse.com/openrtb?pub_id=1111&tagid_src=ext",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            },
+                            "tagid": "999999",
+                            "ext": {
+                                "skadn": {
+                                    "versions": [
+                                        "2.0",
+                                        "2.1"
+                                    ],
+                                    "sourceapp": "11111",
+                                    "skadnetids": [
+                                        "424m5254lk.skadnetwork",
+                                        "4fzdc2evr5.skadnetwork"
+                                    ]
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 204,
+                "body": {}
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/orbidder/orbiddertest/exemplary/multibid-multi-format-with-mtype.json b/adapters/orbidder/orbiddertest/exemplary/multibid-multi-format-with-mtype.json
new file mode 100644
index 00000000..011acb2c
--- /dev/null
+++ b/adapters/orbidder/orbiddertest/exemplary/multibid-multi-format-with-mtype.json
@@ -0,0 +1,185 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "page": "https://test.orbidder.de"
+    },
+    "user": {
+      "buyeruid": "XX672XXX-5XXd-4XX2-8XX6-6XXXXc9cXXXX.v1"
+    },
+    "imp": [
+      {
+        "id": "multi-format-test-imp-id-1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "native": {
+          "ver":"1.2",
+          "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]"
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "orbidder-test",
+            "placementId": "web-test-placement-1",
+            "bidfloor": 0.1
+          }
+        }
+      },{
+        "id": "multi-format-test-imp-id-2",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "native": {
+          "ver":"1.2",
+          "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]"
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "orbidder-test",
+            "placementId": "web-test-placement-2",
+            "bidfloor": 0.1
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://orbidder-test",
+        "body": {
+          "id": "test-request-id",
+          "site": {
+            "page": "https://test.orbidder.de"
+          },
+          "user": {
+            "buyeruid": "XX672XXX-5XXd-4XX2-8XX6-6XXXXc9cXXXX.v1"
+          },
+          "imp": [
+            {
+              "id": "multi-format-test-imp-id-1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ]
+              },
+              "native": {
+                "ver":"1.2",
+                "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]"
+              },
+              "bidfloorcur": "EUR",
+              "ext": {
+                "bidder": {
+                  "accountId": "orbidder-test",
+                  "placementId": "web-test-placement-1",
+                  "bidfloor": 0.1
+                }
+              }
+            },{
+              "id": "multi-format-test-imp-id-2",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ]
+              },
+              "native": {
+                "ver":"1.2",
+                "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]"
+              },
+              "bidfloorcur": "EUR",
+              "ext": {
+                "bidder": {
+                  "accountId": "orbidder-test",
+                  "placementId": "web-test-placement-2",
+                  "bidfloor": 0.1
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "seat-id",
+              "bid": [
+                {
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e900",
+                  "impid": "multi-format-test-imp-id-1",
+                  "adid": "11110126",
+                  "price": 0.600000,
+                  "adm": "banner-some-test-ad",
+                  "crid": "banner-test-crid",
+                  "h": 250,
+                  "w": 300,
+                  "mtype": 1
+                },{
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                  "impid": "multi-format-test-imp-id-2",
+                  "adid": "11110136",
+                  "price": 0.500000,
+                  "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"text\":\"Orbidder rocks\"}},{\"id\":1,\"required\":1,\"img\":{\"url\":\"https://prd.orbidder.de/common/logo.png\"}}],\"link\":{\"url\":\"https://www.otto.de/\",\"clicktrackers\":[\"https://prd.orbidder.de/click\"]},\"imptrackers\":[\"https://prd.orbidder.de/imp\"]}",
+                  "crid": "native-test-crid",
+                  "mtype": 4
+                }
+              ]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "EUR"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e900",
+            "impid": "multi-format-test-imp-id-1",
+            "adid": "11110126",
+            "price": 0.6,
+            "adm": "banner-some-test-ad",
+            "crid": "banner-test-crid",
+            "w": 300,
+            "h": 250,
+            "mtype": 1
+          },
+          "type": "banner"
+        },{
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "multi-format-test-imp-id-2",
+            "adid": "11110136",
+            "price": 0.5,
+            "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"text\":\"Orbidder rocks\"}},{\"id\":1,\"required\":1,\"img\":{\"url\":\"https://prd.orbidder.de/common/logo.png\"}}],\"link\":{\"url\":\"https://www.otto.de/\",\"clicktrackers\":[\"https://prd.orbidder.de/click\"]},\"imptrackers\":[\"https://prd.orbidder.de/imp\"]}",
+            "crid": "native-test-crid",
+            "mtype": 4
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json b/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json
index 2315a52c..80b3c794 100644
--- a/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json
+++ b/adapters/orbidder/orbiddertest/exemplary/simple-app-banner.json
@@ -79,7 +79,8 @@
                   "adm": "some-test-ad",
                   "crid": "test-crid",
                   "h": 250,
-                  "w": 300
+                  "w": 300,
+                  "mtype": 1
                 }
               ]
             }
@@ -102,7 +103,8 @@
             "adm": "some-test-ad",
             "crid": "test-crid",
             "w": 300,
-            "h": 250
+            "h": 250,
+            "mtype": 1
           },
           "type": "banner"
         }
diff --git a/adapters/orbidder/orbiddertest/exemplary/simple-web-banner.json b/adapters/orbidder/orbiddertest/exemplary/simple-web-banner.json
new file mode 100644
index 00000000..9e1181a5
--- /dev/null
+++ b/adapters/orbidder/orbiddertest/exemplary/simple-web-banner.json
@@ -0,0 +1,114 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "page": "https://test.orbidder.de"
+    },
+    "user": {
+      "buyeruid": "XX672XXX-5XXd-4XX2-8XX6-6XXXXc9cXXXX.v1"
+    },
+    "imp": [
+      {
+        "id": "web-test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "orbidder-test",
+            "placementId": "web-test-placement",
+            "bidfloor": 0.1
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://orbidder-test",
+        "body": {
+          "id": "test-request-id",
+          "site": {
+            "page": "https://test.orbidder.de"
+          },
+          "user": {
+            "buyeruid": "XX672XXX-5XXd-4XX2-8XX6-6XXXXc9cXXXX.v1"
+          },
+          "imp": [
+            {
+              "id": "web-test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ]
+              },
+              "bidfloorcur": "EUR",
+              "ext": {
+                "bidder": {
+                  "accountId": "orbidder-test",
+                  "placementId": "web-test-placement",
+                  "bidfloor": 0.1
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "web-test-request-id",
+          "seatbid": [
+            {
+              "seat": "web-seat-id",
+              "bid": [
+                {
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                  "impid": "web-test-imp-id",
+                  "adid": "11110126",
+                  "price": 0.600000,
+                  "adm": "web-some-test-ad",
+                  "crid": "web-test-crid",
+                  "h": 250,
+                  "w": 300,
+                  "mtype": 1
+                }
+              ]
+            }
+          ],
+          "cur": "EUR"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "web-test-imp-id",
+            "adid": "11110126",
+            "price": 0.6,
+            "adm": "web-some-test-ad",
+            "crid": "web-test-crid",
+            "w": 300,
+            "h": 250,
+            "mtype": 1
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/orbidder/orbiddertest/exemplary/simple-web-native.json b/adapters/orbidder/orbiddertest/exemplary/simple-web-native.json
new file mode 100644
index 00000000..f4b8ec2c
--- /dev/null
+++ b/adapters/orbidder/orbiddertest/exemplary/simple-web-native.json
@@ -0,0 +1,102 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "page": "test.orbidder.de"
+    },
+    "user": {
+      "buyeruid": "test-user"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "native": {
+          "ver":"1.2",
+          "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]"
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "orbidder-test",
+            "placementId": "test-placement",
+            "bidfloor": 0.1
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://orbidder-test",
+        "body": {
+          "id": "test-request-id",
+          "site": {
+            "page": "test.orbidder.de"
+          },
+          "user": {
+            "buyeruid": "test-user"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "native": {
+                "ver":"1.2",
+                "request":"\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140},{\"id\":1,\"img\":{\"h\":250,\"hmin\":0,\"type\":3,\"w\":300,\"wmin\":0}}]"
+              },
+              "bidfloorcur": "EUR",
+              "ext": {
+                "bidder": {
+                  "accountId": "orbidder-test",
+                  "placementId": "test-placement",
+                  "bidfloor": 0.1
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "seat-id",
+              "bid": [
+                {
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                  "impid": "test-imp-id",
+                  "adid": "11110126",
+                  "price": 0.500000,
+                  "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"text\":\"Orbidder rocks\"}},{\"id\":1,\"required\":1,\"img\":{\"url\":\"https://prd.orbidder.de/common/logo.png\"}}],\"link\":{\"url\":\"https://www.otto.de/\",\"clicktrackers\":[\"https://prd.orbidder.de/click\"]},\"imptrackers\":[\"https://prd.orbidder.de/imp\"]}",
+                  "crid": "test-crid",
+                  "mtype": 4
+                }
+              ]
+            }
+          ],
+          "cur": "EUR"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "adid": "11110126",
+            "price": 0.5,
+            "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"text\":\"Orbidder rocks\"}},{\"id\":1,\"required\":1,\"img\":{\"url\":\"https://prd.orbidder.de/common/logo.png\"}}],\"link\":{\"url\":\"https://www.otto.de/\",\"clicktrackers\":[\"https://prd.orbidder.de/click\"]},\"imptrackers\":[\"https://prd.orbidder.de/imp\"]}",
+            "crid": "test-crid",
+            "mtype": 4
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json b/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json
index 1ea133ab..5b4a523f 100644
--- a/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json
+++ b/adapters/orbidder/orbiddertest/supplemental/valid-and-invalid-imps.json
@@ -85,7 +85,8 @@
                   "adm": "some-test-ad",
                   "crid": "test-crid",
                   "h": 250,
-                  "w": 300
+                  "w": 300,
+                  "mtype": 1
                 }
               ]
             }
@@ -108,7 +109,8 @@
             "adm": "some-test-ad",
             "crid": "test-crid",
             "w": 300,
-            "h": 250
+            "h": 250,
+            "mtype": 1
           },
           "type": "banner"
         }
diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json b/adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json
new file mode 100644
index 00000000..95bfb1e4
--- /dev/null
+++ b/adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json
@@ -0,0 +1,123 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "bidfloor": 1.00,
+          "bidfloorcur": "EUR",
+          "ext": {
+            "bidder": {
+              "placementId": "12345"
+            }
+          }
+        }
+      ],
+      "ext": {
+        "prebid": {
+          "currency": {
+            "rates": {
+              "EUR": {
+                "USD": 0.05
+              }
+            },
+            "usepbsrates": false
+          }
+        }
+      }
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://localhost/prebid_server",
+          "body": {
+            "id": "test-request-id",
+            "cur": ["USD"],
+            "imp": [
+              {
+                "banner": {
+                  "format": [
+                    {
+                      "h": 250,
+                      "w": 300
+                    }
+                  ]
+                },
+                "bidfloor": 0.05,
+                "bidfloorcur": "USD",
+                "ext": {
+                  "bidder": {
+                    "placementId": "12345"
+                  }
+                },
+                "id": "test-imp-id"
+              }
+            ],
+            "ext": {
+              "prebid": {
+                "currency": {
+                  "rates": {
+                    "EUR": {
+                      "USD": 0.05
+                    }
+                  },
+                  "usepbsrates": false
+                }
+              }
+             }
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "test-response-id",
+            "cur": "USD",
+            "seatbid": [{
+              "seat": "rtbhouse",
+              "bid": [{
+                "id": "randomid",
+                "impid": "test-imp-id",
+                "price": 300,
+                "adid": "12345678",
+                "adm": "some-test-ad",
+                "cid": "987",
+                "crid": "12345678",
+                "h": 250,
+                "w": 300
+                }
+              ]
+            }]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "randomid",
+              "impid": "test-imp-id",
+              "price": 300,
+              "adid": "12345678",
+              "adm": "some-test-ad",
+              "cid": "987",
+              "crid": "12345678",
+              "h": 250,
+              "w": 300
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json b/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json
index 3a636450..2a8c4681 100644
--- a/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json
+++ b/adapters/rtbhouse/rtbhousetest/exemplary/simple-banner.json
@@ -23,6 +23,7 @@
             "uri": "http://localhost/prebid_server",
             "body": {
                 "id": "test-request-id",
+                "cur": ["USD"],
                 "site": {
                     "page": "https://good.site/url"
                 },
diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json b/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json
index f84f5555..e0861fb0 100644
--- a/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json
+++ b/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json
@@ -26,6 +26,7 @@
         "uri": "http://localhost/prebid_server",
         "body": {
           "id": "test-request-id",
+          "cur": ["USD"],
           "imp": [
             {
               "id": "test-imp-id",
diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/currency-missing-conversion.json b/adapters/rtbhouse/rtbhousetest/supplemental/currency-missing-conversion.json
new file mode 100644
index 00000000..d7d65e43
--- /dev/null
+++ b/adapters/rtbhouse/rtbhousetest/supplemental/currency-missing-conversion.json
@@ -0,0 +1,43 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "banner-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "bidfloor": 1.00,
+          "bidfloorcur": "JPY",
+          "ext": {
+            "bidder": {
+              "placement": "12345"
+            }
+          }
+        }
+      ],
+      "ext": {
+        "prebid": {
+          "currency": {
+              "rates": {
+              "MXN": {
+                  "USD": 0.05
+              }
+              },
+              "usepbsrates": false
+          }
+        }
+      }
+    },
+    "expectedMakeRequestsErrors": [
+      {
+        "value": "Currency conversion rate not found: 'JPY' => 'USD'",
+        "comparison": "literal"
+      }
+    ]
+  }
diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/status_204.json b/adapters/rtbhouse/rtbhousetest/supplemental/status_204.json
index 0702c103..bb7754ec 100644
--- a/adapters/rtbhouse/rtbhousetest/supplemental/status_204.json
+++ b/adapters/rtbhouse/rtbhousetest/supplemental/status_204.json
@@ -26,6 +26,7 @@
         "uri": "http://localhost/prebid_server",
         "body": {
           "id": "test-request-id",
+          "cur": ["USD"],
           "imp": [
             {
               "id": "test-imp-id",
diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/status_400.json b/adapters/rtbhouse/rtbhousetest/supplemental/status_400.json
index 65d21406..0b527932 100644
--- a/adapters/rtbhouse/rtbhousetest/supplemental/status_400.json
+++ b/adapters/rtbhouse/rtbhousetest/supplemental/status_400.json
@@ -26,6 +26,7 @@
         "uri": "http://localhost/prebid_server",
         "body": {
           "id": "test-request-id",
+          "cur": ["USD"],
           "imp": [
             {
               "id": "test-imp-id",
diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/status_418.json b/adapters/rtbhouse/rtbhousetest/supplemental/status_418.json
index 4c5dd576..6ff3860e 100644
--- a/adapters/rtbhouse/rtbhousetest/supplemental/status_418.json
+++ b/adapters/rtbhouse/rtbhousetest/supplemental/status_418.json
@@ -26,6 +26,7 @@
         "uri": "http://localhost/prebid_server",
         "body": {
           "id": "test-request-id",
+          "cur": ["USD"],
           "imp": [
             {
               "id": "test-imp-id",
diff --git a/adapters/vox/params_test.go b/adapters/vox/params_test.go
new file mode 100644
index 00000000..be148d3b
--- /dev/null
+++ b/adapters/vox/params_test.go
@@ -0,0 +1,56 @@
+package vox
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderVox, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderVox, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"placementId": "64be6fe6685a271d37e900d2"}`,
+	`{"placementId": "Any String Basically"}`,
+	`{"placementId":""}`,
+	`{"placementId":"id", "imageUrl":"http://site.com/img1.png"}`,
+	`{"placementId":"id", "imageUrl":"http://site.com/img1.png", "displaySizes":["123x90", "1x1", "987x1111"]}`,
+}
+
+var invalidParams = []string{
+	`{"placementId": 42}`,
+	`{"placementId": null}`,
+	`{"placementId": 3.1415}`,
+	`{"placementId": true}`,
+	`{"placementId": false}`,
+	`{"placementId":"id", "imageUrl": null}`,
+	`{"placementId":"id", "imageUrl": true}`,
+	`{"placementId":"id", "imageUrl": []}`,
+	`{"placementId":"id", "imageUrl": "http://some.url", "displaySizes": null}`,
+	`{"placementId":"id", "imageUrl": "http://some.url", "displaySizes": {}}`,
+	`{"placementId":"id", "imageUrl": "http://some.url", "displaySizes": "String"}`,
+}
diff --git a/adapters/vox/vox_test.go b/adapters/vox/vox_test.go
new file mode 100644
index 00000000..95d11a8a
--- /dev/null
+++ b/adapters/vox/vox_test.go
@@ -0,0 +1,21 @@
+package vox
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderVox, config.Adapter{
+		Endpoint: "http://somecoolurlfor.vox"},
+		config.Server{ExternalUrl: "http://somecoolurlfor.vox", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error: %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "voxtest", bidder)
+}
diff --git a/adapters/vox/voxtest/exemplary/banner.json b/adapters/vox/voxtest/exemplary/banner.json
new file mode 100644
index 00000000..898fcea4
--- /dev/null
+++ b/adapters/vox/voxtest/exemplary/banner.json
@@ -0,0 +1,82 @@
+{
+  "mockBidRequest": {
+    "id": "cf4b0abb-7ccc-4057-9914-c85f467260c6",
+    "site": {
+      "page": "http://some.site/url/page?id=338"
+    },
+    "cur": [ "USD" ],
+    "imp": [{
+        "id": "8a7510f9-0ca1-44c4-a8c6-1ce639b5eef9",
+        "banner": { "w": 100, "h": 100 },
+        "bidfloorcur": "USD",
+        "ext": {
+          "bidder": {
+            "placementId":"64b939146d66df22ccae95c5"
+          }
+        }
+      }]
+  },
+
+  "httpCalls": [{
+      "expectedRequest": {
+        "uri": "http://somecoolurlfor.vox",
+        "headers": {},
+        "body": {
+          "id": "cf4b0abb-7ccc-4057-9914-c85f467260c6",
+          "site": {
+            "page": "http://some.site/url/page?id=338"
+          },
+          "imp": [
+            {
+              "id": "8a7510f9-0ca1-44c4-a8c6-1ce639b5eef9",
+              "banner": {
+                "w": 100,
+                "h": 100
+              },
+              "bidfloorcur": "USD",
+              "ext": {
+                "bidder": {
+                  "placementId": "64b939146d66df22ccae95c5"
+                }
+              }
+            }
+          ],
+          "cur": [ "USD" ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "headers": {},
+        "body": {
+          "id": "cf4b0abb-7ccc-4057-9914-c85f467260c6",
+          "cur": "USD",
+          "seatbid": [{
+            "bid": [{
+              "id": "e6a143f3-5176-4607-b09e-0e67e358b0b6",
+              "impid": "8a7510f9-0ca1-44c4-a8c6-1ce639b5eef9",
+              "price": 3.1415,
+              "adm": "<html><h1>Hi, there</h1></html>",
+              "w": 50,
+              "h": 50,
+              "mtype": 1
+            }]
+           }]
+        }}
+    }],
+
+    "expectedBidResponses": [{
+      "bids": [{
+        "currency": "USD",
+        "bid": {
+          "id": "e6a143f3-5176-4607-b09e-0e67e358b0b6",
+          "impid": "8a7510f9-0ca1-44c4-a8c6-1ce639b5eef9",
+          "price": 3.1415,
+          "adm": "<html><h1>Hi, there</h1></html>",
+          "w": 50,
+          "h": 50,
+          "mtype": 1
+        },
+        "type": "banner"
+      }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/vox/voxtest/exemplary/video.json b/adapters/vox/voxtest/exemplary/video.json
new file mode 100644
index 00000000..37464da1
--- /dev/null
+++ b/adapters/vox/voxtest/exemplary/video.json
@@ -0,0 +1,87 @@
+{
+  "mockBidRequest": {
+    "id": "9a4bdb67-43ef-488f-937d-cd01e9dce43d",
+    "site": {
+      "page": "http://blog.com/article/1"
+    },
+    "cur": ["PLN"],
+    "imp": [{
+        "id": "d190d6f3-5264-4df5-91b2-8a9c72cbeb6a",
+        "bidfloorcur": "PLN",
+        "video": {
+          "mimes": ["video/mp4"],
+          "minduration": 5,
+          "maxduration": 15,
+          "w": 1280,
+          "h": 720
+        },
+        "ext": {
+          "bidder": {
+            "placementId":"64b9486efecad2330718e233"
+          }
+        }
+      }]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "http://somecoolurlfor.vox",
+      "header": {},
+      "body": {
+        "id": "9a4bdb67-43ef-488f-937d-cd01e9dce43d",
+        "site": {
+          "page": "http://blog.com/article/1"
+        },
+        "cur": [ "PLN" ],
+        "imp": [{
+            "id": "d190d6f3-5264-4df5-91b2-8a9c72cbeb6a",
+            "bidfloorcur": "PLN",
+            "video": {
+              "mimes": ["video/mp4"],
+              "minduration": 5,
+              "maxduration": 15,
+              "w": 1280,
+              "h": 720
+            },
+            "ext": {
+              "bidder": {
+                "placementId":"64b9486efecad2330718e233"
+              }
+            }
+          }]
+      }
+    },
+
+    "mockResponse": {
+      "status": 200,
+      "headers": {},
+      "body": {
+        "id":"d64fdeae-cb1c-4322-8fb6-18f5ec49bb76",
+        "cur": "PLN",
+        "seatbid": [{
+          "bid": [{
+            "id": "05349123-29e2-4be0-b662-48914f75ebe1",
+            "impid": "d190d6f3-5264-4df5-91b2-8a9c72cbeb6a",
+            "price": 2.149,
+            "adm": "<VAST>...</VAST>",
+            "mtype": 2
+          }]
+        }]
+      }
+    }
+  }],
+
+  "expectedBidResponses": [{
+    "bids": [{
+      "currency": "PLN",
+      "bid": {
+        "id": "05349123-29e2-4be0-b662-48914f75ebe1",
+        "impid": "d190d6f3-5264-4df5-91b2-8a9c72cbeb6a",
+        "price": 2.149,
+        "adm": "<VAST>...</VAST>",
+        "mtype": 2
+      },
+      "type": "video"
+    }]
+  }]
+}
\ No newline at end of file
diff --git a/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json b/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json
new file mode 100644
index 00000000..0a49b85d
--- /dev/null
+++ b/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json
@@ -0,0 +1,45 @@
+{
+  "mockBidRequest": {
+    "id": "Some Id (445455)",
+    "cur": ["AUD"],
+    "imp": [{
+      "id": "Impression id #1",
+      "banner": {},
+      "bidfloorcur": "AUD",
+      "ext": {
+        "bidder": {
+          "placementId": "64b94ad4ed136806629dd51c"
+        }
+      }
+    }]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "http://somecoolurlfor.vox",
+      "headers": {},
+      "body": {
+        "id": "Some Id (445455)",
+        "cur": ["AUD"],
+        "imp": [{
+          "id": "Impression id #1",
+          "banner": {},
+          "bidfloorcur": "AUD",
+          "ext": {
+            "bidder": {
+              "placementId": "64b94ad4ed136806629dd51c"
+            }
+          }
+        }]
+      }
+    },
+
+    "mockResponse": {
+      "status": 204,
+      "headers": {},
+      "body": {}
+    }
+  }],
+
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/vox/voxtest/supplemental/response_500_to_error.json b/adapters/vox/voxtest/supplemental/response_500_to_error.json
new file mode 100644
index 00000000..f245ef45
--- /dev/null
+++ b/adapters/vox/voxtest/supplemental/response_500_to_error.json
@@ -0,0 +1,56 @@
+{
+  "mockBidRequest": {
+    "id": "It is hard to make up ids.",
+    "site": {
+      "page": "http://catsanddogs.com/article/887"
+    },
+    "cur": ["USD"],
+    "imp": [{
+      "id": "Impression id #4",
+      "bidfloorcur": "USD",
+      "video": { "w": 1280, "h": 720, "mimes": ["video/mp4"] },
+      "ext": {
+        "bidder": {
+          "placementId": "64b94ca2b5eb3605e0a0e3be"
+        }
+      }
+    }]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "http://somecoolurlfor.vox",
+      "heades": {},
+      "body": {
+        "id": "It is hard to make up ids.",
+        "site": {
+          "page": "http://catsanddogs.com/article/887"
+        },
+        "cur": ["USD"],
+        "imp": [{
+          "id": "Impression id #4",
+          "bidfloorcur": "USD",
+          "video": { "w": 1280, "h": 720, "mimes": ["video/mp4"] },
+          "ext": {
+            "bidder": {
+              "placementId": "64b94ca2b5eb3605e0a0e3be"
+            }
+          }
+        }]
+      }
+    },
+
+    "mockResponse": {
+      "status": 500,
+      "headers": {},
+      "body": {}
+    }
+  }],
+
+  "expectedBidResponses": [],
+
+  "expectedMakeBidsErrors": [{
+    "value": "Unexpected status code: 500",
+    "comparison": "regex"
+  }]
+}
\ No newline at end of file
diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go
index 27daa9c7..c0eb74ab 100644
--- a/bidadjustment/apply_test.go
+++ b/bidadjustment/apply_test.go
@@ -223,12 +223,12 @@ type mockConversions struct {
 	mock.Mock
 }
 
-func (m mockConversions) GetRate(from string, to string) (float64, error) {
+func (m *mockConversions) GetRate(from string, to string) (float64, error) {
 	args := m.Called(from, to)
 	return args.Get(0).(float64), args.Error(1)
 }
 
-func (m mockConversions) GetRates() *map[string]map[string]float64 {
+func (m *mockConversions) GetRates() *map[string]map[string]float64 {
 	args := m.Called()
 	return args.Get(0).(*map[string]map[string]float64)
 }
diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go
index 6005178c..f83d942c 100644
--- a/config/bidderinfo_test.go
+++ b/config/bidderinfo_test.go
@@ -244,6 +244,22 @@ func TestBidderInfoValidationPositive(t *testing.T) {
 				},
 			},
 		},
+		"bidderC": BidderInfo{
+			Endpoint: "http://bidderB.com/openrtb2",
+			Maintainer: &MaintainerInfo{
+				Email: "maintainer@bidderA.com",
+			},
+			Capabilities: &CapabilitiesInfo{
+				Site: &PlatformInfo{
+					MediaTypes: []openrtb_ext.BidType{
+						openrtb_ext.BidTypeVideo,
+						openrtb_ext.BidTypeNative,
+						openrtb_ext.BidTypeBanner,
+					},
+				},
+			},
+			AliasOf: "bidderB",
+		},
 	}
 	errs := bidderInfos.validate(make([]error, 0))
 	assert.Len(t, errs, 0, "All bidder infos should be correct")
@@ -467,6 +483,43 @@ func TestBidderInfoValidationNegative(t *testing.T) {
 				errors.New("syncer could not be created, invalid supported endpoint: incorrect"),
 			},
 		},
+		{
+			"Invalid aliases",
+			BidderInfos{
+				"bidderA": BidderInfo{
+					Endpoint: "http://bidderA.com/openrtb2",
+					Maintainer: &MaintainerInfo{
+						Email: "maintainer@bidderA.com",
+					},
+					Capabilities: &CapabilitiesInfo{
+						Site: &PlatformInfo{
+							MediaTypes: []openrtb_ext.BidType{
+								openrtb_ext.BidTypeVideo,
+							},
+						},
+					},
+					AliasOf: "bidderB",
+				},
+				"bidderB": BidderInfo{
+					Endpoint: "http://bidderA.com/openrtb2",
+					Maintainer: &MaintainerInfo{
+						Email: "maintainer@bidderA.com",
+					},
+					Capabilities: &CapabilitiesInfo{
+						Site: &PlatformInfo{
+							MediaTypes: []openrtb_ext.BidType{
+								openrtb_ext.BidTypeVideo,
+							},
+						},
+					},
+					AliasOf: "bidderC",
+				},
+			},
+			[]error{
+				errors.New("bidder: bidderB cannot be an alias of an alias: bidderA"),
+				errors.New("bidder: bidderC not found for an alias: bidderB"),
+			},
+		},
 		{
 			"Two bidders, one with incorrect url",
 			BidderInfos{
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 27f84aee..87033f71 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -88,7 +88,7 @@ func TestGoodAmpRequests(t *testing.T) {
 				continue
 			}
 
-			test.storedRequest = map[string]json.RawMessage{tagID: test.BidRequest}
+			test.StoredRequest = map[string]json.RawMessage{tagID: test.BidRequest}
 			test.endpointType = AMP_ENDPOINT
 
 			cfg := &config.Configuration{
@@ -165,7 +165,7 @@ func TestAccountErrors(t *testing.T) {
 		if !assert.NoError(t, json.Unmarshal(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", tt.filename, err) {
 			continue
 		}
-		test.storedRequest = map[string]json.RawMessage{tt.storedReqID: test.BidRequest}
+		test.StoredRequest = map[string]json.RawMessage{tt.storedReqID: test.BidRequest}
 		test.endpointType = AMP_ENDPOINT
 
 		cfg := &config.Configuration{
@@ -216,6 +216,7 @@ func TestAMPPageInfo(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 	request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&curl=%s", url.QueryEscape(page)), nil)
 	recorder := httptest.NewRecorder()
@@ -319,6 +320,7 @@ func TestGDPRConsent(t *testing.T) {
 			openrtb_ext.BuildBidderMap(),
 			empty_fetcher.EmptyFetcher{},
 			hooks.EmptyPlanBuilder{},
+			nil,
 		)
 
 		// Invoke Endpoint
@@ -742,6 +744,7 @@ func TestCCPAConsent(t *testing.T) {
 			openrtb_ext.BuildBidderMap(),
 			empty_fetcher.EmptyFetcher{},
 			hooks.EmptyPlanBuilder{},
+			nil,
 		)
 
 		// Invoke Endpoint
@@ -855,6 +858,7 @@ func TestConsentWarnings(t *testing.T) {
 			openrtb_ext.BuildBidderMap(),
 			empty_fetcher.EmptyFetcher{},
 			hooks.EmptyPlanBuilder{},
+			nil,
 		)
 
 		// Invoke Endpoint
@@ -953,6 +957,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) {
 			openrtb_ext.BuildBidderMap(),
 			empty_fetcher.EmptyFetcher{},
 			hooks.EmptyPlanBuilder{},
+			nil,
 		)
 
 		// Invoke Endpoint
@@ -1007,6 +1012,7 @@ func TestAMPSiteExt(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 	request, err := http.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil)
 	if !assert.NoError(t, err) {
@@ -1049,6 +1055,7 @@ func TestAmpBadRequests(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 	for requestID := range badRequests {
 		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s", requestID), nil)
@@ -1082,6 +1089,7 @@ func TestAmpDebug(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 
 	for requestID := range requests {
@@ -1217,6 +1225,7 @@ func TestQueryParamOverrides(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 
 	requestID := "1"
@@ -1374,6 +1383,7 @@ func (s formatOverrideSpec) execute(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 
 	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)
@@ -1914,6 +1924,7 @@ func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMe
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 	return &actualAmpObject, endpoint
 }
@@ -1966,6 +1977,7 @@ func TestAmpAuctionResponseHeaders(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 
 	for _, test := range testCases {
@@ -2001,6 +2013,7 @@ func TestRequestWithTargeting(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 	url, err := url.Parse("/openrtb2/auction/amp")
 	assert.NoError(t, err, "unexpected error received while parsing url")
@@ -2190,7 +2203,7 @@ func TestValidAmpResponseWhenRequestRejected(t *testing.T) {
 			query := request.URL.Query()
 			tagID := query.Get("tag_id")
 
-			test.storedRequest = map[string]json.RawMessage{tagID: test.BidRequest}
+			test.StoredRequest = map[string]json.RawMessage{tagID: test.BidRequest}
 			test.planBuilder = tc.planBuilder
 			test.endpointType = AMP_ENDPOINT
 
diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go
index 9ad3c768..a3c19ee0 100644
--- a/endpoints/openrtb2/auction_benchmark_test.go
+++ b/endpoints/openrtb2/auction_benchmark_test.go
@@ -111,6 +111,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
 		nil,
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 
 	b.ResetTimer()
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index c6604fcf..4be48811 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -454,7 +454,9 @@ func TestExplicitUserId(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{})
+		hooks.EmptyPlanBuilder{},
+		nil,
+	)
 
 	endpoint(httptest.NewRecorder(), request, nil)
 
@@ -510,7 +512,9 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) {
 		aliasJSON,
 		bidderMap,
 		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{})
+		hooks.EmptyPlanBuilder{},
+		nil,
+	)
 
 	request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(testBidRequest))
 	recorder := httptest.NewRecorder()
@@ -562,7 +566,9 @@ func TestNilExchange(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{})
+		hooks.EmptyPlanBuilder{},
+		nil,
+	)
 
 	if err == nil {
 		t.Errorf("NewEndpoint should return an error when given a nil Exchange.")
@@ -586,7 +592,9 @@ func TestNilValidator(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{})
+		hooks.EmptyPlanBuilder{},
+		nil,
+	)
 
 	if err == nil {
 		t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.")
@@ -610,7 +618,9 @@ func TestExchangeError(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{})
+		hooks.EmptyPlanBuilder{},
+		nil,
+	)
 
 	request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
 	recorder := httptest.NewRecorder()
@@ -735,7 +745,9 @@ func TestImplicitIPsEndToEnd(t *testing.T) {
 			[]byte{},
 			openrtb_ext.BuildBidderMap(),
 			empty_fetcher.EmptyFetcher{},
-			hooks.EmptyPlanBuilder{})
+			hooks.EmptyPlanBuilder{},
+			nil,
+		)
 
 		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
 		httpReq.Header.Set("X-Forwarded-For", test.xForwardedForHeader)
@@ -933,7 +945,9 @@ func TestImplicitDNTEndToEnd(t *testing.T) {
 			[]byte{},
 			openrtb_ext.BuildBidderMap(),
 			empty_fetcher.EmptyFetcher{},
-			hooks.EmptyPlanBuilder{})
+			hooks.EmptyPlanBuilder{},
+			nil,
+		)
 
 		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
 		httpReq.Header.Set("DNT", test.dntHeader)
@@ -1144,6 +1158,7 @@ func TestStoredRequests(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	testStoreVideoAttr := []bool{true, true, false, false, false}
@@ -1517,6 +1532,7 @@ func TestValidateRequest(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	testCases := []struct {
@@ -2235,6 +2251,7 @@ func TestSetIntegrationType(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	testCases := []struct {
@@ -2300,6 +2317,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	req := &openrtb2.BidRequest{}
@@ -2403,6 +2421,7 @@ func TestOversizedRequest(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -2441,6 +2460,7 @@ func TestRequestSizeEdgeCase(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -2473,6 +2493,7 @@ func TestNoEncoding(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 	request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
 	recorder := httptest.NewRecorder()
@@ -2557,6 +2578,7 @@ func TestContentType(t *testing.T) {
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	)
 	request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
 	recorder := httptest.NewRecorder()
@@ -2777,6 +2799,7 @@ func TestValidateImpExt(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	for _, group := range testGroups {
@@ -2829,6 +2852,7 @@ func TestCurrencyTrunc(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	ui := int64(1)
@@ -2876,6 +2900,7 @@ func TestCCPAInvalid(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	ui := int64(1)
@@ -2927,6 +2952,7 @@ func TestNoSaleInvalid(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	ui := int64(1)
@@ -2981,6 +3007,7 @@ func TestValidateSourceTID(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	ui := int64(1)
@@ -3025,6 +3052,7 @@ func TestSChainInvalid(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	ui := int64(1)
@@ -3520,6 +3548,7 @@ func TestEidPermissionsInvalid(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	ui := int64(1)
@@ -3768,7 +3797,9 @@ func TestIOS14EndToEnd(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{})
+		hooks.EmptyPlanBuilder{},
+		nil,
+	)
 
 	httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "app-ios140-no-ifa.json")))
 
@@ -3834,6 +3865,7 @@ func TestAuctionWarnings(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
@@ -3879,6 +3911,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
@@ -3958,6 +3991,7 @@ func TestParseGzipedRequest(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
@@ -4451,7 +4485,9 @@ func TestAuctionResponseHeaders(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BuildBidderMap(),
 		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{})
+		hooks.EmptyPlanBuilder{},
+		nil,
+	)
 
 	for _, test := range testCases {
 		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.requestBody))
@@ -4555,6 +4591,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 				hardcodedResponseIPValidator{response: true},
 				empty_fetcher.EmptyFetcher{},
 				hooks.EmptyPlanBuilder{},
+				nil,
 			}
 
 			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
@@ -4657,6 +4694,7 @@ func TestParseRequestStoredResponses(t *testing.T) {
 				hardcodedResponseIPValidator{response: true},
 				&mockStoredResponseFetcher{mockStoredResponses},
 				hooks.EmptyPlanBuilder{},
+				nil,
 			}
 
 			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
@@ -4744,6 +4782,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
 				hardcodedResponseIPValidator{response: true},
 				&mockStoredResponseFetcher{mockStoredBidResponses},
 				hooks.EmptyPlanBuilder{},
+				nil,
 			}
 
 			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
@@ -4780,6 +4819,7 @@ func TestValidateStoredResp(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 		&mockStoredResponseFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	testCases := []struct {
@@ -5592,6 +5632,7 @@ func TestParseRequestMultiBid(t *testing.T) {
 				hardcodedResponseIPValidator{response: true},
 				empty_fetcher.EmptyFetcher{},
 				hooks.EmptyPlanBuilder{},
+				nil,
 			}
 
 			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go
index afe88397..401fbf62 100644
--- a/endpoints/openrtb2/test_utils.go
+++ b/endpoints/openrtb2/test_utils.go
@@ -76,7 +76,7 @@ type testCase struct {
 	ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"`
 
 	// "/openrtb2/amp" endpoint JSON test info
-	storedRequest       map[string]json.RawMessage `json:"mockAmpStoredRequest"`
+	StoredRequest       map[string]json.RawMessage `json:"mockAmpStoredRequest"`
 	StoredResponse      map[string]json.RawMessage `json:"mockAmpStoredResponse"`
 	ExpectedAmpResponse json.RawMessage            `json:"expectedAmpResponse"`
 }
@@ -1265,8 +1265,8 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han
 	testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher)
 
 	var storedRequestFetcher stored_requests.Fetcher
-	if len(test.storedRequest) > 0 {
-		storedRequestFetcher = &mockAmpStoredReqFetcher{test.storedRequest}
+	if len(test.StoredRequest) > 0 {
+		storedRequestFetcher = &mockAmpStoredReqFetcher{test.StoredRequest}
 	} else {
 		storedRequestFetcher = &mockStoredReqFetcher{}
 	}
@@ -1290,7 +1290,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han
 		planBuilder = hooks.EmptyPlanBuilder{}
 	}
 
-	var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.PBSAnalyticsModule, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder) (httprouter.Handle, error)
+	var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.PBSAnalyticsModule, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error)
 
 	switch test.endpointType {
 	case AMP_ENDPOINT:
@@ -1313,6 +1313,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han
 		bidderMap,
 		storedResponseFetcher,
 		planBuilder,
+		nil,
 	)
 
 	return endpoint, testExchange.(*exchangeTestWrapper), mockBidServersArray, mockCurrencyRatesServer, err
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 496a2ac8..1f28efb8 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -1225,6 +1225,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 	return deps, metrics, mockModule
 }
@@ -1270,6 +1271,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 }
 
@@ -1293,6 +1295,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames)
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	return deps
@@ -1318,6 +1321,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps {
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
 		hooks.EmptyPlanBuilder{},
+		nil,
 	}
 
 	return edep
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index 2dd0fd04..3b818bdd 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -67,6 +67,13 @@ func TestBuildAdapters(t *testing.T) {
 				errors.New("unknown: unknown bidder"),
 			},
 		},
+		{
+			description: "Alias feature disabled",
+			bidderInfos: map[string]config.BidderInfo{"appNexus": {AliasOf: "rubicon"}},
+			expectedErrors: []error{
+				errors.New("This feature is currently under development"),
+			},
+		},
 	}
 
 	cfg := &config.Configuration{}
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 9d36808c..3eeb3e90 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -3063,3 +3063,79 @@ func TestGetBidType(t *testing.T) {
 		})
 	}
 }
+
+type mockBidderTmaxCtx struct {
+	startTime, deadline time.Time
+	ok                  bool
+}
+
+func (m *mockBidderTmaxCtx) Deadline() (deadline time.Time, _ bool) {
+	return m.deadline, m.ok
+}
+func (m *mockBidderTmaxCtx) RemainingDurationMS(deadline time.Time) int64 {
+	return deadline.Sub(m.startTime).Milliseconds()
+}
+
+func TestUpdateBidderTmax(t *testing.T) {
+	respStatus := 200
+	respBody := "{\"bid\":false}"
+	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	defer server.Close()
+
+	requestHeaders := http.Header{}
+	requestHeaders.Add("Content-Type", "application/json")
+
+	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	var requestTmax int64 = 700
+
+	bidderReq := BidderRequest{
+		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}, TMax: requestTmax},
+		BidderName: "test",
+	}
+	extraInfo := &adapters.ExtraRequestInfo{}
+
+	tests := []struct {
+		description     string
+		requestTmax     int64
+		tmaxAdjustments *TmaxAdjustmentsPreprocessed
+		assertFn        func(actualTmax int64) bool
+	}{
+		{
+			description:     "tmax-is-not-enabled",
+			requestTmax:     requestTmax,
+			tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: false},
+			assertFn: func(actualTmax int64) bool {
+				return requestTmax == actualTmax
+			},
+		},
+		{
+			description:     "updates-bidder-tmax",
+			requestTmax:     requestTmax,
+			tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 50, PBSResponsePreparationDuration: 50},
+			assertFn: func(actualTmax int64) bool {
+				return requestTmax > actualTmax
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.description, func(t *testing.T) {
+			bidderImpl := &goodSingleBidder{
+				httpRequest: &adapters.RequestData{
+					Method:  "POST",
+					Uri:     server.URL,
+					Body:    []byte("{\"key\":\"val\"}"),
+					Headers: http.Header{},
+				},
+				bidResponse: &adapters.BidderResponse{},
+			}
+
+			ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(50*time.Millisecond))
+			defer cancel()
+			bidReqOptions := bidRequestOptions{tmaxAdjustments: test.tmaxAdjustments}
+			bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false}, "")
+			_, _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
+			assert.Empty(t, errs)
+			assert.True(t, test.assertFn(bidderImpl.bidRequest.TMax))
+		})
+	}
+}
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 2a35ef57..8e7ba80b 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -379,7 +379,7 @@ func TestDebugBehaviour(t *testing.T) {
 		// Assert no HoldAuction error
 		assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err)
 		assert.NotNilf(t, outBidResponse.Ext, "%s. outBidResponse.Ext should not be nil \n", test.desc)
-
+		assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
 		actualExt := &openrtb_ext.ExtBidResponse{}
 		err = json.Unmarshal(outBidResponse.Ext, actualExt)
 		assert.NoErrorf(t, err, "%s. \"ext\" JSON field could not be unmarshaled. err: \"%v\" \n outBidResponse.Ext: \"%s\" \n", test.desc, err, outBidResponse.Ext)
@@ -540,6 +540,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
 		// Assert no HoldAuction err
 		assert.NoErrorf(t, err, "ex.HoldAuction returned an err")
 		assert.NotNilf(t, outBidResponse.Ext, "outBidResponse.Ext should not be nil")
+		assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
 
 		actualExt := &openrtb_ext.ExtBidResponse{}
 		err = json.Unmarshal(outBidResponse.Ext, actualExt)
@@ -715,6 +716,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) {
 
 		// Assertions
 		assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err)
+		assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
 
 		if test.expected.numBids > 0 {
 			// Assert out currency
@@ -1362,6 +1364,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 				continue
 			} else {
 				assert.NoErrorf(t, err, "%s: %s. HoldAuction error: %v \n", testGroup.groupDesc, test.desc, err)
+				assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
 			}
 
 			// Assert returned bid
@@ -5143,6 +5146,7 @@ func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) {
 		// Assertions
 		assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err)
 		assert.NotNil(t, outBidResponse)
+		assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
 
 		// So 2 seatBids are expected as,
 		// the default "" and "pubmatic" bids will be in one seat and the extra-bids "groupm"/"appnexus"/"ix" in another seat.
@@ -5569,6 +5573,7 @@ func TestModulesCanBeExecutedForMultipleBiddersSimultaneously(t *testing.T) {
 	_, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
 	// Assert no HoldAuction err
 	assert.NoErrorf(t, err, "ex.HoldAuction returned an err")
+	assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
 
 	// check stage outcomes
 	assert.Equal(t, len(exec.GetOutcomes()), len(e.adapterMap), "stage outcomes append operation failed")
diff --git a/exchange/tmax_adjustments_test.go b/exchange/tmax_adjustments_test.go
new file mode 100644
index 00000000..7e6a02ab
--- /dev/null
+++ b/exchange/tmax_adjustments_test.go
@@ -0,0 +1,108 @@
+package exchange
+
+import (
+	"testing"
+	"time"
+
+	"github.com/prebid/prebid-server/config"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetBidderTmax(t *testing.T) {
+	var (
+		requestTmaxMS               int64 = 700
+		bidderNetworkLatencyBuffer  uint  = 50
+		responsePreparationDuration uint  = 60
+	)
+	requestTmaxNS := requestTmaxMS * int64(time.Millisecond)
+	startTime := time.Date(2023, 5, 30, 1, 0, 0, 0, time.UTC)
+	deadline := time.Date(2023, 5, 30, 1, 0, 0, int(requestTmaxNS), time.UTC)
+	ctx := &mockBidderTmaxCtx{startTime: startTime, deadline: deadline, ok: true}
+	tests := []struct {
+		description     string
+		ctx             bidderTmaxContext
+		requestTmax     int64
+		expectedTmax    int64
+		tmaxAdjustments TmaxAdjustmentsPreprocessed
+	}{
+		{
+			description:     "returns-requestTmax-when-IsEnforced-is-false",
+			ctx:             ctx,
+			requestTmax:     requestTmaxMS,
+			tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: false},
+			expectedTmax:    requestTmaxMS,
+		},
+		{
+			description:     "returns-requestTmax-when-BidderResponseDurationMin-is-not-set",
+			ctx:             ctx,
+			requestTmax:     requestTmaxMS,
+			tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 0},
+			expectedTmax:    requestTmaxMS,
+		},
+		{
+			description:     "returns-requestTmax-when-BidderNetworkLatencyBuffer-and-PBSResponsePreparationDuration-is-not-set",
+			ctx:             ctx,
+			requestTmax:     requestTmaxMS,
+			tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 0},
+			expectedTmax:    requestTmaxMS,
+		},
+		{
+			description:     "returns-requestTmax-when-context-deadline-is-not-set",
+			ctx:             &mockBidderTmaxCtx{ok: false},
+			requestTmax:     requestTmaxMS,
+			tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 50, PBSResponsePreparationDuration: 60},
+			expectedTmax:    requestTmaxMS,
+		},
+		{
+			description:     "returns-remaing-duration-by-subtracting-BidderNetworkLatencyBuffer-and-PBSResponsePreparationDuration",
+			ctx:             ctx,
+			requestTmax:     requestTmaxMS,
+			tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: bidderNetworkLatencyBuffer, PBSResponsePreparationDuration: responsePreparationDuration},
+			expectedTmax:    ctx.RemainingDurationMS(deadline) - int64(bidderNetworkLatencyBuffer) - int64(responsePreparationDuration),
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.description, func(t *testing.T) {
+			assert.Equal(t, test.expectedTmax, getBidderTmax(test.ctx, test.requestTmax, test.tmaxAdjustments))
+		})
+	}
+}
+
+func TestProcessTMaxAdjustments(t *testing.T) {
+	tests := []struct {
+		description     string
+		expected        *TmaxAdjustmentsPreprocessed
+		tmaxAdjustments config.TmaxAdjustments
+	}{
+		{
+			description:     "returns-nil-when-tmax-is-not-enabled",
+			tmaxAdjustments: config.TmaxAdjustments{Enabled: false},
+			expected:        nil,
+		},
+		{
+			description:     "BidderResponseDurationMin-is-not-set",
+			tmaxAdjustments: config.TmaxAdjustments{Enabled: true, BidderResponseDurationMin: 0, BidderNetworkLatencyBuffer: 10, PBSResponsePreparationDuration: 20},
+			expected:        &TmaxAdjustmentsPreprocessed{IsEnforced: false, BidderResponseDurationMin: 0, BidderNetworkLatencyBuffer: 10, PBSResponsePreparationDuration: 20},
+		},
+		{
+			description:     "BidderNetworkLatencyBuffer-and-PBSResponsePreparationDuration-are-not-set",
+			tmaxAdjustments: config.TmaxAdjustments{Enabled: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 0},
+			expected:        &TmaxAdjustmentsPreprocessed{IsEnforced: false, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 0},
+		},
+		{
+			description:     "BidderNetworkLatencyBuffer-is-not-set",
+			tmaxAdjustments: config.TmaxAdjustments{Enabled: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 10},
+			expected:        &TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 0, PBSResponsePreparationDuration: 10},
+		},
+		{
+			description:     "PBSResponsePreparationDuration-is-not-set",
+			tmaxAdjustments: config.TmaxAdjustments{Enabled: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 10, PBSResponsePreparationDuration: 0},
+			expected:        &TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 10, PBSResponsePreparationDuration: 0},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.description, func(t *testing.T) {
+			assert.Equal(t, test.expected, ProcessTMaxAdjustments(test.tmaxAdjustments))
+		})
+	}
+}
diff --git a/gdpr/consent_parser_test.go b/gdpr/consent_parser_test.go
new file mode 100644
index 00000000..4708f689
--- /dev/null
+++ b/gdpr/consent_parser_test.go
@@ -0,0 +1,160 @@
+package gdpr
+
+import (
+	"errors"
+	"testing"
+	"time"
+
+	"github.com/prebid/go-gdpr/consentconstants"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParseConsent(t *testing.T) {
+	validTCF1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY"
+	validTCF2Consent := "CPuKGCPPuKGCPNEAAAENCZCAAAAAAAAAAAAAAAAAAAAA"
+
+	tests := []struct {
+		name                    string
+		consent                 string
+		expectedEncodingVersion uint8
+		expectedListVersion     uint16
+		expectedSpecVersion     uint16
+		expectedError           error
+	}{
+
+		{
+			name:                    "valid_consent_with_encoding_version_2",
+			consent:                 validTCF2Consent,
+			expectedEncodingVersion: 2,
+			expectedListVersion:     153,
+			expectedSpecVersion:     2,
+		},
+		{
+			name:    "invalid_consent_parsing_error",
+			consent: "",
+			expectedError: &ErrorMalformedConsent{
+				Consent: "",
+				Cause:   consentconstants.ErrEmptyDecodedConsent,
+			},
+		},
+		{
+			name:    "invalid_consent_version_validation_error",
+			consent: validTCF1Consent,
+			expectedError: &ErrorMalformedConsent{
+				Consent: validTCF1Consent,
+				Cause:   errors.New("invalid encoding format version: 1"),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			parsedConsent, err := parseConsent(tt.consent)
+
+			if tt.expectedError != nil {
+				assert.Equal(t, tt.expectedError, err)
+				assert.Nil(t, parsedConsent)
+			} else {
+				assert.NoError(t, err)
+				assert.NotNil(t, parsedConsent)
+				assert.Equal(t, uint8(2), parsedConsent.encodingVersion)
+				assert.Equal(t, uint16(153), parsedConsent.listVersion)
+				assert.Equal(t, uint16(2), parsedConsent.specVersion)
+				assert.Equal(t, tt.expectedEncodingVersion, parsedConsent.consentMeta.Version())
+				assert.Equal(t, tt.expectedListVersion, parsedConsent.consentMeta.VendorListVersion())
+			}
+		})
+	}
+}
+
+func TestValidateVersions(t *testing.T) {
+	tests := []struct {
+		name          string
+		version       uint8
+		expectedError error
+	}{
+		{
+			name:    "valid_consent_version=2",
+			version: 2,
+		},
+		{
+			name:          "invalid_consent_version<2",
+			version:       1,
+			expectedError: errors.New("invalid encoding format version: 1"),
+		},
+		{
+			name:          "invalid_consent_version>2",
+			version:       3,
+			expectedError: errors.New("invalid encoding format version: 3"),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			mcs := mockConsentString{
+				version: tt.version,
+			}
+			err := validateVersions(&mcs)
+			if tt.expectedError != nil {
+				assert.Equal(t, tt.expectedError, err)
+			} else {
+				assert.Nil(t, err)
+			}
+		})
+	}
+}
+
+func TestGetSpecVersion(t *testing.T) {
+	tests := []struct {
+		name                string
+		policyVersion       uint8
+		expectedSpecVersion uint16
+	}{
+		{
+			name:                "policy_version_0_gives_spec_version_2",
+			policyVersion:       0,
+			expectedSpecVersion: 2,
+		},
+		{
+			name:                "policy_version_3_gives_spec_version_2",
+			policyVersion:       3,
+			expectedSpecVersion: 2,
+		},
+		{
+			name:                "policy_version_4_gives_spec_version_3",
+			policyVersion:       4,
+			expectedSpecVersion: 3,
+		},
+		{
+			name:                "policy_version_5_gives_spec_version_3",
+			policyVersion:       5,
+			expectedSpecVersion: 3,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			specVersion := getSpecVersion(tt.policyVersion)
+			assert.Equal(t, tt.expectedSpecVersion, specVersion)
+		})
+	}
+}
+
+type mockConsentString struct {
+	version       uint8
+	policyVersion uint8
+}
+
+func (mcs *mockConsentString) Version() uint8                                  { return mcs.version }
+func (mcs *mockConsentString) Created() time.Time                              { return time.Time{} }
+func (mcs *mockConsentString) LastUpdated() time.Time                          { return time.Time{} }
+func (mcs *mockConsentString) CmpID() uint16                                   { return 0 }
+func (mcs *mockConsentString) CmpVersion() uint16                              { return 0 }
+func (mcs *mockConsentString) ConsentScreen() uint8                            { return 0 }
+func (mcs *mockConsentString) ConsentLanguage() string                         { return "" }
+func (mcs *mockConsentString) VendorListVersion() uint16                       { return 0 }
+func (mcs *mockConsentString) TCFPolicyVersion() uint8                         { return mcs.policyVersion }
+func (mcs *mockConsentString) MaxVendorID() uint16                             { return 0 }
+func (mcs *mockConsentString) PurposeAllowed(id consentconstants.Purpose) bool { return false }
+func (mcs *mockConsentString) VendorConsent(id uint16) bool                    { return false }
diff --git a/gdpr/full_enforcement_test.go b/gdpr/full_enforcement_test.go
index e8ccd1e4..61f6e8b8 100644
--- a/gdpr/full_enforcement_test.go
+++ b/gdpr/full_enforcement_test.go
@@ -898,6 +898,66 @@ func TestLegalBasisWithPubRestrictionRequireLI(t *testing.T) {
 	}
 }
 
+func TestLegalBasisWithoutVendor(t *testing.T) {
+	P1P2P3PurposeConsent := "CPfCRQAPfCRQAAAAAAENCgCAAOAAAAAAAAAAAAAAAAAA"
+	tests := []struct {
+		name       string
+		config     purposeConfig
+		wantResult bool
+	}{
+		{
+			name: "enforce_purpose_&_vendors_off",
+			config: purposeConfig{
+				EnforcePurpose: false,
+				EnforceVendors: false,
+			},
+			wantResult: true,
+		},
+		{
+			name: "enforce_purpose_on,_bidder_is_a_vendor_exception",
+			config: purposeConfig{
+				EnforcePurpose:     true,
+				EnforceVendors:     false,
+				VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
+			},
+			wantResult: true,
+		},
+		{
+			name: "enforce_purpose_on",
+			config: purposeConfig{
+				EnforcePurpose: true,
+				EnforceVendors: false,
+			},
+			wantResult: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// convert consent string to TCF2 object
+			parsedConsent, err := vendorconsent.ParseString(P1P2P3PurposeConsent)
+			if err != nil {
+				t.Fatalf("Failed to parse consent %s\n", P1P2P3PurposeConsent)
+			}
+			consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata)
+			if !ok {
+				t.Fatalf("Failed to convert consent %s\n", P1P2P3PurposeConsent)
+			}
+
+			vendorInfo := VendorInfo{
+				vendorID: 32,
+				vendor:   nil,
+			}
+
+			enforcer := FullEnforcement{cfg: tt.config}
+			enforcer.cfg.PurposeID = consentconstants.Purpose(3)
+
+			result := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{})
+			assert.Equal(t, tt.wantResult, result)
+		})
+	}
+}
+
 func getVendorList(t *testing.T) vendorlist.VendorList {
 	GVL := makeVendorList()
 
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index 4a65dbaf..835a580f 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -342,6 +342,109 @@ func TestAllowActivities(t *testing.T) {
 	}
 }
 
+func TestAllowActivitiesBidderWithoutGVLID(t *testing.T) {
+	bidderWithoutGVLID := openrtb_ext.BidderPangle
+	purpose2Consent := "CPuDXznPuDXznMOAAAENCZCAAEAAAAAAAAAAAAAAAAAA"
+	noPurposeConsent := "CPuDXznPuDXznMOAAAENCZCAAAAAAAAAAAAAAAAAAAAA"
+
+	tests := []struct {
+		name                    string
+		enforceAlgoID           config.TCF2EnforcementAlgo
+		vendorExceptions        map[openrtb_ext.BidderName]struct{}
+		basicEnforcementVendors map[string]struct{}
+		consent                 string
+		allowBidRequest         bool
+		passID                  bool
+	}{
+		{
+			name:          "full_enforcement_no_exceptions_user_consents_to_purpose_2",
+			enforceAlgoID: config.TCF2FullEnforcement,
+			consent:       purpose2Consent,
+		},
+		{
+			name:             "full_enforcement_vendor_exception_user_consents_to_purpose_2",
+			enforceAlgoID:    config.TCF2FullEnforcement,
+			vendorExceptions: map[openrtb_ext.BidderName]struct{}{bidderWithoutGVLID: {}},
+			consent:          purpose2Consent,
+			allowBidRequest:  true,
+			passID:           true,
+		},
+		{
+			name:    "basic_enforcement_no_exceptions_user_consents_to_purpose_2",
+			consent: purpose2Consent,
+		},
+		{
+			name:             "basic_enforcement_vendor_exception_user_consents_to_purpose_2",
+			vendorExceptions: map[openrtb_ext.BidderName]struct{}{bidderWithoutGVLID: {}},
+			consent:          purpose2Consent,
+			allowBidRequest:  true,
+			passID:           true,
+		},
+		{
+			name:                    "full_enforcement_soft_vendor_exception_user_consents_to_purpose_2", // allow bid request and pass ID
+			enforceAlgoID:           config.TCF2FullEnforcement,
+			basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}},
+			consent:                 purpose2Consent,
+			allowBidRequest:         true,
+			passID:                  true,
+		},
+		{
+			name:                    "basic_enforcement_soft_vendor_exception_user_consents_to_purpose_2", // allow bid request and pass ID
+			enforceAlgoID:           config.TCF2BasicEnforcement,
+			basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}},
+			consent:                 purpose2Consent,
+			allowBidRequest:         true,
+			passID:                  true,
+		},
+		{
+			name:                    "full_enforcement_soft_vendor_exception_user_consents_to_purpose_4",
+			enforceAlgoID:           config.TCF2FullEnforcement,
+			basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}},
+			consent:                 noPurposeConsent,
+			allowBidRequest:         false,
+			passID:                  false,
+		},
+		{
+			name:                    "basic_enforcement_soft_vendor_exception_user_consents_to_purpose_4",
+			enforceAlgoID:           config.TCF2BasicEnforcement,
+			basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}},
+			consent:                 noPurposeConsent,
+			allowBidRequest:         false,
+			passID:                  false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tcf2AggConfig := allPurposesEnabledTCF2Config()
+			tcf2AggConfig.AccountConfig.BasicEnforcementVendorsMap = tt.basicEnforcementVendors
+			tcf2AggConfig.HostConfig.Purpose2.VendorExceptionMap = tt.vendorExceptions
+			tcf2AggConfig.HostConfig.Purpose2.EnforceAlgoID = tt.enforceAlgoID
+			tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = &tcf2AggConfig.HostConfig.Purpose2
+
+			perms := permissionsImpl{
+				cfg:                   &tcf2AggConfig,
+				consent:               tt.consent,
+				gdprSignal:            SignalYes,
+				hostVendorID:          2,
+				nonStandardPublishers: map[string]struct{}{},
+				vendorIDs:             map[openrtb_ext.BidderName]uint16{},
+				fetchVendorList: listFetcher(map[uint16]map[uint16]vendorlist.VendorList{
+					2: {
+						153: parseVendorListDataV2(t, MarshalVendorList(vendorList{GVLSpecificationVersion: 2, VendorListVersion: 153, Vendors: map[string]*vendor{}})),
+					},
+				}),
+				purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig),
+			}
+
+			permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderWithoutGVLID, bidderWithoutGVLID)
+			assert.NoError(t, err)
+			assert.Equal(t, tt.allowBidRequest, permissions.AllowBidRequest)
+			assert.Equal(t, tt.passID, permissions.PassID)
+		})
+	}
+}
+
 func buildVendorList34() vendorList {
 	return vendorList{
 		VendorListVersion: 2,
@@ -1006,28 +1109,6 @@ func TestAllowActivitiesBidRequests(t *testing.T) {
 	}
 }
 
-func TestTCF1Consent(t *testing.T) {
-	bidderAllowedByConsent := openrtb_ext.BidderAppnexus
-	tcf1Consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA"
-
-	perms := permissionsImpl{
-		cfg: &tcf2Config{},
-		vendorIDs: map[openrtb_ext.BidderName]uint16{
-			openrtb_ext.BidderAppnexus: 2,
-		},
-		aliasGVLIDs: map[string]uint16{},
-		consent:     tcf1Consent,
-		gdprSignal:  SignalYes,
-	}
-
-	permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, bidderAllowedByConsent)
-
-	assert.Nil(t, err, "TCF1 consent - no error returned")
-	assert.Equal(t, true, permissions.AllowBidRequest, "TCF1 consent - bid request not allowed")
-	assert.Equal(t, true, permissions.PassGeo, "TCF1 consent - passing geo not allowed")
-	assert.Equal(t, false, permissions.PassID, "TCF1 consent - passing id not allowed")
-}
-
 func TestAllowActivitiesVendorException(t *testing.T) {
 	noPurposeOrVendorConsentAndPubRestrictsP2 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAACEAAgAgAA"
 	noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA"
@@ -1244,53 +1325,9 @@ func TestDefaultPermissions(t *testing.T) {
 	}
 }
 
-func TestGetSpecVersion(t *testing.T) {
-	tests := []struct {
-		name                string
-		policyVersion       uint8
-		expectedSpecVersion uint16
-		expectedError       bool
-	}{
-		{
-			name:                "policy_version_0_gives_spec_version_2",
-			policyVersion:       0,
-			expectedSpecVersion: 2,
-		},
-		{
-			name:                "policy_version_3_gives_spec_version_2",
-			policyVersion:       3,
-			expectedSpecVersion: 2,
-		},
-		{
-			name:                "policy_version_4_spec_version_3",
-			policyVersion:       4,
-			expectedSpecVersion: 3,
-		},
-		{
-			name:                "policy_version_5_error",
-			policyVersion:       5,
-			expectedSpecVersion: 0,
-			expectedError:       true,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			specVersion, err := getSpecVersion(tt.policyVersion)
-			assert.Equal(t, tt.expectedSpecVersion, specVersion)
-			if tt.expectedError {
-				assert.Error(t, err)
-			} else {
-				assert.Nil(t, err)
-			}
-		})
-	}
-}
-
 func TestVendorListSelection(t *testing.T) {
 	policyVersion3WithVendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABDAAIAAAAAAAAAAACEAAAAA"
 	policyVersion4WithVendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABEAAIAAAAAAAAAAACEAAAAA"
-	policyVersion5WithVendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABFAAIAAAAAAAAAAACEAAAAA"
 
 	specVersion2vendorListData := MarshalVendorList(vendorList{
 		GVLSpecificationVersion: 2,
@@ -1359,11 +1396,6 @@ func TestVendorListSelection(t *testing.T) {
 			consent:           policyVersion4WithVendor2AndPurpose1Consent,
 			expectedAllowSync: true,
 		},
-		{
-			name:        "consent_tcf_policy_version_5_causes_error",
-			consent:     policyVersion5WithVendor2AndPurpose1Consent,
-			expectedErr: true,
-		},
 	}
 
 	for _, tt := range tests {