Repository: prebid/prebid-server
Pull request #2083 (2021-11-15 12:38:42)

Adyoulike: Add an iframe sync endpoint


Patch:
diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go
new file mode 100644
index 00000000..5587cac7
--- /dev/null
+++ b/adapters/openrtb_util.go
@@ -0,0 +1,61 @@
+package adapters
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func ExtractAdapterReqBidderParams(bidRequest *openrtb2.BidRequest) (map[string]json.RawMessage, error) {
+	if bidRequest == nil {
+		return nil, errors.New("error bidRequest should not be nil")
+	}
+
+	reqExt := &openrtb_ext.ExtRequest{}
+	if len(bidRequest.Ext) > 0 {
+		err := json.Unmarshal(bidRequest.Ext, &reqExt)
+		if err != nil {
+			return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error())
+		}
+	}
+
+	if reqExt.Prebid.BidderParams == nil {
+		return nil, nil
+	}
+
+	var bidderParams map[string]json.RawMessage
+	err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams)
+	if err != nil {
+		return nil, err
+	}
+
+	return bidderParams, nil
+}
+
+func ExtractReqExtBidderParams(bidReq *openrtb2.BidRequest) (map[string]map[string]json.RawMessage, error) {
+	if bidReq == nil {
+		return nil, errors.New("error bidRequest should not be nil")
+	}
+
+	reqExt := &openrtb_ext.ExtRequest{}
+	if len(bidReq.Ext) > 0 {
+		err := json.Unmarshal(bidReq.Ext, &reqExt)
+		if err != nil {
+			return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error())
+		}
+	}
+
+	if reqExt.Prebid.BidderParams == nil {
+		return nil, nil
+	}
+
+	var bidderParams map[string]map[string]json.RawMessage
+	err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams)
+	if err != nil {
+		return nil, err
+	}
+
+	return bidderParams, nil
+}
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index 19024f4a..799d3364 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -26,6 +26,11 @@ type pubmaticBidExt struct {
 	VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"`
 }
 
+type pubmaticWrapperExt struct {
+	ProfileID int `json:"profile,omitempty"`
+	VersionID int `json:"version,omitempty"`
+}
+
 type pubmaticBidExtVideo struct {
 	Duration *int `json:"duration,omitempty"`
 }
@@ -56,16 +61,51 @@ const (
 func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 	errs := make([]error, 0, len(request.Imp))
 
-	wrapExt := ""
 	pubID := ""
+	var wrapperExt *pubmaticWrapperExt
+	extractWrapperExtFromImp := true
+	extractPubIDFromImp := true
+
+	wrapperExt, err := extractPubmaticWrapperExtFromRequest(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+	if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 {
+		extractWrapperExtFromImp = false
+	}
 
 	for i := 0; i < len(request.Imp); i++ {
-		err := parseImpressionObject(&request.Imp[i], &wrapExt, &pubID)
+		wrapperExtFromImp, pubIDFromImp, err := parseImpressionObject(&request.Imp[i], extractWrapperExtFromImp, extractPubIDFromImp)
+
 		// If the parsing is failed, remove imp and add the error.
 		if err != nil {
 			errs = append(errs, err)
 			request.Imp = append(request.Imp[:i], request.Imp[i+1:]...)
 			i--
+			continue
+		}
+
+		if extractWrapperExtFromImp {
+			if wrapperExtFromImp != nil {
+				if wrapperExt == nil {
+					wrapperExt = &pubmaticWrapperExt{}
+				}
+				if wrapperExt.ProfileID == 0 {
+					wrapperExt.ProfileID = wrapperExtFromImp.ProfileID
+				}
+				if wrapperExt.VersionID == 0 {
+					wrapperExt.VersionID = wrapperExtFromImp.VersionID
+				}
+
+				if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 {
+					extractWrapperExtFromImp = false
+				}
+			}
+		}
+
+		if extractPubIDFromImp && pubIDFromImp != "" {
+			pubID = pubIDFromImp
+			extractPubIDFromImp = false
 		}
 	}
 
@@ -74,9 +114,14 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad
 		return nil, errs
 	}
 
-	if wrapExt != "" {
-		rawExt := fmt.Sprintf("{\"wrapper\": %s}", wrapExt)
-		request.Ext = json.RawMessage(rawExt)
+	if wrapperExt != nil {
+		reqExt := make(map[string]interface{})
+		reqExt["wrapper"] = wrapperExt
+		rawExt, err := json.Marshal(reqExt)
+		if err != nil {
+			return nil, []error{err}
+		}
+		request.Ext = rawExt
 	}
 
 	if request.Site != nil {
@@ -179,10 +224,13 @@ func assignBannerWidthAndHeight(banner *openrtb2.Banner, w, h int64) *openrtb2.B
 }
 
 // parseImpressionObject parse the imp to get it ready to send to pubmatic
-func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) error {
+func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractPubIDFromImp bool) (*pubmaticWrapperExt, string, error) {
+	var wrapExt *pubmaticWrapperExt
+	var pubID string
+
 	// PubMatic supports banner and video impressions.
 	if imp.Banner == nil && imp.Video == nil {
-		return fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID)
+		return wrapExt, pubID, fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID)
 	}
 
 	if imp.Audio != nil {
@@ -191,36 +239,34 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er
 
 	var bidderExt ExtImpBidderPubmatic
 	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
-		return err
+		return wrapExt, pubID, err
 	}
 
 	var pubmaticExt openrtb_ext.ExtImpPubmatic
 	if err := json.Unmarshal(bidderExt.Bidder, &pubmaticExt); err != nil {
-		return err
+		return wrapExt, pubID, err
 	}
 
-	if *pubID == "" {
-		*pubID = strings.TrimSpace(pubmaticExt.PublisherId)
+	if extractPubIDFromImp {
+		pubID = strings.TrimSpace(pubmaticExt.PublisherId)
 	}
 
 	// Parse Wrapper Extension only once per request
-	if *wrapExt == "" && len(pubmaticExt.WrapExt) != 0 {
-		var wrapExtMap map[string]int
-		err := json.Unmarshal([]byte(pubmaticExt.WrapExt), &wrapExtMap)
+	if extractWrapperExtFromImp && len(pubmaticExt.WrapExt) != 0 {
+		err := json.Unmarshal([]byte(pubmaticExt.WrapExt), &wrapExt)
 		if err != nil {
-			return fmt.Errorf("Error in Wrapper Parameters = %v  for ImpID = %v WrapperExt = %v", err.Error(), imp.ID, string(pubmaticExt.WrapExt))
+			return wrapExt, pubID, fmt.Errorf("Error in Wrapper Parameters = %v  for ImpID = %v WrapperExt = %v", err.Error(), imp.ID, string(pubmaticExt.WrapExt))
 		}
-		*wrapExt = string(pubmaticExt.WrapExt)
 	}
 
 	if err := validateAdSlot(strings.TrimSpace(pubmaticExt.AdSlot), imp); err != nil {
-		return err
+		return wrapExt, pubID, err
 	}
 
 	if imp.Banner != nil {
 		bannerCopy, err := assignBannerSize(imp.Banner)
 		if err != nil {
-			return err
+			return wrapExt, pubID, err
 		}
 		imp.Banner = bannerCopy
 	}
@@ -253,7 +299,26 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er
 		}
 	}
 
-	return nil
+	return wrapExt, pubID, nil
+}
+
+// extractPubmaticWrapperExtFromRequest parse the imp to get it ready to send to pubmatic
+func extractPubmaticWrapperExtFromRequest(request *openrtb2.BidRequest) (*pubmaticWrapperExt, error) {
+	var wrpExt pubmaticWrapperExt
+	reqExtBidderParams, err := adapters.ExtractAdapterReqBidderParams(request)
+	if err != nil {
+		return nil, err
+	}
+
+	//get request ext bidder params
+	if wrapperObj, present := reqExtBidderParams["wrapper"]; present && len(wrapperObj) != 0 {
+		err = json.Unmarshal(wrapperObj, &wrpExt)
+		if err != nil {
+			return nil, err
+		}
+		return &wrpExt, nil
+	}
+	return nil, nil
 }
 
 func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) {
diff --git a/config/config.go b/config/config.go
index 5a20e257..83585146 100644
--- a/config/config.go
+++ b/config/config.go
@@ -24,6 +24,10 @@ type Configuration struct {
 	CacheClient HTTPClient `mapstructure:"http_client_cache"`
 	AdminPort   int        `mapstructure:"admin_port"`
 	EnableGzip  bool       `mapstructure:"enable_gzip"`
+	// GarbageCollectorThreshold allocates virtual memory (in bytes) which is not used by PBS but
+	// serves as a hack to trigger the garbage collector only when the heap reaches at least this size.
+	// More info: https://github.com/golang/go/issues/48409
+	GarbageCollectorThreshold int `mapstructure:"garbage_collector_threshold"`
 	// StatusResponse is the string which will be returned by the /status endpoint when things are OK.
 	// If empty, it will return a 204 with no content.
 	StatusResponse    string          `mapstructure:"status_response"`
@@ -634,6 +638,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("port", 8000)
 	v.SetDefault("admin_port", 6060)
 	v.SetDefault("enable_gzip", false)
+	v.SetDefault("garbage_collector_threshold", 0)
 	v.SetDefault("status_response", "")
 	v.SetDefault("auction_timeouts_ms.default", 0)
 	v.SetDefault("auction_timeouts_ms.max", 0)
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index ec9e4152..10a15b3f 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -23,6 +23,7 @@ import (
 	nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request"
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	accountService "github.com/prebid/prebid-server/account"
+	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
@@ -290,6 +291,11 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_
 		return
 	}
 
+	if err := mergeBidderParams(req); err != nil {
+		errs = []error{err}
+		return
+	}
+
 	// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
 	deps.setFieldsImplicitly(httpRequest, req.BidRequest)
 
@@ -323,6 +329,117 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio
 	return defaultTimeout
 }
 
+// mergeBidderParams merges bidder parameters passed at req.ext level with imp[].ext level.
+// Preference is given to parameters at imp[].ext level over req.ext level.
+// Parameters at req.ext level are propagated to adapters as is without any validation.
+func mergeBidderParams(req *openrtb_ext.RequestWrapper) error {
+	reqBidderParams, err := adapters.ExtractReqExtBidderParams(req.BidRequest)
+	if err != nil {
+		return err
+	}
+
+	impCpy := make([]openrtb2.Imp, 0, len(req.BidRequest.Imp))
+	for _, imp := range req.BidRequest.Imp {
+		updatedImp := imp
+
+		if len(imp.Ext) == 0 {
+			impCpy = append(impCpy, updatedImp)
+			continue
+		}
+
+		var impExt map[string]map[string]json.RawMessage
+		err := json.Unmarshal(imp.Ext, &impExt)
+		if err != nil {
+			return err
+		}
+
+		//merges bidder parameters passed at req.ext level with imp[].ext level.
+		err = addMissingReqExtParamsInImpExt(impExt, reqBidderParams)
+		if err != nil {
+			return err
+		}
+
+		//merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level.
+		err = addMissingReqExtParamsInImpExtPrebid(impExt, reqBidderParams)
+		if err != nil {
+			return err
+		}
+
+		iExt, err := json.Marshal(impExt)
+		if err != nil {
+			return fmt.Errorf("error marshalling imp[].ext : %s", err.Error())
+		}
+		updatedImp.Ext = iExt
+		impCpy = append(impCpy, updatedImp)
+	}
+
+	req.BidRequest.Imp = impCpy
+	return nil
+}
+
+// addMissingReqExtParamsInImpExtPrebid merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level.
+func addMissingReqExtParamsInImpExtPrebid(impExtBidder map[string]map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) error {
+	var bidderParams map[string]json.RawMessage
+	if impExtBidder["prebid"] != nil && impExtBidder["prebid"]["bidder"] != nil {
+		err := json.Unmarshal(impExtBidder["prebid"]["bidder"], &bidderParams)
+		if err != nil {
+			return err
+		}
+	}
+
+	if len(bidderParams) != 0 {
+		for bidder, bidderExt := range bidderParams {
+			if !isBidderToValidate(bidder) {
+				continue
+			}
+
+			var params map[string]json.RawMessage
+			err := json.Unmarshal(bidderExt, &params)
+
+			for key, value := range reqExtParams[bidder] {
+				if _, present := params[key]; !present {
+					params[key] = value
+				}
+			}
+
+			paramsJson, err := json.Marshal(params)
+			if err != nil {
+				return err
+			}
+			bidderParams[bidder] = paramsJson
+		}
+
+		bidderParamsJson, err := json.Marshal(bidderParams)
+		if err != nil {
+			return err
+		}
+		impExtBidder["prebid"]["bidder"] = bidderParamsJson
+	}
+
+	return nil
+}
+
+// addMissingReqExtParamsInImpExt merges bidder parameters passed at req.ext level with imp[].ext level.
+func addMissingReqExtParamsInImpExt(impExtBidder map[string]map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) error {
+	for bidder, bidderExt := range impExtBidder {
+		if !isBidderToValidate(bidder) {
+			continue
+		}
+
+		wasModified := false
+		for key, value := range reqExtParams[bidder] {
+			if _, present := bidderExt[key]; !present {
+				bidderExt[key] = value
+				wasModified = true
+			}
+		}
+		if wasModified {
+			impExtBidder[bidder] = bidderExt
+		}
+	}
+	return nil
+}
+
 func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []error {
 	errL := []error{}
 	if req.ID == "" {
@@ -1077,6 +1194,8 @@ func isBidderToValidate(bidder string) bool {
 		return false
 	case openrtb_ext.BidderReservedSKAdN:
 		return false
+	case openrtb_ext.BidderReservedBidder:
+		return false
 	default:
 		return true
 	}
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json
index 76dc1c2b..5baaa09d 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json
@@ -27,5 +27,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n"
+  "expectedErrorMessage": "Invalid request: error decoding Request.ext : request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n"
 }
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json
index d9ed59c8..b1e9b784 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json
@@ -20,5 +20,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.imp[0].ext.appnexus failed validation.\n(root): Invalid type. Expected: object, given: string\n"
+  "expectedErrorMessage": "Invalid request: json: cannot unmarshal string into Go value of type map[string]json.RawMessage"
 }
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json
new file mode 100644
index 00000000..274c2c70
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json
@@ -0,0 +1,101 @@
+{
+  "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 111111
+          },
+          "pubmatic": {
+            "wrapper": {
+              "profile": 1234
+            }
+          },
+          "prebid": {
+            "bidder": {}
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        },
+        "bidderparams": {
+          "appnexus": {
+            "placementId": 222222
+          },
+          "pubmatic": {
+            "publisherId": "5678",
+            "wrapper": {
+              "profile": 5678,
+              "version": 2
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedReqExt": {
+    "prebid": {
+      "targeting": {
+        "pricegranularity": "low"
+      },
+      "cache": {
+        "bids": {}
+      },
+      "bidderparams": {
+        "appnexus": {
+          "placementId": 222222
+        },
+        "pubmatic": {
+          "publisherId": "5678",
+          "wrapper": {
+            "profile": 5678,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedImpExt": {
+    "appnexus": {
+      "placementId": 111111
+    },
+    "pubmatic": {
+      "publisherId": "5678",
+      "wrapper": {
+        "profile": 1234
+      }
+    },
+    "prebid": {
+      "bidder": {}
+    }
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json
new file mode 100644
index 00000000..982f86cb
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json
@@ -0,0 +1,116 @@
+{
+  "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder for reserved keyword",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "bidder": {
+                "param1": 111111
+              },
+              "appnexus": {
+                "placementId": 111111
+              },
+              "pubmatic": {
+                "publisherId": "1234",
+                "wrapper": {
+                  "profile": 1234
+                }
+              }
+            }
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        },
+        "bidderparams": {
+          "bidder": {
+            "param2": 111111
+          },
+          "appnexus": {
+            "placementId": 222222
+          },
+          "pubmatic": {
+            "publisherId": "5678",
+            "wrapper": {
+              "profile": 5678,
+              "version": 2
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedReqExt": {
+    "prebid": {
+      "targeting": {
+        "pricegranularity": "low"
+      },
+      "cache": {
+        "bids": {}
+      },
+      "bidderparams": {
+        "bidder": {
+          "param2": 111111
+        },
+        "appnexus": {
+          "placementId": 222222
+        },
+        "pubmatic": {
+          "publisherId": "5678",
+          "wrapper": {
+            "profile": 5678,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedImpExt": {
+    "prebid": {
+      "bidder": {
+        "bidder": {
+          "param1": 111111
+        },
+        "appnexus": {
+          "placementId": 111111
+        },
+        "pubmatic": {
+          "publisherId": "1234",
+          "wrapper": {
+            "profile": 1234
+          }
+        }
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json
new file mode 100644
index 00000000..22026f56
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json
@@ -0,0 +1,104 @@
+{
+  "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {
+                "placementId": 111111
+              },
+              "pubmatic": {
+                "publisherId": "1234",
+                "wrapper": {
+                  "profile": 1234
+                }
+              }
+            }
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        },
+        "bidderparams": {
+          "appnexus": {
+            "placementId": 222222
+          },
+          "pubmatic": {
+            "publisherId": "5678",
+            "wrapper": {
+              "profile": 5678,
+              "version": 2
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedReqExt": {
+    "prebid": {
+      "targeting": {
+        "pricegranularity": "low"
+      },
+      "cache": {
+        "bids": {}
+      },
+      "bidderparams": {
+        "appnexus": {
+          "placementId": 222222
+        },
+        "pubmatic": {
+          "publisherId": "5678",
+          "wrapper": {
+            "profile": 5678,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedImpExt": {
+    "prebid": {
+      "bidder": {
+        "appnexus": {
+          "placementId": 111111
+        },
+        "pubmatic": {
+          "publisherId": "1234",
+          "wrapper": {
+            "profile": 1234
+          }
+        }
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json
new file mode 100644
index 00000000..9d4bcd0d
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json
@@ -0,0 +1,98 @@
+{
+  "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {},
+              "pubmatic": {}
+            }
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        },
+        "bidderparams": {
+          "appnexus": {
+            "placementId": 12883451
+          },
+          "pubmatic": {
+            "publisherId": "1234",
+            "wrapper": {
+              "profile": 1234,
+              "version": 2
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedReqExt": {
+    "prebid": {
+      "targeting": {
+        "pricegranularity": "low"
+      },
+      "cache": {
+        "bids": {}
+      },
+      "bidderparams": {
+        "appnexus": {
+          "placementId": 12883451
+        },
+        "pubmatic": {
+          "publisherId": "1234",
+          "wrapper": {
+            "profile": 1234,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedImpExt": {
+    "prebid": {
+      "bidder": {
+        "appnexus": {
+          "placementId": 12883451
+        },
+        "pubmatic": {
+          "publisherId": "1234",
+          "wrapper": {
+            "profile": 1234,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/exchange/utils.go b/exchange/utils.go
index 5a68ba45..23ffe5e5 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -11,6 +11,7 @@ import (
 	"github.com/prebid/go-gdpr/vendorconsent"
 
 	"github.com/buger/jsonparser"
+	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/firstpartydata"
 	"github.com/prebid/prebid-server/gdpr"
@@ -222,6 +223,11 @@ func getAuctionBidderRequests(req AuctionRequest,
 		return nil, []error{err}
 	}
 
+	bidderParamsInReqExt, err := adapters.ExtractReqExtBidderParams(req.BidRequest)
+	if err != nil {
+		return nil, []error{err}
+	}
+
 	var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain
 
 	// Quick extra wrapper until RequestWrapper makes its way into CleanRequests
@@ -232,21 +238,33 @@ func getAuctionBidderRequests(req AuctionRequest,
 		}
 	}
 
-	reqExt, err := getExtJson(req.BidRequest, requestExt)
-	if err != nil {
-		return nil, []error{err}
-	}
-
 	var errs []error
 	for bidder, imps := range impsByBidder {
 		coreBidder := resolveBidder(bidder, aliases)
 
 		reqCopy := *req.BidRequest
 		reqCopy.Imp = imps
-		reqCopy.Ext = reqExt
 
 		prepareSource(&reqCopy, bidder, sChainsByBidder)
 
+		if len(bidderParamsInReqExt) != 0 {
+
+			// Update bidder-params(requestExt.Prebid.BidderParams) for the bidder to only contain bidder-params for
+			// this bidder only and remove bidder-params for all other bidders from requestExt.Prebid.BidderParams
+			params, err := getBidderParamsForBidder(bidderParamsInReqExt, bidder)
+			if err != nil {
+				return nil, []error{err}
+			}
+
+			requestExt.Prebid.BidderParams = params
+		}
+
+		reqExt, err := getExtJson(req.BidRequest, requestExt)
+		if err != nil {
+			return nil, []error{err}
+		}
+		reqCopy.Ext = reqExt
+
 		if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil {
 			errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err))
 			continue
@@ -278,6 +296,18 @@ func getAuctionBidderRequests(req AuctionRequest,
 	return bidderRequests, errs
 }
 
+func getBidderParamsForBidder(bidderParamsInReqExt map[string]map[string]json.RawMessage, bidder string) (json.RawMessage, error) {
+	var params json.RawMessage
+	if bidderParams, ok := bidderParamsInReqExt[bidder]; ok {
+		var err error
+		params, err = json.Marshal(bidderParams)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return params, nil
+}
+
 func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) {
 	if len(req.Ext) == 0 || unpackedExt == nil {
 		return json.RawMessage(``), nil
@@ -309,6 +339,9 @@ func prepareSource(req *openrtb2.BidRequest, bidder string, sChainsByBidder map[
 	// set source
 	if req.Source == nil {
 		req.Source = &openrtb2.Source{}
+	} else {
+		sourceCopy := *req.Source
+		req.Source = &sourceCopy
 	}
 	schain := openrtb_ext.ExtRequestPrebidSChain{
 		SChain: *selectedSChain,
diff --git a/main.go b/main.go
index 76fa64f7..c1038631 100644
--- a/main.go
+++ b/main.go
@@ -4,6 +4,7 @@ import (
 	"flag"
 	"math/rand"
 	"net/http"
+	"runtime"
 	"time"
 
 	"github.com/prebid/prebid-server/config"
@@ -28,6 +29,14 @@ func main() {
 		glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err)
 	}
 
+	// Create a soft memory limit on the total amount of memory that PBS uses to tune the behavior
+	// of the Go garbage collector. In summary, `cfg.GarbageCollectorThreshold` serves as a fixed cost
+	// of memory that is going to be held garbage before a garbage collection cycle is triggered.
+	// This amount of virtual memory won’t translate into physical memory allocation unless we attempt
+	// to read or write to the slice below, which PBS will not do.
+	garbageCollectionThreshold := make([]byte, cfg.GarbageCollectorThreshold)
+	defer runtime.KeepAlive(garbageCollectionThreshold)
+
 	err = serve(cfg)
 	if err != nil {
 		glog.Exitf("prebid-server failed: %v", err)
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index d5c424c5..8483ea67 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -34,6 +34,7 @@ const (
 	BidderReservedGeneral BidderName = "general" // Reserved for non-bidder specific messages when using a map keyed on the bidder name.
 	BidderReservedPrebid  BidderName = "prebid"  // Reserved for Prebid Server configuration.
 	BidderReservedSKAdN   BidderName = "skadn"   // Reserved for Apple's SKAdNetwork OpenRTB extension.
+	BidderReservedBidder  BidderName = "bidder"  // Reserved for passing bidder parameters.
 )
 
 // IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name.
@@ -62,6 +63,10 @@ func IsBidderNameReserved(name string) bool {
 		return true
 	}
 
+	if strings.EqualFold(name, string(BidderReservedBidder)) {
+		return true
+	}
+
 	return false
 }
 
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index d399715f..a65e8d3c 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -37,6 +37,7 @@ type ExtRequestPrebid struct {
 	StoredRequest        *ExtStoredRequest         `json:"storedrequest,omitempty"`
 	SupportDeals         bool                      `json:"supportdeals,omitempty"`
 	Targeting            *ExtRequestTargeting      `json:"targeting,omitempty"`
+	BidderParams         json.RawMessage           `json:"bidderparams,omitempty"`
 
 	// NoSale specifies bidders with whom the publisher has a legal relationship where the
 	// passing of personally identifiable information doesn't constitute a sale per CCPA law.
diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml
index a8769ddd..0853cf10 100644
--- a/static/bidder-info/adyoulike.yaml
+++ b/static/bidder-info/adyoulike.yaml
@@ -9,6 +9,10 @@ capabilities:
       - video
       - native
 userSync:
+  default: iframe
+  iframe:
+    url: "http://visitor.omnitagjs.com/visitor/isync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}"
+    userMacro: "[BUYER_USERID]"
   redirect:
     url: "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}"
     userMacro: "[BUYER_USERID]"


Test patch:
diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go
new file mode 100644
index 00000000..e09149a7
--- /dev/null
+++ b/adapters/openrtb_util_test.go
@@ -0,0 +1,117 @@
+package adapters
+
+import (
+	"encoding/json"
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"reflect"
+	"testing"
+)
+
+func TestExtractAdapterReqBidderParams(t *testing.T) {
+	type args struct {
+		bidRequest *openrtb2.BidRequest
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    map[string]json.RawMessage
+		wantErr bool
+	}{
+		{
+			name: "extract bidder params from nil req",
+			args: args{
+				bidRequest: nil,
+			},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name: "extract bidder params from nil req.Ext",
+			args: args{
+				bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)},
+			},
+			want:    nil,
+			wantErr: false,
+		},
+		{
+			name: "extract bidder params from req.Ext for input request in adapter code",
+			args: args{
+				bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams": {"profile": 1234, "version": 1}}}`)},
+			},
+			want:    map[string]json.RawMessage{"profile": json.RawMessage(`1234`), "version": json.RawMessage(`1`)},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := ExtractAdapterReqBidderParams(tt.args.bidRequest)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ExtractReqExtBidderParams() error = %v, wantErr = %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ExtractReqExtBidderParams() got = %v, want = %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestExtractReqExtBidderParams(t *testing.T) {
+	type args struct {
+		request *openrtb2.BidRequest
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    map[string]map[string]json.RawMessage
+		wantErr bool
+	}{
+		{
+			name: "extract bidder params from nil req",
+			args: args{
+				request: nil,
+			},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name: "extract bidder params from nil req.Ext.prebid",
+			args: args{
+				request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)},
+			},
+			want:    nil,
+			wantErr: false,
+		},
+		{
+			name: "extract bidder params from nil req.Ext",
+			args: args{
+				request: &openrtb2.BidRequest{Ext: nil},
+			},
+			want:    nil,
+			wantErr: false,
+		},
+		{
+			name: "extract bidder params from req.Ext for input request before adapter code",
+			args: args{
+				request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile": 1234, "version": 1}, "appnexus": {"key1": 123, "key2": {"innerKey1":"innerValue1"} } }}}`)},
+			},
+			want: map[string]map[string]json.RawMessage{
+				"pubmatic": {"profile": json.RawMessage(`1234`), "version": json.RawMessage(`1`)},
+				"appnexus": {"key1": json.RawMessage(`123`), "key2": json.RawMessage(`{"innerKey1":"innerValue1"}`)},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := ExtractReqExtBidderParams(tt.args.request)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ExtractReqExtBidderParams() error = %v, wantErr = %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ExtractReqExtBidderParams() got = %v, want = %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json b/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json
new file mode 100644
index 00000000..06fc80ae
--- /dev/null
+++ b/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json
@@ -0,0 +1,160 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adSlot": "AdTag_Div1@300x250",
+            "publisherId": "  999  ",
+            "keywords": [
+              {
+                "key": "pmZoneID",
+                "value": [
+                  "Zone1",
+                  "Zone2"
+                ]
+              },
+              {
+                "key": "preference",
+                "value": [
+                  "sports",
+                  "movies"
+                ]
+              }
+            ],
+            "wrapper": {
+              "version": 1,
+              "profile": 5123
+            }
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+    },
+    "ext": {},
+    "site": {
+      "id": "siteID",
+      "publisher": {
+        "id": "1234"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "AdTag_Div1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ],
+                "h": 250,
+                "w": 300
+              },
+              "ext": {
+                "pmZoneId": "Zone1,Zone2",
+                "preference": "sports,movies"
+              }
+            }
+          ],
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+          },
+          "site": {
+            "id": "siteID",
+            "publisher": {
+              "id": "999"
+            }
+          },
+          "ext": {
+            "wrapper": {
+              "profile": 5123,
+              "version": 1
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [
+                {
+                  "id": "7706636740145184841",
+                  "impid": "test-imp-id",
+                  "price": 0.500000,
+                  "adid": "29681110",
+                  "adm": "some-test-ad",
+                  "adomain": [
+                    "pubmatic.com"
+                  ],
+                  "crid": "29681110",
+                  "h": 250,
+                  "w": 300,
+                  "dealid": "test deal",
+                  "ext": {
+                    "dspid": 6,
+                    "deal_channel": 1
+                  }
+                }
+              ]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adid": "29681110",
+            "adm": "some-test-ad",
+            "adomain": [
+              "pubmatic.com"
+            ],
+            "crid": "29681110",
+            "w": 300,
+            "h": 250,
+            "dealid": "test deal",
+            "ext": {
+              "dspid": 6,
+              "deal_channel": 1
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json b/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json
new file mode 100644
index 00000000..5d4a1819
--- /dev/null
+++ b/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json
@@ -0,0 +1,169 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adSlot": "AdTag_Div1@300x250",
+            "publisherId": "  999  ",
+            "keywords": [
+              {
+                "key": "pmZoneID",
+                "value": [
+                  "Zone1",
+                  "Zone2"
+                ]
+              },
+              {
+                "key": "preference",
+                "value": [
+                  "sports",
+                  "movies"
+                ]
+              }
+            ],
+            "wrapper": {
+              "version": 1,
+              "profile": 5123
+            }
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+    },
+    "ext": {
+      "prebid": {
+        "bidderparams": {
+          "wrapper": {
+            "profile": 1234,
+            "version": 2
+          }
+        }
+      }
+    },
+    "site": {
+      "id": "siteID",
+      "publisher": {
+        "id": "1234"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "AdTag_Div1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ],
+                "h": 250,
+                "w": 300
+              },
+              "ext": {
+                "pmZoneId": "Zone1,Zone2",
+                "preference": "sports,movies"
+              }
+            }
+          ],
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+          },
+          "site": {
+            "id": "siteID",
+            "publisher": {
+              "id": "999"
+            }
+          },
+          "ext": {
+            "wrapper": {
+              "profile": 1234,
+              "version": 2
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [
+                {
+                  "id": "7706636740145184841",
+                  "impid": "test-imp-id",
+                  "price": 0.500000,
+                  "adid": "29681110",
+                  "adm": "some-test-ad",
+                  "adomain": [
+                    "pubmatic.com"
+                  ],
+                  "crid": "29681110",
+                  "h": 250,
+                  "w": 300,
+                  "dealid": "test deal",
+                  "ext": {
+                    "dspid": 6,
+                    "deal_channel": 1
+                  }
+                }
+              ]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adid": "29681110",
+            "adm": "some-test-ad",
+            "adomain": [
+              "pubmatic.com"
+            ],
+            "crid": "29681110",
+            "w": 300,
+            "h": 250,
+            "dealid": "test deal",
+            "ext": {
+              "dspid": 6,
+              "deal_channel": 1
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/config/config_test.go b/config/config_test.go
index 9e9c7037..f86f13bd 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -289,6 +289,7 @@ external_url: http://prebid-server.prebid.org/
 host: prebid-server.prebid.org
 port: 1234
 admin_port: 5678
+garbage_collector_threshold: 1
 auction_timeouts_ms:
   max: 123
   default: 50
@@ -426,6 +427,7 @@ func TestFullConfig(t *testing.T) {
 	cmpStrings(t, "host", cfg.Host, "prebid-server.prebid.org")
 	cmpInts(t, "port", cfg.Port, 1234)
 	cmpInts(t, "admin_port", cfg.AdminPort, 5678)
+	cmpInts(t, "garbage_collector_threshold", cfg.GarbageCollectorThreshold, 1)
 	cmpInts(t, "auction_timeouts_ms.default", int(cfg.AuctionTimeouts.Default), 50)
 	cmpInts(t, "auction_timeouts_ms.max", int(cfg.AuctionTimeouts.Max), 123)
 	cmpStrings(t, "cache.scheme", cfg.CacheURL.Scheme, "http")
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 8439cc65..67348b57 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -3915,3 +3915,117 @@ func TestValidateBanner(t *testing.T) {
 		assert.Equal(t, test.expectedError, result, test.description)
 	}
 }
+
+func TestParseRequestMergeBidderParams(t *testing.T) {
+	type args struct {
+		reqBody string
+	}
+	tests := []struct {
+		name           string
+		args           args
+		expectedImpExt json.RawMessage
+		expectedReqExt json.RawMessage
+		errL           int
+	}{
+		{
+			name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder",
+			args: args{
+				reqBody: validRequest(t, "req-ext-bidder-params.json"),
+			},
+			expectedImpExt: getObject(t, "req-ext-bidder-params.json", "expectedImpExt"),
+			expectedReqExt: getObject(t, "req-ext-bidder-params.json", "expectedReqExt"),
+			errL:           0,
+		},
+		{
+			name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params",
+			args: args{
+				reqBody: validRequest(t, "req-ext-bidder-params-merge.json"),
+			},
+			expectedImpExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedImpExt"),
+			expectedReqExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedReqExt"),
+			errL:           0,
+		},
+		{
+			name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility",
+			args: args{
+				reqBody: validRequest(t, "req-ext-bidder-params-backward-compatible-merge.json"),
+			},
+			expectedImpExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedImpExt"),
+			expectedReqExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedReqExt"),
+			errL:           0,
+		},
+		{
+			name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder for reserved keyword",
+			args: args{
+				reqBody: validRequest(t, "req-ext-bidder-params-merge-reserved-keyword.json"),
+			},
+			expectedImpExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedImpExt"),
+			expectedReqExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedReqExt"),
+			errL:           0,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			deps := &endpointDeps{
+				fakeUUIDGenerator{},
+				&warningsCheckExchange{},
+				newParamsValidator(t),
+				&mockStoredReqFetcher{},
+				empty_fetcher.EmptyFetcher{},
+				empty_fetcher.EmptyFetcher{},
+				&config.Configuration{MaxRequestSize: int64(len(tt.args.reqBody))},
+				&metricsConfig.NilMetricsEngine{},
+				analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+				map[string]string{},
+				false,
+				[]byte{},
+				openrtb_ext.BuildBidderMap(),
+				nil,
+				nil,
+				hardcodedResponseIPValidator{response: true},
+			}
+
+			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(tt.args.reqBody))
+
+			resReq, _, errL := deps.parseRequest(req)
+
+			var expIExt, iExt map[string]interface{}
+			err := json.Unmarshal(tt.expectedImpExt, &expIExt)
+			assert.Nil(t, err, "unmarshal() should return nil error")
+
+			assert.NotNil(t, resReq.BidRequest.Imp[0].Ext, "imp[0].Ext should not be nil")
+			err = json.Unmarshal(resReq.BidRequest.Imp[0].Ext, &iExt)
+			assert.Nil(t, err, "unmarshal() should return nil error")
+
+			assert.Equal(t, expIExt, iExt, "bidderparams in imp[].Ext should match")
+
+			var eReqE, reqE map[string]interface{}
+			err = json.Unmarshal(tt.expectedReqExt, &eReqE)
+			assert.Nil(t, err, "unmarshal() should return nil error")
+
+			err = json.Unmarshal(resReq.BidRequest.Ext, &reqE)
+			assert.Nil(t, err, "unmarshal() should return nil error")
+
+			assert.Equal(t, eReqE, reqE, "req.Ext should match")
+
+			assert.Len(t, errL, tt.errL, "error length should match")
+		})
+	}
+}
+
+func getObject(t *testing.T, filename, key string) json.RawMessage {
+	requestData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + filename)
+	if err != nil {
+		t.Fatalf("Failed to fetch a valid request: %v", err)
+	}
+	testBidRequest, _, _, err := jsonparser.Get(requestData, key)
+	assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err)
+
+	var obj json.RawMessage
+	err = json.Unmarshal(testBidRequest, &obj)
+	if err != nil {
+		t.Fatalf("Failed to fetch object with key '%s' ... got error: %v", key, err)
+	}
+	return obj
+}
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index ef7d0bdb..d0b4006d 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -5,12 +5,12 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/prebid/prebid-server/firstpartydata"
 	"testing"
 
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/firstpartydata"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -933,6 +933,90 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) {
 	}
 }
 
+func TestCleanOpenRTBRequestsBidderParams(t *testing.T) {
+	testCases := []struct {
+		description string
+		inExt       json.RawMessage
+		expectedExt map[string]json.RawMessage
+		hasError    bool
+	}{
+		{
+			description: "Nil Bidder params",
+			inExt:       nil,
+			expectedExt: getExpectedReqExt(true, false, false),
+			hasError:    false,
+		},
+		{
+			description: "Bidder params for single partner",
+			inExt:       json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile":1234,"version":2}}}}`),
+			expectedExt: getExpectedReqExt(false, true, false),
+			hasError:    false,
+		},
+		{
+			description: "Bidder params for two partners",
+			inExt:       json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile":1234,"version":2}, "appnexus": {"key1": 123, "key2": {"innerKey1":"innerValue1"}} }}}`),
+			expectedExt: getExpectedReqExt(false, true, true),
+			hasError:    false,
+		},
+	}
+
+	for _, test := range testCases {
+		req := newBidRequestWithBidderParams(t)
+		var extRequest *openrtb_ext.ExtRequest
+		if test.inExt != nil {
+			req.Ext = test.inExt
+			unmarshaledExt, err := extractBidRequestExt(req)
+			assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt")
+			extRequest = unmarshaledExt
+		}
+
+		auctionReq := AuctionRequest{
+			BidRequest: req,
+			UserSyncs:  &emptyUsersync{},
+		}
+
+		bidderToSyncerKey := map[string]string{}
+		permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
+		metrics := metrics.MetricsEngineMock{}
+		bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
+		if test.hasError == true {
+			assert.NotNil(t, errs)
+			assert.Len(t, bidderRequests, 0)
+		} else {
+			assert.Nil(t, errs)
+			for _, r := range bidderRequests {
+				expected := test.expectedExt[r.BidderName.String()]
+				actual := r.BidRequest.Ext
+				assert.Equal(t, expected, actual, test.description+" Req:Ext.Prebid.BidderParams")
+			}
+		}
+	}
+}
+
+func getExpectedReqExt(nilExt, includePubmaticParams, includeAppnexusParams bool) map[string]json.RawMessage {
+	bidderParamsMap := make(map[string]json.RawMessage)
+
+	if nilExt {
+		bidderParamsMap["pubmatic"] = json.RawMessage(``)
+		bidderParamsMap["appnexus"] = json.RawMessage(``)
+		return bidderParamsMap
+	}
+
+	if includePubmaticParams {
+		bidderParamsMap["pubmatic"] = json.RawMessage(`{"prebid":{"bidderparams":{"profile":1234,"version":2}}}`)
+	} else {
+		bidderParamsMap["pubmatic"] = json.RawMessage(`{"prebid":{}}`)
+	}
+
+	if includeAppnexusParams {
+		bidderParamsMap["appnexus"] = json.RawMessage(`{"prebid":{"bidderparams":{"key1":123,"key2":{"innerKey1":"innerValue1"}}}}`)
+	} else {
+		bidderParamsMap["appnexus"] = json.RawMessage(`{"prebid":{}}`)
+	}
+
+	return bidderParamsMap
+}
+
 func TestExtractBidRequestExt(t *testing.T) {
 	var boolFalse, boolTrue *bool = new(bool), new(bool)
 	*boolFalse = false
@@ -2000,6 +2084,47 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest {
 	}
 }
 
+func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest {
+	return &openrtb2.BidRequest{
+		Site: &openrtb2.Site{
+			Page:   "www.some.domain.com",
+			Domain: "domain.com",
+			Publisher: &openrtb2.Publisher{
+				ID: "some-publisher-id",
+			},
+		},
+		Device: &openrtb2.Device{
+			DIDMD5:   "some device ID hash",
+			UA:       "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
+			IFA:      "ifa",
+			IP:       "132.173.230.74",
+			Language: "EN",
+		},
+		Source: &openrtb2.Source{
+			TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87",
+		},
+		User: &openrtb2.User{
+			ID:       "our-id",
+			BuyerUID: "their-id",
+			Yob:      1982,
+			Ext:      json.RawMessage(`{}`),
+		},
+		Imp: []openrtb2.Imp{{
+			ID: "some-imp-id",
+			Banner: &openrtb2.Banner{
+				Format: []openrtb2.Format{{
+					W: 300,
+					H: 250,
+				}, {
+					W: 300,
+					H: 600,
+				}},
+			},
+			Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "pubmatic":{"publisherId": "1234"}}`),
+		}},
+	}
+}
+
 func TestRandomizeList(t *testing.T) {
 	var (
 		bidder1 = openrtb_ext.BidderName("bidder1")
@@ -2609,8 +2734,87 @@ func TestBuildXPrebidHeader(t *testing.T) {
 	}
 }
 
-func TestApplyFPD(t *testing.T) {
+func TestSourceExtSChainCopied(t *testing.T) {
+	bidRequest := newBidRequest(t)
 
+	bidderSchains := map[string]*openrtb_ext.ExtRequestPrebidSChainSChain{
+		"bidder1": {
+			Ver:      "1.0",
+			Complete: 1,
+			Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
+				{
+					ASI: "bidder1.com",
+					SID: "0001",
+					HP:  1,
+				},
+			},
+		},
+		"bidder2": {
+			Ver:      "1.0",
+			Complete: 1,
+			Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
+				{
+					ASI: "bidder2.com",
+					SID: "0002",
+					HP:  1,
+				},
+			},
+		},
+	}
+
+	copy1 := *bidRequest
+	originalTid := copy1.Source.TID
+	prepareSource(&copy1, "bidder1", bidderSchains)
+	copy2 := *bidRequest
+	copy2.Source.TID = "new TID"
+	prepareSource(&copy2, "bidder2", bidderSchains)
+
+	assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder1.com","sid":"0001","hp":1}],"ver":"1.0"}}`), copy1.Source.Ext, "First schain was overwritten or not set")
+	assert.Equal(t, originalTid, copy1.Source.TID, "Original TID was overwritten")
+	assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder2.com","sid":"0002","hp":1}],"ver":"1.0"}}`), copy2.Source.Ext, "Second schain was overwritten or not set")
+	assert.Equal(t, "new TID", copy2.Source.TID, "New TID was not set")
+}
+
+func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) {
+	req := &openrtb2.BidRequest{
+		Site: &openrtb2.Site{},
+		Source: &openrtb2.Source{
+			TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87",
+		},
+		Imp: []openrtb2.Imp{{
+			Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "axonix": { "supplyId": "123"}}`),
+		}},
+		Ext: json.RawMessage(`{"prebid":{"schains":[{ "bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["axonix"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
+	}
+
+	unmarshaledExt, err := extractBidRequestExt(req)
+	assert.NoErrorf(t, err, "Error unmarshaling inExt")
+	extRequest := unmarshaledExt
+
+	auctionReq := AuctionRequest{
+		BidRequest: req,
+		UserSyncs:  &emptyUsersync{},
+	}
+	bidderToSyncerKey := map[string]string{}
+	permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
+	metrics := metrics.MetricsEngineMock{}
+	bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
+
+	assert.Nil(t, errs)
+	assert.Len(t, bidderRequests, 2, "Bid request count is not 2")
+
+	bidRequestSourceExts := map[openrtb_ext.BidderName]json.RawMessage{}
+	for _, bidderRequest := range bidderRequests {
+		bidRequestSourceExts[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.Ext
+	}
+
+	appnexusPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`)
+	axonixPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}`)
+	assert.Equal(t, appnexusPrebidSchainsSchain, bidRequestSourceExts["appnexus"], "Incorrect appnexus bid request schain in source.ext")
+	assert.Equal(t, axonixPrebidSchainsSchain, bidRequestSourceExts["axonix"], "Incorrect axonix bid request schain in source.ext")
+}
+
+func TestApplyFPD(t *testing.T) {
 	fpdBidderTest := map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{}
 	bidderTest := openrtb_ext.BidderName("test")
 	fpdBidderTest[bidderTest] = &firstpartydata.ResolvedFirstPartyData{Site: nil, App: nil, User: nil}
diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go
index 26ebf7dc..8bd25d9e 100644
--- a/openrtb_ext/bidders_test.go
+++ b/openrtb_ext/bidders_test.go
@@ -110,6 +110,9 @@ func TestIsBidderNameReserved(t *testing.T) {
 		{"prebid", true},
 		{"PREbid", true},
 		{"PREBID", true},
+		{"bidder", true},
+		{"BIDDER", true},
+		{"BidDer", true},
 		{"notreserved", false},
 	}