github.com/prebid/prebid-server@v0.275.0/exchange/exchange.go (about)

     1  package exchange
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"math/rand"
    10  	"net/url"
    11  	"runtime/debug"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/prebid/prebid-server/privacy"
    18  
    19  	"github.com/prebid/prebid-server/adapters"
    20  	"github.com/prebid/prebid-server/adservertargeting"
    21  	"github.com/prebid/prebid-server/bidadjustment"
    22  	"github.com/prebid/prebid-server/config"
    23  	"github.com/prebid/prebid-server/currency"
    24  	"github.com/prebid/prebid-server/errortypes"
    25  	"github.com/prebid/prebid-server/exchange/entities"
    26  	"github.com/prebid/prebid-server/experiment/adscert"
    27  	"github.com/prebid/prebid-server/firstpartydata"
    28  	"github.com/prebid/prebid-server/floors"
    29  	"github.com/prebid/prebid-server/gdpr"
    30  	"github.com/prebid/prebid-server/hooks/hookexecution"
    31  	"github.com/prebid/prebid-server/macros"
    32  	"github.com/prebid/prebid-server/metrics"
    33  	"github.com/prebid/prebid-server/openrtb_ext"
    34  	"github.com/prebid/prebid-server/prebid_cache_client"
    35  	"github.com/prebid/prebid-server/stored_requests"
    36  	"github.com/prebid/prebid-server/stored_responses"
    37  	"github.com/prebid/prebid-server/usersync"
    38  	"github.com/prebid/prebid-server/util/maputil"
    39  
    40  	"github.com/buger/jsonparser"
    41  	"github.com/gofrs/uuid"
    42  	"github.com/golang/glog"
    43  	"github.com/prebid/openrtb/v19/openrtb2"
    44  	"github.com/prebid/openrtb/v19/openrtb3"
    45  )
    46  
    47  type extCacheInstructions struct {
    48  	cacheBids, cacheVAST, returnCreative bool
    49  }
    50  
    51  // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines.
    52  type Exchange interface {
    53  	// HoldAuction executes an OpenRTB v2.5 Auction.
    54  	HoldAuction(ctx context.Context, r *AuctionRequest, debugLog *DebugLog) (*AuctionResponse, error)
    55  }
    56  
    57  // IdFetcher can find the user's ID for a specific Bidder.
    58  type IdFetcher interface {
    59  	GetUID(key string) (uid string, exists bool, notExpired bool)
    60  	HasAnyLiveSyncs() bool
    61  }
    62  
    63  type exchange struct {
    64  	adapterMap               map[openrtb_ext.BidderName]AdaptedBidder
    65  	bidderInfo               config.BidderInfos
    66  	bidderToSyncerKey        map[string]string
    67  	me                       metrics.MetricsEngine
    68  	cache                    prebid_cache_client.Client
    69  	cacheTime                time.Duration
    70  	gdprPermsBuilder         gdpr.PermissionsBuilder
    71  	currencyConverter        *currency.RateConverter
    72  	externalURL              string
    73  	gdprDefaultValue         gdpr.Signal
    74  	privacyConfig            config.Privacy
    75  	categoriesFetcher        stored_requests.CategoryFetcher
    76  	bidIDGenerator           BidIDGenerator
    77  	hostSChainNode           *openrtb2.SupplyChainNode
    78  	adsCertSigner            adscert.Signer
    79  	server                   config.Server
    80  	bidValidationEnforcement config.Validations
    81  	requestSplitter          requestSplitter
    82  	macroReplacer            macros.Replacer
    83  	floor                    config.PriceFloors
    84  }
    85  
    86  // Container to pass out response ext data from the GetAllBids goroutines back into the main thread
    87  type seatResponseExtra struct {
    88  	ResponseTimeMillis int
    89  	Errors             []openrtb_ext.ExtBidderMessage
    90  	Warnings           []openrtb_ext.ExtBidderMessage
    91  	// httpCalls is the list of debugging info. It should only be populated if the request.test == 1.
    92  	// This will become response.ext.debug.httpcalls.{bidder} on the final Response.
    93  	HttpCalls []*openrtb_ext.ExtHttpCall
    94  }
    95  
    96  type bidResponseWrapper struct {
    97  	adapterSeatBids         []*entities.PbsOrtbSeatBid
    98  	adapterExtra            *seatResponseExtra
    99  	bidder                  openrtb_ext.BidderName
   100  	adapter                 openrtb_ext.BidderName
   101  	bidderResponseStartTime time.Time
   102  }
   103  
   104  type BidIDGenerator interface {
   105  	New() (string, error)
   106  	Enabled() bool
   107  }
   108  
   109  type bidIDGenerator struct {
   110  	enabled bool
   111  }
   112  
   113  func (big *bidIDGenerator) Enabled() bool {
   114  	return big.enabled
   115  }
   116  
   117  func (big *bidIDGenerator) New() (string, error) {
   118  	rawUuid, err := uuid.NewV4()
   119  	return rawUuid.String(), err
   120  }
   121  
   122  type deduplicateChanceGenerator interface {
   123  	Generate() bool
   124  }
   125  
   126  type randomDeduplicateBidBooleanGenerator struct{}
   127  
   128  func (randomDeduplicateBidBooleanGenerator) Generate() bool {
   129  	return rand.Intn(100) < 50
   130  }
   131  
   132  func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer, macroReplacer macros.Replacer) Exchange {
   133  	bidderToSyncerKey := map[string]string{}
   134  	for bidder, syncer := range syncersByBidder {
   135  		bidderToSyncerKey[bidder] = syncer.Key()
   136  	}
   137  
   138  	gdprDefaultValue := gdpr.SignalYes
   139  	if cfg.GDPR.DefaultValue == "0" {
   140  		gdprDefaultValue = gdpr.SignalNo
   141  	}
   142  
   143  	privacyConfig := config.Privacy{
   144  		CCPA: cfg.CCPA,
   145  		GDPR: cfg.GDPR,
   146  		LMT:  cfg.LMT,
   147  	}
   148  	requestSplitter := requestSplitter{
   149  		bidderToSyncerKey: bidderToSyncerKey,
   150  		me:                metricsEngine,
   151  		privacyConfig:     privacyConfig,
   152  		gdprPermsBuilder:  gdprPermsBuilder,
   153  		hostSChainNode:    cfg.HostSChainNode,
   154  		bidderInfo:        infos,
   155  	}
   156  
   157  	return &exchange{
   158  		adapterMap:               adapters,
   159  		bidderInfo:               infos,
   160  		bidderToSyncerKey:        bidderToSyncerKey,
   161  		cache:                    cache,
   162  		cacheTime:                time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond,
   163  		categoriesFetcher:        categoriesFetcher,
   164  		currencyConverter:        currencyConverter,
   165  		externalURL:              cfg.ExternalURL,
   166  		gdprPermsBuilder:         gdprPermsBuilder,
   167  		me:                       metricsEngine,
   168  		gdprDefaultValue:         gdprDefaultValue,
   169  		privacyConfig:            privacyConfig,
   170  		bidIDGenerator:           &bidIDGenerator{cfg.GenerateBidID},
   171  		hostSChainNode:           cfg.HostSChainNode,
   172  		adsCertSigner:            adsCertSigner,
   173  		server:                   config.Server{ExternalUrl: cfg.ExternalURL, GvlID: cfg.GDPR.HostVendorID, DataCenter: cfg.DataCenter},
   174  		bidValidationEnforcement: cfg.Validations,
   175  		requestSplitter:          requestSplitter,
   176  		macroReplacer:            macroReplacer,
   177  		floor:                    cfg.PriceFloors,
   178  	}
   179  }
   180  
   181  type ImpExtInfo struct {
   182  	EchoVideoAttrs bool
   183  	StoredImp      []byte
   184  	Passthrough    json.RawMessage
   185  }
   186  
   187  // AuctionRequest holds the bid request for the auction
   188  // and all other information needed to process that request
   189  type AuctionRequest struct {
   190  	BidRequestWrapper          *openrtb_ext.RequestWrapper
   191  	ResolvedBidRequest         json.RawMessage
   192  	Account                    config.Account
   193  	UserSyncs                  IdFetcher
   194  	RequestType                metrics.RequestType
   195  	StartTime                  time.Time
   196  	Warnings                   []error
   197  	GlobalPrivacyControlHeader string
   198  	ImpExtInfoMap              map[string]ImpExtInfo
   199  	TCF2Config                 gdpr.TCF2ConfigReader
   200  	Activities                 privacy.ActivityControl
   201  
   202  	// LegacyLabels is included here for temporary compatibility with cleanOpenRTBRequests
   203  	// in HoldAuction until we get to factoring it away. Do not use for anything new.
   204  	LegacyLabels   metrics.Labels
   205  	FirstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData
   206  	// map of imp id to stored response
   207  	StoredAuctionResponses stored_responses.ImpsWithBidResponses
   208  	// map of imp id to bidder to stored response
   209  	StoredBidResponses      stored_responses.ImpBidderStoredResp
   210  	BidderImpReplaceImpID   stored_responses.BidderImpReplaceImpID
   211  	PubID                   string
   212  	HookExecutor            hookexecution.StageExecutor
   213  	QueryParams             url.Values
   214  	BidderResponseStartTime time.Time
   215  	TmaxAdjustments         *TmaxAdjustmentsPreprocessed
   216  }
   217  
   218  // BidderRequest holds the bidder specific request and all other
   219  // information needed to process that bidder request.
   220  type BidderRequest struct {
   221  	BidRequest            *openrtb2.BidRequest
   222  	BidderName            openrtb_ext.BidderName
   223  	BidderCoreName        openrtb_ext.BidderName
   224  	BidderLabels          metrics.AdapterLabels
   225  	BidderStoredResponses map[string]json.RawMessage
   226  	ImpReplaceImpId       map[string]bool
   227  }
   228  
   229  func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog *DebugLog) (*AuctionResponse, error) {
   230  	if r == nil {
   231  		return nil, nil
   232  	}
   233  
   234  	var floorErrs []error
   235  	err := r.HookExecutor.ExecuteProcessedAuctionStage(r.BidRequestWrapper)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	requestExt, err := r.BidRequestWrapper.GetRequestExt()
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	// ensure prebid object always exists
   246  	requestExtPrebid := requestExt.GetPrebid()
   247  	if requestExtPrebid == nil {
   248  		requestExtPrebid = &openrtb_ext.ExtRequestPrebid{}
   249  	}
   250  
   251  	if !e.server.Empty() {
   252  		requestExtPrebid.Server = &openrtb_ext.ExtRequestPrebidServer{
   253  			ExternalUrl: e.server.ExternalUrl,
   254  			GvlID:       e.server.GvlID,
   255  			DataCenter:  e.server.DataCenter}
   256  		requestExt.SetPrebid(requestExtPrebid)
   257  	}
   258  
   259  	cacheInstructions := getExtCacheInstructions(requestExtPrebid)
   260  
   261  	targData := getExtTargetData(requestExtPrebid, cacheInstructions)
   262  	if targData != nil {
   263  		_, targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData()
   264  	}
   265  
   266  	// Get currency rates conversions for the auction
   267  	conversions := e.getAuctionCurrencyRates(requestExtPrebid.CurrencyConversions)
   268  
   269  	if e.floor.Enabled {
   270  		floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions)
   271  	}
   272  
   273  	responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(r.BidRequestWrapper.Test, requestExtPrebid, r.Account.DebugAllow, debugLog)
   274  
   275  	// save incoming request with stored requests (if applicable) to return in debug logs
   276  	if responseDebugAllow || len(requestExtPrebid.AdServerTargeting) > 0 {
   277  		if err := r.BidRequestWrapper.RebuildRequest(); err != nil {
   278  			return nil, err
   279  		}
   280  		resolvedBidReq, err := json.Marshal(r.BidRequestWrapper.BidRequest)
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  		r.ResolvedBidRequest = resolvedBidReq
   285  	}
   286  	e.me.RecordDebugRequest(responseDebugAllow || accountDebugAllow, r.PubID)
   287  
   288  	if r.RequestType == metrics.ReqTypeORTB2Web ||
   289  		r.RequestType == metrics.ReqTypeORTB2App ||
   290  		r.RequestType == metrics.ReqTypeAMP {
   291  		//Extract First party data for auction endpoint only
   292  		resolvedFPD, fpdErrors := firstpartydata.ExtractFPDForBidders(r.BidRequestWrapper)
   293  		if len(fpdErrors) > 0 {
   294  			var errMessages []string
   295  			for _, fpdError := range fpdErrors {
   296  				errMessages = append(errMessages, fpdError.Error())
   297  			}
   298  			return nil, &errortypes.BadInput{
   299  				Message: strings.Join(errMessages, ","),
   300  			}
   301  		}
   302  		r.FirstPartyData = resolvedFPD
   303  	}
   304  
   305  	bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExtPrebid)
   306  
   307  	recordImpMetrics(r.BidRequestWrapper, e.me)
   308  
   309  	// Make our best guess if GDPR applies
   310  	gdprDefaultValue := e.parseGDPRDefaultValue(r.BidRequestWrapper)
   311  
   312  	// rebuild/resync the request in the request wrapper.
   313  	if err := r.BidRequestWrapper.RebuildRequest(); err != nil {
   314  		return nil, err
   315  	}
   316  
   317  	// Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder
   318  	requestExtLegacy := &openrtb_ext.ExtRequest{
   319  		Prebid: *requestExtPrebid,
   320  		SChain: requestExt.GetSChain(),
   321  	}
   322  	bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprDefaultValue, bidAdjustmentFactors)
   323  	errs = append(errs, floorErrs...)
   324  
   325  	mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments)
   326  	if err != nil {
   327  		if errortypes.ContainsFatalError([]error{err}) {
   328  			return nil, err
   329  		}
   330  		errs = append(errs, err)
   331  	}
   332  	bidAdjustmentRules := bidadjustment.BuildRules(mergedBidAdj)
   333  
   334  	e.me.RecordRequestPrivacy(privacyLabels)
   335  
   336  	if len(r.StoredAuctionResponses) > 0 || len(r.StoredBidResponses) > 0 {
   337  		e.me.RecordStoredResponse(r.PubID)
   338  	}
   339  
   340  	// If we need to cache bids, then it will take some time to call prebid cache.
   341  	// We should reduce the amount of time the bidders have, to compensate.
   342  	auctionCtx, cancel := e.makeAuctionContext(ctx, cacheInstructions.cacheBids)
   343  	defer cancel()
   344  
   345  	var (
   346  		adapterBids     map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
   347  		adapterExtra    map[openrtb_ext.BidderName]*seatResponseExtra
   348  		fledge          *openrtb_ext.Fledge
   349  		anyBidsReturned bool
   350  		// List of bidders we have requests for.
   351  		liveAdapters []openrtb_ext.BidderName
   352  	)
   353  
   354  	if len(r.StoredAuctionResponses) > 0 {
   355  		adapterBids, fledge, liveAdapters, err = buildStoredAuctionResponse(r.StoredAuctionResponses)
   356  		if err != nil {
   357  			return nil, err
   358  		}
   359  		anyBidsReturned = true
   360  
   361  	} else {
   362  		// List of bidders we have requests for.
   363  		liveAdapters = listBiddersWithRequests(bidderRequests)
   364  
   365  		//This will be used to validate bids
   366  		var alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes
   367  		if requestExtPrebid.AlternateBidderCodes != nil {
   368  			alternateBidderCodes = *requestExtPrebid.AlternateBidderCodes
   369  		} else if r.Account.AlternateBidderCodes != nil {
   370  			alternateBidderCodes = *r.Account.AlternateBidderCodes
   371  		}
   372  		var extraRespInfo extraAuctionResponseInfo
   373  		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)
   374  		fledge = extraRespInfo.fledge
   375  		anyBidsReturned = extraRespInfo.bidsFound
   376  		r.BidderResponseStartTime = extraRespInfo.bidderResponseStartTime
   377  	}
   378  
   379  	var (
   380  		auc            *auction
   381  		cacheErrs      []error
   382  		bidResponseExt *openrtb_ext.ExtBidResponse
   383  		seatNonBids    = nonBids{}
   384  	)
   385  
   386  	if anyBidsReturned {
   387  		if e.floor.Enabled {
   388  			var rejectedBids []*entities.PbsOrtbSeatBid
   389  			var enforceErrs []error
   390  
   391  			adapterBids, enforceErrs, rejectedBids = floors.Enforce(r.BidRequestWrapper, adapterBids, r.Account, conversions)
   392  			errs = append(errs, enforceErrs...)
   393  			for _, rejectedBid := range rejectedBids {
   394  				errs = append(errs, &errortypes.Warning{
   395  					Message:     fmt.Sprintf("%s bid id %s rejected - bid price %.4f %s is less than bid floor %.4f %s for imp %s", rejectedBid.Seat, rejectedBid.Bids[0].Bid.ID, rejectedBid.Bids[0].Bid.Price, rejectedBid.Currency, rejectedBid.Bids[0].BidFloors.FloorValue, rejectedBid.Bids[0].BidFloors.FloorCurrency, rejectedBid.Bids[0].Bid.ImpID),
   396  					WarningCode: errortypes.FloorBidRejectionWarningCode})
   397  			}
   398  		}
   399  
   400  		var bidCategory map[string]string
   401  		//If includebrandcategory is present in ext then CE feature is on.
   402  		if requestExtPrebid.Targeting != nil && requestExtPrebid.Targeting.IncludeBrandCategory != nil {
   403  			var rejections []string
   404  			bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, *requestExtPrebid.Targeting, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids)
   405  			if err != nil {
   406  				return nil, fmt.Errorf("Error in category mapping : %s", err.Error())
   407  			}
   408  			for _, message := range rejections {
   409  				errs = append(errs, errors.New(message))
   410  			}
   411  		}
   412  
   413  		if e.bidIDGenerator.Enabled() {
   414  			for _, seatBid := range adapterBids {
   415  				for _, pbsBid := range seatBid.Bids {
   416  					pbsBid.GeneratedBidID, err = e.bidIDGenerator.New()
   417  					if err != nil {
   418  						errs = append(errs, errors.New("Error generating bid.ext.prebid.bidid"))
   419  					}
   420  				}
   421  			}
   422  		}
   423  
   424  		evTracking := getEventTracking(requestExtPrebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL)
   425  		adapterBids = evTracking.modifyBidsForEvents(adapterBids)
   426  
   427  		r.HookExecutor.ExecuteAllProcessedBidResponsesStage(adapterBids)
   428  
   429  		if targData != nil {
   430  			multiBidMap := buildMultiBidMap(requestExtPrebid)
   431  
   432  			// A non-nil auction is only needed if targeting is active. (It is used below this block to extract cache keys)
   433  			auc = newAuction(adapterBids, len(r.BidRequestWrapper.Imp), targData.preferDeals)
   434  			auc.validateAndUpdateMultiBid(adapterBids, targData.preferDeals, r.Account.DefaultBidLimit)
   435  			auc.setRoundedPrices(*targData)
   436  
   437  			if requestExtPrebid.SupportDeals {
   438  				dealErrs := applyDealSupport(r.BidRequestWrapper.BidRequest, auc, bidCategory, multiBidMap)
   439  				errs = append(errs, dealErrs...)
   440  			}
   441  
   442  			bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
   443  			if debugLog.DebugEnabledOrOverridden {
   444  				if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
   445  					debugLog.Data.Response = string(bidRespExtBytes)
   446  				} else {
   447  					debugLog.Data.Response = "Unable to marshal response ext for debugging"
   448  					errs = append(errs, err)
   449  				}
   450  			}
   451  
   452  			cacheErrs = auc.doCache(ctx, e.cache, targData, evTracking, r.BidRequestWrapper.BidRequest, 60, &r.Account.CacheTTL, bidCategory, debugLog)
   453  			if len(cacheErrs) > 0 {
   454  				errs = append(errs, cacheErrs...)
   455  			}
   456  
   457  			targData.setTargeting(auc, r.BidRequestWrapper.BidRequest.App != nil, bidCategory, r.Account.TruncateTargetAttribute, multiBidMap)
   458  		}
   459  		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
   460  	} else {
   461  		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
   462  
   463  		if debugLog.DebugEnabledOrOverridden {
   464  
   465  			if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
   466  				debugLog.Data.Response = string(bidRespExtBytes)
   467  			} else {
   468  				debugLog.Data.Response = "Unable to marshal response ext for debugging"
   469  				errs = append(errs, err)
   470  			}
   471  		}
   472  	}
   473  
   474  	if !accountDebugAllow && !debugLog.DebugOverride {
   475  		accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{
   476  			Code:    errortypes.AccountLevelDebugDisabledWarningCode,
   477  			Message: "debug turned off for account",
   478  		}
   479  		bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], accountDebugDisabledWarning)
   480  	}
   481  
   482  	for _, warning := range r.Warnings {
   483  		generalWarning := openrtb_ext.ExtBidderMessage{
   484  			Code:    errortypes.ReadCode(warning),
   485  			Message: warning.Error(),
   486  		}
   487  		bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], generalWarning)
   488  	}
   489  
   490  	e.bidValidationEnforcement.SetBannerCreativeMaxSize(r.Account.Validations)
   491  
   492  	// Build the response
   493  	bidResponse := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs)
   494  	bidResponse = adservertargeting.Apply(r.BidRequestWrapper, r.ResolvedBidRequest, bidResponse, r.QueryParams, bidResponseExt, r.Account.TruncateTargetAttribute)
   495  
   496  	bidResponse.Ext, err = encodeBidResponseExt(bidResponseExt)
   497  	if err != nil {
   498  		return nil, err
   499  	}
   500  	bidResponseExt = setSeatNonBid(bidResponseExt, seatNonBids)
   501  
   502  	return &AuctionResponse{
   503  		BidResponse:    bidResponse,
   504  		ExtBidResponse: bidResponseExt,
   505  	}, nil
   506  }
   507  
   508  func buildMultiBidMap(prebid *openrtb_ext.ExtRequestPrebid) map[string]openrtb_ext.ExtMultiBid {
   509  	if prebid == nil || prebid.MultiBid == nil {
   510  		return nil
   511  	}
   512  
   513  	// validation already done in validateRequestExt(), directly build a map here for downstream processing
   514  	multiBidMap := make(map[string]openrtb_ext.ExtMultiBid)
   515  	for _, multiBid := range prebid.MultiBid {
   516  		if multiBid.Bidder != "" {
   517  			multiBidMap[multiBid.Bidder] = *multiBid
   518  		} else {
   519  			for _, bidder := range multiBid.Bidders {
   520  				multiBidMap[bidder] = *multiBid
   521  			}
   522  		}
   523  	}
   524  
   525  	return multiBidMap
   526  }
   527  
   528  func (e *exchange) parseGDPRDefaultValue(r *openrtb_ext.RequestWrapper) gdpr.Signal {
   529  	gdprDefaultValue := e.gdprDefaultValue
   530  
   531  	var geo *openrtb2.Geo
   532  	if r.User != nil && r.User.Geo != nil {
   533  		geo = r.User.Geo
   534  	} else if r.Device != nil && r.Device.Geo != nil {
   535  		geo = r.Device.Geo
   536  	}
   537  
   538  	if geo != nil {
   539  		// If we have a country set, and it is on the list, we assume GDPR applies if not set on the request.
   540  		// Otherwise we assume it does not apply as long as it appears "valid" (is 3 characters long).
   541  		if _, found := e.privacyConfig.GDPR.EEACountriesMap[strings.ToUpper(geo.Country)]; found {
   542  			gdprDefaultValue = gdpr.SignalYes
   543  		} else if len(geo.Country) == 3 {
   544  			// The country field is formatted properly as a three character country code
   545  			gdprDefaultValue = gdpr.SignalNo
   546  		}
   547  	}
   548  
   549  	return gdprDefaultValue
   550  }
   551  
   552  func recordImpMetrics(r *openrtb_ext.RequestWrapper, metricsEngine metrics.MetricsEngine) {
   553  	for _, impInRequest := range r.GetImp() {
   554  		var impLabels metrics.ImpLabels = metrics.ImpLabels{
   555  			BannerImps: impInRequest.Banner != nil,
   556  			VideoImps:  impInRequest.Video != nil,
   557  			AudioImps:  impInRequest.Audio != nil,
   558  			NativeImps: impInRequest.Native != nil,
   559  		}
   560  		metricsEngine.RecordImps(impLabels)
   561  	}
   562  }
   563  
   564  // applyDealSupport updates targeting keys with deal prefixes if minimum deal tier exceeded
   565  func applyDealSupport(bidRequest *openrtb2.BidRequest, auc *auction, bidCategory map[string]string, multiBid map[string]openrtb_ext.ExtMultiBid) []error {
   566  	errs := []error{}
   567  	impDealMap := getDealTiers(bidRequest)
   568  
   569  	for impID, topBidsPerImp := range auc.winningBidsByBidder {
   570  		impDeal := impDealMap[impID]
   571  		for bidder, topBidsPerBidder := range topBidsPerImp {
   572  			maxBid := bidsToUpdate(multiBid, bidder.String())
   573  
   574  			for i, topBid := range topBidsPerBidder {
   575  				if i == maxBid {
   576  					break
   577  				}
   578  				if topBid.DealPriority > 0 {
   579  					if validateDealTier(impDeal[bidder]) {
   580  						updateHbPbCatDur(topBid, impDeal[bidder], bidCategory)
   581  					} else {
   582  						errs = append(errs, fmt.Errorf("dealTier configuration invalid for bidder '%s', imp ID '%s'", string(bidder), impID))
   583  					}
   584  				}
   585  			}
   586  		}
   587  	}
   588  
   589  	return errs
   590  }
   591  
   592  // By default, update 1 bid,
   593  // For 2nd and the following bids, updateHbPbCatDur only if this bidder's multibid config is fully defined.
   594  func bidsToUpdate(multiBid map[string]openrtb_ext.ExtMultiBid, bidder string) int {
   595  	if multiBid != nil {
   596  		if bidderMultiBid, ok := multiBid[bidder]; ok && bidderMultiBid.TargetBidderCodePrefix != "" {
   597  			return *bidderMultiBid.MaxBids
   598  		}
   599  	}
   600  
   601  	return openrtb_ext.DefaultBidLimit
   602  }
   603  
   604  // getDealTiers creates map of impression to bidder deal tier configuration
   605  func getDealTiers(bidRequest *openrtb2.BidRequest) map[string]openrtb_ext.DealTierBidderMap {
   606  	impDealMap := make(map[string]openrtb_ext.DealTierBidderMap)
   607  
   608  	for _, imp := range bidRequest.Imp {
   609  		dealTierBidderMap, err := openrtb_ext.ReadDealTiersFromImp(imp)
   610  		if err != nil {
   611  			continue
   612  		}
   613  		impDealMap[imp.ID] = dealTierBidderMap
   614  	}
   615  
   616  	return impDealMap
   617  }
   618  
   619  func validateDealTier(dealTier openrtb_ext.DealTier) bool {
   620  	return len(dealTier.Prefix) > 0 && dealTier.MinDealTier > 0
   621  }
   622  
   623  func updateHbPbCatDur(bid *entities.PbsOrtbBid, dealTier openrtb_ext.DealTier, bidCategory map[string]string) {
   624  	if bid.DealPriority >= dealTier.MinDealTier {
   625  		prefixTier := fmt.Sprintf("%s%d_", dealTier.Prefix, bid.DealPriority)
   626  		bid.DealTierSatisfied = true
   627  
   628  		if oldCatDur, ok := bidCategory[bid.Bid.ID]; ok {
   629  			oldCatDurSplit := strings.SplitAfterN(oldCatDur, "_", 2)
   630  			oldCatDurSplit[0] = prefixTier
   631  
   632  			newCatDur := strings.Join(oldCatDurSplit, "")
   633  			bidCategory[bid.Bid.ID] = newCatDur
   634  		}
   635  	}
   636  }
   637  
   638  func (e *exchange) makeAuctionContext(ctx context.Context, needsCache bool) (auctionCtx context.Context, cancel context.CancelFunc) {
   639  	auctionCtx = ctx
   640  	cancel = func() {}
   641  	if needsCache {
   642  		if deadline, ok := ctx.Deadline(); ok {
   643  			auctionCtx, cancel = context.WithDeadline(ctx, deadline.Add(-e.cacheTime))
   644  		}
   645  	}
   646  	return
   647  }
   648  
   649  // This piece sends all the requests to the bidder adapters and gathers the results.
   650  func (e *exchange) getAllBids(
   651  	ctx context.Context,
   652  	bidderRequests []BidderRequest,
   653  	bidAdjustments map[string]float64,
   654  	conversions currency.Conversions,
   655  	accountDebugAllowed bool,
   656  	globalPrivacyControlHeader string,
   657  	headerDebugAllowed bool,
   658  	alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes,
   659  	experiment *openrtb_ext.Experiment,
   660  	hookExecutor hookexecution.StageExecutor,
   661  	pbsRequestStartTime time.Time,
   662  	bidAdjustmentRules map[string][]openrtb_ext.Adjustment,
   663  	tmaxAdjustments *TmaxAdjustmentsPreprocessed) (
   664  	map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
   665  	map[openrtb_ext.BidderName]*seatResponseExtra,
   666  	extraAuctionResponseInfo) {
   667  	// Set up pointers to the bid results
   668  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, len(bidderRequests))
   669  	adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, len(bidderRequests))
   670  	chBids := make(chan *bidResponseWrapper, len(bidderRequests))
   671  	extraRespInfo := extraAuctionResponseInfo{}
   672  
   673  	e.me.RecordOverheadTime(metrics.MakeBidderRequests, time.Since(pbsRequestStartTime))
   674  
   675  	for _, bidder := range bidderRequests {
   676  		// Here we actually call the adapters and collect the bids.
   677  		bidderRunner := e.recoverSafely(bidderRequests, func(bidderRequest BidderRequest, conversions currency.Conversions) {
   678  			// Passing in aName so a doesn't change out from under the go routine
   679  			if bidderRequest.BidderLabels.Adapter == "" {
   680  				glog.Errorf("Exchange: bidlables for %s (%s) missing adapter string", bidderRequest.BidderName, bidderRequest.BidderCoreName)
   681  				bidderRequest.BidderLabels.Adapter = bidderRequest.BidderCoreName
   682  			}
   683  			brw := new(bidResponseWrapper)
   684  			brw.bidder = bidderRequest.BidderName
   685  			brw.adapter = bidderRequest.BidderCoreName
   686  			// Defer basic metrics to insure we capture them after all the values have been set
   687  			defer func() {
   688  				e.me.RecordAdapterRequest(bidderRequest.BidderLabels)
   689  			}()
   690  			start := time.Now()
   691  
   692  			reqInfo := adapters.NewExtraRequestInfo(conversions)
   693  			reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType
   694  			reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader
   695  
   696  			bidReqOptions := bidRequestOptions{
   697  				accountDebugAllowed:    accountDebugAllowed,
   698  				headerDebugAllowed:     headerDebugAllowed,
   699  				addCallSignHeader:      isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]),
   700  				bidAdjustments:         bidAdjustments,
   701  				tmaxAdjustments:        tmaxAdjustments,
   702  				bidderRequestStartTime: start,
   703  			}
   704  			seatBids, extraBidderRespInfo, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, bidAdjustmentRules)
   705  			brw.bidderResponseStartTime = extraBidderRespInfo.respProcessingStartTime
   706  
   707  			// Add in time reporting
   708  			elapsed := time.Since(start)
   709  			brw.adapterSeatBids = seatBids
   710  			// Structure to record extra tracking data generated during bidding
   711  			ae := new(seatResponseExtra)
   712  			ae.ResponseTimeMillis = int(elapsed / time.Millisecond)
   713  			if len(seatBids) != 0 {
   714  				ae.HttpCalls = seatBids[0].HttpCalls
   715  			}
   716  			// Timing statistics
   717  			e.me.RecordAdapterTime(bidderRequest.BidderLabels, elapsed)
   718  			bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterSeatBids)
   719  			bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err)
   720  			// Append any bid validation errors to the error list
   721  			ae.Errors = errsToBidderErrors(err)
   722  			ae.Warnings = errsToBidderWarnings(err)
   723  			brw.adapterExtra = ae
   724  			for _, seatBid := range seatBids {
   725  				if seatBid != nil {
   726  					for _, bid := range seatBid.Bids {
   727  						var cpm = float64(bid.Bid.Price * 1000)
   728  						e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm)
   729  						e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.BidType, bid.Bid.AdM != "")
   730  					}
   731  				}
   732  			}
   733  			chBids <- brw
   734  		}, chBids)
   735  		go bidderRunner(bidder, conversions)
   736  	}
   737  
   738  	// Wait for the bidders to do their thing
   739  	for i := 0; i < len(bidderRequests); i++ {
   740  		brw := <-chBids
   741  		if !brw.bidderResponseStartTime.IsZero() {
   742  			extraRespInfo.bidderResponseStartTime = brw.bidderResponseStartTime
   743  		}
   744  		//if bidder returned no bids back - remove bidder from further processing
   745  		for _, seatBid := range brw.adapterSeatBids {
   746  			if seatBid != nil {
   747  				bidderName := openrtb_ext.BidderName(seatBid.Seat)
   748  				if len(seatBid.Bids) != 0 {
   749  					if val, ok := adapterBids[bidderName]; ok {
   750  						adapterBids[bidderName].Bids = append(val.Bids, seatBid.Bids...)
   751  					} else {
   752  						adapterBids[bidderName] = seatBid
   753  					}
   754  				}
   755  				// collect fledgeAuctionConfigs separately from bids, as empty seatBids may be discarded
   756  				extraRespInfo.fledge = collectFledgeFromSeatBid(extraRespInfo.fledge, bidderName, brw.adapter, seatBid)
   757  			}
   758  		}
   759  		//but we need to add all bidders data to adapterExtra to have metrics and other metadata
   760  		adapterExtra[brw.bidder] = brw.adapterExtra
   761  
   762  		if !extraRespInfo.bidsFound && adapterBids[brw.bidder] != nil && len(adapterBids[brw.bidder].Bids) > 0 {
   763  			extraRespInfo.bidsFound = true
   764  		}
   765  	}
   766  
   767  	return adapterBids, adapterExtra, extraRespInfo
   768  }
   769  
   770  func collectFledgeFromSeatBid(fledge *openrtb_ext.Fledge, bidderName openrtb_ext.BidderName, adapterName openrtb_ext.BidderName, seatBid *entities.PbsOrtbSeatBid) *openrtb_ext.Fledge {
   771  	if seatBid.FledgeAuctionConfigs != nil {
   772  		if fledge == nil {
   773  			fledge = &openrtb_ext.Fledge{
   774  				AuctionConfigs: make([]*openrtb_ext.FledgeAuctionConfig, 0, len(seatBid.FledgeAuctionConfigs)),
   775  			}
   776  		}
   777  		for _, config := range seatBid.FledgeAuctionConfigs {
   778  			fledge.AuctionConfigs = append(fledge.AuctionConfigs, &openrtb_ext.FledgeAuctionConfig{
   779  				Bidder:  bidderName.String(),
   780  				Adapter: config.Bidder,
   781  				ImpId:   config.ImpId,
   782  				Config:  config.Config,
   783  			})
   784  		}
   785  	}
   786  	return fledge
   787  }
   788  
   789  func (e *exchange) recoverSafely(bidderRequests []BidderRequest,
   790  	inner func(BidderRequest, currency.Conversions),
   791  	chBids chan *bidResponseWrapper) func(BidderRequest, currency.Conversions) {
   792  	return func(bidderRequest BidderRequest, conversions currency.Conversions) {
   793  		defer func() {
   794  			if r := recover(); r != nil {
   795  
   796  				allBidders := ""
   797  				sb := strings.Builder{}
   798  				for _, bidder := range bidderRequests {
   799  					sb.WriteString(bidder.BidderName.String())
   800  					sb.WriteString(",")
   801  				}
   802  				if sb.Len() > 0 {
   803  					allBidders = sb.String()[:sb.Len()-1]
   804  				}
   805  
   806  				glog.Errorf("OpenRTB auction recovered panic from Bidder %s: %v. "+
   807  					"Account id: %s, All Bidders: %s, Stack trace is: %v",
   808  					bidderRequest.BidderCoreName, r, bidderRequest.BidderLabels.PubID, allBidders, string(debug.Stack()))
   809  				e.me.RecordAdapterPanic(bidderRequest.BidderLabels)
   810  				// Let the master request know that there is no data here
   811  				brw := new(bidResponseWrapper)
   812  				brw.adapterExtra = new(seatResponseExtra)
   813  				chBids <- brw
   814  			}
   815  		}()
   816  		inner(bidderRequest, conversions)
   817  	}
   818  }
   819  
   820  func bidsToMetric(seatBids []*entities.PbsOrtbSeatBid) metrics.AdapterBid {
   821  	for _, seatBid := range seatBids {
   822  		if seatBid != nil && len(seatBid.Bids) != 0 {
   823  			return metrics.AdapterBidPresent
   824  		}
   825  	}
   826  	return metrics.AdapterBidNone
   827  }
   828  
   829  func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} {
   830  	if len(errs) == 0 {
   831  		return nil
   832  	}
   833  	ret := make(map[metrics.AdapterError]struct{}, len(errs))
   834  	var s struct{}
   835  	for _, err := range errs {
   836  		switch errortypes.ReadCode(err) {
   837  		case errortypes.TimeoutErrorCode:
   838  			ret[metrics.AdapterErrorTimeout] = s
   839  		case errortypes.BadInputErrorCode:
   840  			ret[metrics.AdapterErrorBadInput] = s
   841  		case errortypes.BadServerResponseErrorCode:
   842  			ret[metrics.AdapterErrorBadServerResponse] = s
   843  		case errortypes.FailedToRequestBidsErrorCode:
   844  			ret[metrics.AdapterErrorFailedToRequestBids] = s
   845  		case errortypes.AlternateBidderCodeWarningCode:
   846  			ret[metrics.AdapterErrorValidation] = s
   847  		default:
   848  			ret[metrics.AdapterErrorUnknown] = s
   849  		}
   850  	}
   851  	return ret
   852  }
   853  
   854  func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderMessage {
   855  	sErr := make([]openrtb_ext.ExtBidderMessage, 0)
   856  	for _, err := range errortypes.FatalOnly(errs) {
   857  		newErr := openrtb_ext.ExtBidderMessage{
   858  			Code:    errortypes.ReadCode(err),
   859  			Message: err.Error(),
   860  		}
   861  		sErr = append(sErr, newErr)
   862  	}
   863  
   864  	return sErr
   865  }
   866  
   867  func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage {
   868  	sWarn := make([]openrtb_ext.ExtBidderMessage, 0)
   869  	for _, warn := range errortypes.WarningOnly(errs) {
   870  		newErr := openrtb_ext.ExtBidderMessage{
   871  			Code:    errortypes.ReadCode(warn),
   872  			Message: warn.Error(),
   873  		}
   874  		sWarn = append(sWarn, newErr)
   875  	}
   876  	return sWarn
   877  }
   878  
   879  // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester
   880  func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, pubID string, errList []error) *openrtb2.BidResponse {
   881  	bidResponse := new(openrtb2.BidResponse)
   882  
   883  	bidResponse.ID = bidRequest.ID
   884  	if len(liveAdapters) == 0 {
   885  		// signal "Invalid Request" if no valid bidders.
   886  		bidResponse.NBR = openrtb3.NoBidInvalidRequest.Ptr()
   887  	}
   888  
   889  	// Create the SeatBids. We use a zero sized slice so that we can append non-zero seat bids, and not include seatBid
   890  	// objects for seatBids without any bids. Preallocate the max possible size to avoid reallocating the array as we go.
   891  	seatBids := make([]openrtb2.SeatBid, 0, len(liveAdapters))
   892  	for a, adapterSeatBids := range adapterSeatBids {
   893  		//while processing every single bib, do we need to handle categories here?
   894  		if adapterSeatBids != nil && len(adapterSeatBids.Bids) > 0 {
   895  			sb := e.makeSeatBid(adapterSeatBids, a, adapterExtra, auc, returnCreative, impExtInfoMap, bidResponseExt, pubID)
   896  			seatBids = append(seatBids, *sb)
   897  			bidResponse.Cur = adapterSeatBids.Currency
   898  		}
   899  	}
   900  	bidResponse.SeatBid = seatBids
   901  
   902  	return bidResponse
   903  }
   904  
   905  func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, error) {
   906  	buffer := &bytes.Buffer{}
   907  	enc := json.NewEncoder(buffer)
   908  
   909  	enc.SetEscapeHTML(false)
   910  	err := enc.Encode(bidResponseExt)
   911  
   912  	return buffer.Bytes(), err
   913  }
   914  
   915  func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestTargeting, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator, seatNonBids *nonBids) (map[string]string, map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string, error) {
   916  	res := make(map[string]string)
   917  
   918  	type bidDedupe struct {
   919  		bidderName openrtb_ext.BidderName
   920  		bidIndex   int
   921  		bidID      string
   922  		bidPrice   string
   923  	}
   924  
   925  	dedupe := make(map[string]bidDedupe)
   926  
   927  	// applyCategoryMapping doesn't get called unless
   928  	brandCatExt := targeting.IncludeBrandCategory
   929  
   930  	//If ext.prebid.targeting.includebrandcategory is present in ext then competitive exclusion feature is on.
   931  	var includeBrandCategory = brandCatExt != nil //if not present - category will no be appended
   932  	appendBidderNames := targeting.AppendBidderNames
   933  
   934  	var primaryAdServer string
   935  	var publisher string
   936  	var err error
   937  	var rejections []string
   938  	var translateCategories = true
   939  
   940  	if includeBrandCategory && brandCatExt.WithCategory {
   941  		if brandCatExt.TranslateCategories != nil {
   942  			translateCategories = *brandCatExt.TranslateCategories
   943  		}
   944  		//if translateCategories is set to false, ignore checking primaryAdServer and publisher
   945  		if translateCategories {
   946  			//if ext.prebid.targeting.includebrandcategory present but primaryadserver/publisher not present then error out the request right away.
   947  			primaryAdServer, err = getPrimaryAdServer(brandCatExt.PrimaryAdServer) //1-Freewheel 2-DFP
   948  			if err != nil {
   949  				return res, seatBids, rejections, err
   950  			}
   951  			publisher = brandCatExt.Publisher
   952  		}
   953  	}
   954  
   955  	seatBidsToRemove := make([]openrtb_ext.BidderName, 0)
   956  
   957  	for bidderName, seatBid := range seatBids {
   958  		bidsToRemove := make([]int, 0)
   959  		for bidInd := range seatBid.Bids {
   960  			bid := seatBid.Bids[bidInd]
   961  			bidID := bid.Bid.ID
   962  			var duration int
   963  			var category string
   964  			var priceBucket string
   965  
   966  			if bid.BidVideo != nil {
   967  				duration = bid.BidVideo.Duration
   968  				category = bid.BidVideo.PrimaryCategory
   969  			}
   970  			if brandCatExt.WithCategory && category == "" {
   971  				bidIabCat := bid.Bid.Cat
   972  				if len(bidIabCat) != 1 {
   973  					//TODO: add metrics
   974  					//on receiving bids from adapters if no unique IAB category is returned  or if no ad server category is returned discard the bid
   975  					bidsToRemove = append(bidsToRemove, bidInd)
   976  					rejections = updateRejections(rejections, bidID, "Bid did not contain a category")
   977  					seatNonBids.addBid(bid, int(ResponseRejectedCategoryMappingInvalid), string(bidderName))
   978  					continue
   979  				}
   980  				if translateCategories {
   981  					//if unique IAB category is present then translate it to the adserver category based on mapping file
   982  					category, err = categoriesFetcher.FetchCategories(ctx, primaryAdServer, publisher, bidIabCat[0])
   983  					if err != nil || category == "" {
   984  						//TODO: add metrics
   985  						//if mapping required but no mapping file is found then discard the bid
   986  						bidsToRemove = append(bidsToRemove, bidInd)
   987  						reason := fmt.Sprintf("Category mapping file for primary ad server: '%s', publisher: '%s' not found", primaryAdServer, publisher)
   988  						rejections = updateRejections(rejections, bidID, reason)
   989  						continue
   990  					}
   991  				} else {
   992  					//category translation is disabled, continue with IAB category
   993  					category = bidIabCat[0]
   994  				}
   995  			}
   996  
   997  			// TODO: consider should we remove bids with zero duration here?
   998  
   999  			priceBucket = GetPriceBucket(*bid.Bid, *targData)
  1000  
  1001  			newDur, err := findDurationRange(duration, targeting.DurationRangeSec)
  1002  			if err != nil {
  1003  				bidsToRemove = append(bidsToRemove, bidInd)
  1004  				rejections = updateRejections(rejections, bidID, err.Error())
  1005  				continue
  1006  			}
  1007  
  1008  			var categoryDuration string
  1009  			var dupeKey string
  1010  			if brandCatExt.WithCategory {
  1011  				categoryDuration = fmt.Sprintf("%s_%s_%ds", priceBucket, category, newDur)
  1012  				dupeKey = category
  1013  			} else {
  1014  				categoryDuration = fmt.Sprintf("%s_%ds", priceBucket, newDur)
  1015  				dupeKey = categoryDuration
  1016  			}
  1017  
  1018  			if appendBidderNames {
  1019  				categoryDuration = fmt.Sprintf("%s_%s", categoryDuration, bidderName.String())
  1020  			}
  1021  
  1022  			if dupe, ok := dedupe[dupeKey]; ok {
  1023  
  1024  				dupeBidPrice, err := strconv.ParseFloat(dupe.bidPrice, 64)
  1025  				if err != nil {
  1026  					dupeBidPrice = 0
  1027  				}
  1028  				currBidPrice, err := strconv.ParseFloat(priceBucket, 64)
  1029  				if err != nil {
  1030  					currBidPrice = 0
  1031  				}
  1032  				if dupeBidPrice == currBidPrice {
  1033  					if booleanGenerator.Generate() {
  1034  						dupeBidPrice = -1
  1035  					} else {
  1036  						currBidPrice = -1
  1037  					}
  1038  				}
  1039  
  1040  				if dupeBidPrice < currBidPrice {
  1041  					if dupe.bidderName == bidderName {
  1042  						// An older bid from the current bidder
  1043  						bidsToRemove = append(bidsToRemove, dupe.bidIndex)
  1044  						rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated")
  1045  					} else {
  1046  						// An older bid from a different seatBid we've already finished with
  1047  						oldSeatBid := (seatBids)[dupe.bidderName]
  1048  						rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated")
  1049  						if len(oldSeatBid.Bids) == 1 {
  1050  							seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName)
  1051  						} else {
  1052  							// This is a very rare, but still possible case where bid needs to be removed from already processed bidder
  1053  							// This happens when current processing bidder has a bid that has same deduplication key as a bid from already processed bidder
  1054  							// and already processed bid was selected to be removed
  1055  							// See example of input data in unit test `TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice`
  1056  							// Need to remove bid by name, not index in this case
  1057  							removeBidById(oldSeatBid, dupe.bidID)
  1058  						}
  1059  					}
  1060  					delete(res, dupe.bidID)
  1061  				} else {
  1062  					// Remove this bid
  1063  					bidsToRemove = append(bidsToRemove, bidInd)
  1064  					rejections = updateRejections(rejections, bidID, "Bid was deduplicated")
  1065  					continue
  1066  				}
  1067  			}
  1068  			res[bidID] = categoryDuration
  1069  			dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: priceBucket}
  1070  		}
  1071  
  1072  		if len(bidsToRemove) > 0 {
  1073  			sort.Ints(bidsToRemove)
  1074  			if len(bidsToRemove) == len(seatBid.Bids) {
  1075  				//if all bids are invalid - remove entire seat bid
  1076  				seatBidsToRemove = append(seatBidsToRemove, bidderName)
  1077  			} else {
  1078  				bids := seatBid.Bids
  1079  				for i := len(bidsToRemove) - 1; i >= 0; i-- {
  1080  					remInd := bidsToRemove[i]
  1081  					bids = append(bids[:remInd], bids[remInd+1:]...)
  1082  				}
  1083  				seatBid.Bids = bids
  1084  			}
  1085  		}
  1086  
  1087  	}
  1088  	for _, seatBidInd := range seatBidsToRemove {
  1089  		seatBids[seatBidInd].Bids = nil
  1090  	}
  1091  
  1092  	return res, seatBids, rejections, nil
  1093  }
  1094  
  1095  // findDurationRange returns the element in the array 'durationRanges' that is both greater than 'dur' and closest
  1096  // in value to 'dur' unless a value equal to 'dur' is found. Returns an error if all elements in 'durationRanges'
  1097  // are less than 'dur'.
  1098  func findDurationRange(dur int, durationRanges []int) (int, error) {
  1099  	newDur := dur
  1100  	madeSelection := false
  1101  	var err error
  1102  
  1103  	for i := range durationRanges {
  1104  		if dur > durationRanges[i] {
  1105  			continue
  1106  		}
  1107  		if dur == durationRanges[i] {
  1108  			return durationRanges[i], nil
  1109  		}
  1110  		// dur < durationRanges[i]
  1111  		if durationRanges[i] < newDur || !madeSelection {
  1112  			newDur = durationRanges[i]
  1113  			madeSelection = true
  1114  		}
  1115  	}
  1116  	if !madeSelection && len(durationRanges) > 0 {
  1117  		err = errors.New("bid duration exceeds maximum allowed")
  1118  	}
  1119  	return newDur, err
  1120  }
  1121  
  1122  func removeBidById(seatBid *entities.PbsOrtbSeatBid, bidID string) {
  1123  	//Find index of bid to remove
  1124  	dupeBidIndex := -1
  1125  	for i, bid := range seatBid.Bids {
  1126  		if bid.Bid.ID == bidID {
  1127  			dupeBidIndex = i
  1128  			break
  1129  		}
  1130  	}
  1131  	if dupeBidIndex != -1 {
  1132  		if dupeBidIndex < len(seatBid.Bids)-1 {
  1133  			seatBid.Bids = append(seatBid.Bids[:dupeBidIndex], seatBid.Bids[dupeBidIndex+1:]...)
  1134  		} else if dupeBidIndex == len(seatBid.Bids)-1 {
  1135  			seatBid.Bids = seatBid.Bids[:len(seatBid.Bids)-1]
  1136  		}
  1137  	}
  1138  }
  1139  
  1140  func updateRejections(rejections []string, bidID string, reason string) []string {
  1141  	message := fmt.Sprintf("bid rejected [bid ID: %s] reason: %s", bidID, reason)
  1142  	return append(rejections, message)
  1143  }
  1144  
  1145  func getPrimaryAdServer(adServerId int) (string, error) {
  1146  	switch adServerId {
  1147  	case 1:
  1148  		return "freewheel", nil
  1149  	case 2:
  1150  		return "dfp", nil
  1151  	default:
  1152  		return "", fmt.Errorf("Primary ad server %d not recognized", adServerId)
  1153  	}
  1154  }
  1155  
  1156  // Extract all the data from the SeatBids and build the ExtBidResponse
  1157  func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, passthrough json.RawMessage, fledge *openrtb_ext.Fledge, errList []error) *openrtb_ext.ExtBidResponse {
  1158  	bidResponseExt := &openrtb_ext.ExtBidResponse{
  1159  		Errors:               make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)),
  1160  		Warnings:             make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)),
  1161  		ResponseTimeMillis:   make(map[openrtb_ext.BidderName]int, len(adapterBids)),
  1162  		RequestTimeoutMillis: r.BidRequestWrapper.BidRequest.TMax,
  1163  	}
  1164  	if debugInfo {
  1165  		bidResponseExt.Debug = &openrtb_ext.ExtResponseDebug{
  1166  			HttpCalls:       make(map[openrtb_ext.BidderName][]*openrtb_ext.ExtHttpCall),
  1167  			ResolvedRequest: r.ResolvedBidRequest,
  1168  		}
  1169  	}
  1170  
  1171  	var auctionTimestamp int64
  1172  	if !r.StartTime.IsZero() {
  1173  		auctionTimestamp = r.StartTime.UnixMilli()
  1174  	}
  1175  
  1176  	if auctionTimestamp > 0 ||
  1177  		passthrough != nil ||
  1178  		fledge != nil {
  1179  		bidResponseExt.Prebid = &openrtb_ext.ExtResponsePrebid{
  1180  			AuctionTimestamp: auctionTimestamp,
  1181  			Passthrough:      passthrough,
  1182  			Fledge:           fledge,
  1183  		}
  1184  	}
  1185  
  1186  	for bidderName, responseExtra := range adapterExtra {
  1187  
  1188  		if debugInfo && len(responseExtra.HttpCalls) > 0 {
  1189  			bidResponseExt.Debug.HttpCalls[bidderName] = responseExtra.HttpCalls
  1190  		}
  1191  		if len(responseExtra.Warnings) > 0 {
  1192  			bidResponseExt.Warnings[bidderName] = responseExtra.Warnings
  1193  		}
  1194  		// Only make an entry for bidder errors if the bidder reported any.
  1195  		if len(responseExtra.Errors) > 0 {
  1196  			bidResponseExt.Errors[bidderName] = responseExtra.Errors
  1197  		}
  1198  		if len(errList) > 0 {
  1199  			bidResponseExt.Errors[openrtb_ext.PrebidExtKey] = errsToBidderErrors(errList)
  1200  			if prebidWarn := errsToBidderWarnings(errList); len(prebidWarn) > 0 {
  1201  				bidResponseExt.Warnings[openrtb_ext.PrebidExtKey] = prebidWarn
  1202  			}
  1203  		}
  1204  		bidResponseExt.ResponseTimeMillis[bidderName] = responseExtra.ResponseTimeMillis
  1205  		// Defering the filling of bidResponseExt.Usersync[bidderName] until later
  1206  
  1207  	}
  1208  
  1209  	return bidResponseExt
  1210  }
  1211  
  1212  // Return an openrtb seatBid for a bidder
  1213  // buildBidResponse is responsible for ensuring nil bid seatbids are not included
  1214  func (e *exchange) makeSeatBid(adapterBid *entities.PbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidResponseExt *openrtb_ext.ExtBidResponse, pubID string) *openrtb2.SeatBid {
  1215  	seatBid := &openrtb2.SeatBid{
  1216  		Seat:  adapter.String(),
  1217  		Group: 0, // Prebid cannot support roadblocking
  1218  	}
  1219  
  1220  	var errList []error
  1221  	seatBid.Bid, errList = e.makeBid(adapterBid.Bids, auc, returnCreative, impExtInfoMap, bidResponseExt, adapter, pubID)
  1222  	if len(errList) > 0 {
  1223  		adapterExtra[adapter].Errors = append(adapterExtra[adapter].Errors, errsToBidderErrors(errList)...)
  1224  	}
  1225  
  1226  	return seatBid
  1227  }
  1228  
  1229  func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidResponseExt *openrtb_ext.ExtBidResponse, adapter openrtb_ext.BidderName, pubID string) ([]openrtb2.Bid, []error) {
  1230  	result := make([]openrtb2.Bid, 0, len(bids))
  1231  	errs := make([]error, 0, 1)
  1232  
  1233  	for _, bid := range bids {
  1234  		if e.bidValidationEnforcement.BannerCreativeMaxSize == config.ValidationEnforce && bid.BidType == openrtb_ext.BidTypeBanner {
  1235  			if !e.validateBannerCreativeSize(bid, bidResponseExt, adapter, pubID, e.bidValidationEnforcement.BannerCreativeMaxSize) {
  1236  				continue // Don't add bid to result
  1237  			}
  1238  		} else if e.bidValidationEnforcement.BannerCreativeMaxSize == config.ValidationWarn && bid.BidType == openrtb_ext.BidTypeBanner {
  1239  			e.validateBannerCreativeSize(bid, bidResponseExt, adapter, pubID, e.bidValidationEnforcement.BannerCreativeMaxSize)
  1240  		}
  1241  		if _, ok := impExtInfoMap[bid.Bid.ImpID]; ok {
  1242  			if e.bidValidationEnforcement.SecureMarkup == config.ValidationEnforce && (bid.BidType == openrtb_ext.BidTypeBanner || bid.BidType == openrtb_ext.BidTypeVideo) {
  1243  				if !e.validateBidAdM(bid, bidResponseExt, adapter, pubID, e.bidValidationEnforcement.SecureMarkup) {
  1244  					continue // Don't add bid to result
  1245  				}
  1246  			} else if e.bidValidationEnforcement.SecureMarkup == config.ValidationWarn && (bid.BidType == openrtb_ext.BidTypeBanner || bid.BidType == openrtb_ext.BidTypeVideo) {
  1247  				e.validateBidAdM(bid, bidResponseExt, adapter, pubID, e.bidValidationEnforcement.SecureMarkup)
  1248  			}
  1249  
  1250  		}
  1251  		bidExtPrebid := &openrtb_ext.ExtBidPrebid{
  1252  			DealPriority:      bid.DealPriority,
  1253  			DealTierSatisfied: bid.DealTierSatisfied,
  1254  			Events:            bid.BidEvents,
  1255  			Targeting:         bid.BidTargets,
  1256  			Floors:            bid.BidFloors,
  1257  			Type:              bid.BidType,
  1258  			Meta:              bid.BidMeta,
  1259  			Video:             bid.BidVideo,
  1260  			BidId:             bid.GeneratedBidID,
  1261  			TargetBidderCode:  bid.TargetBidderCode,
  1262  		}
  1263  
  1264  		if cacheInfo, found := e.getBidCacheInfo(bid, auc); found {
  1265  			bidExtPrebid.Cache = &openrtb_ext.ExtBidPrebidCache{
  1266  				Bids: &cacheInfo,
  1267  			}
  1268  		}
  1269  
  1270  		if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur); err != nil {
  1271  			errs = append(errs, err)
  1272  		} else {
  1273  			result = append(result, *bid.Bid)
  1274  			resultBid := &result[len(result)-1]
  1275  			resultBid.Ext = bidExtJSON
  1276  			if !returnCreative {
  1277  				resultBid.AdM = ""
  1278  			}
  1279  		}
  1280  	}
  1281  	return result, errs
  1282  }
  1283  
  1284  func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string, originalBidCpm float64, originalBidCur string) (json.RawMessage, error) {
  1285  	var extMap map[string]interface{}
  1286  
  1287  	if len(ext) != 0 {
  1288  		if err := json.Unmarshal(ext, &extMap); err != nil {
  1289  			return nil, err
  1290  		}
  1291  	} else {
  1292  		extMap = make(map[string]interface{})
  1293  	}
  1294  
  1295  	//ext.origbidcpm
  1296  	if originalBidCpm >= 0 {
  1297  		extMap[openrtb_ext.OriginalBidCpmKey] = originalBidCpm
  1298  	}
  1299  
  1300  	//ext.origbidcur
  1301  	if originalBidCur != "" {
  1302  		extMap[openrtb_ext.OriginalBidCurKey] = originalBidCur
  1303  	}
  1304  
  1305  	// ext.prebid
  1306  	if prebid.Meta == nil && maputil.HasElement(extMap, "prebid", "meta") {
  1307  		metaContainer := struct {
  1308  			Prebid struct {
  1309  				Meta openrtb_ext.ExtBidPrebidMeta `json:"meta"`
  1310  			} `json:"prebid"`
  1311  		}{}
  1312  		if err := json.Unmarshal(ext, &metaContainer); err != nil {
  1313  			return nil, fmt.Errorf("error validaing response from server, %s", err)
  1314  		}
  1315  		prebid.Meta = &metaContainer.Prebid.Meta
  1316  	}
  1317  
  1318  	// ext.prebid.storedrequestattributes and ext.prebid.passthrough
  1319  	if impExtInfo, ok := impExtInfoMap[impId]; ok {
  1320  		prebid.Passthrough = impExtInfoMap[impId].Passthrough
  1321  		if impExtInfo.EchoVideoAttrs {
  1322  			videoData, _, _, err := jsonparser.Get(impExtInfo.StoredImp, "video")
  1323  			if err != nil && err != jsonparser.KeyPathNotFoundError {
  1324  				return nil, err
  1325  			}
  1326  			//handler for case where EchoVideoAttrs is true, but video data is not found
  1327  			if len(videoData) > 0 {
  1328  				extMap[openrtb_ext.StoredRequestAttributes] = json.RawMessage(videoData)
  1329  			}
  1330  		}
  1331  	}
  1332  	extMap[openrtb_ext.PrebidExtKey] = prebid
  1333  	return json.Marshal(extMap)
  1334  }
  1335  
  1336  // If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb2.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`,
  1337  // a UUID should be found inside `a.cacheIds` or `a.vastCacheIds`. This function returns the UUID along with the internal cache URL
  1338  func (e *exchange) getBidCacheInfo(bid *entities.PbsOrtbBid, auction *auction) (cacheInfo openrtb_ext.ExtBidPrebidCacheBids, found bool) {
  1339  	uuid, found := findCacheID(bid, auction)
  1340  
  1341  	if found {
  1342  		cacheInfo.CacheId = uuid
  1343  		cacheInfo.Url = buildCacheURL(e.cache, uuid)
  1344  	}
  1345  
  1346  	return
  1347  }
  1348  
  1349  func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestCurrency) currency.Conversions {
  1350  	if requestRates == nil {
  1351  		// No bidRequest.ext.currency field was found, use PBS rates as usual
  1352  		return e.currencyConverter.Rates()
  1353  	}
  1354  
  1355  	// If bidRequest.ext.currency.usepbsrates is nil, we understand its value as true. It will be false
  1356  	// only if it's explicitly set to false
  1357  	usePbsRates := requestRates.UsePBSRates == nil || *requestRates.UsePBSRates
  1358  
  1359  	if !usePbsRates {
  1360  		// At this point, we can safely assume the ConversionRates map is not empty because
  1361  		// validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) would have
  1362  		// thrown an error under such conditions.
  1363  		return currency.NewRates(requestRates.ConversionRates)
  1364  	}
  1365  
  1366  	// Both PBS and custom rates can be used, check if ConversionRates is not empty
  1367  	if len(requestRates.ConversionRates) == 0 {
  1368  		// Custom rates map is empty, use PBS rates only
  1369  		return e.currencyConverter.Rates()
  1370  	}
  1371  
  1372  	// Return an AggregateConversions object that includes both custom and PBS currency rates but will
  1373  	// prioritize custom rates over PBS rates whenever a currency rate is found in both
  1374  	return currency.NewAggregateConversions(currency.NewRates(requestRates.ConversionRates), e.currencyConverter.Rates())
  1375  }
  1376  
  1377  func findCacheID(bid *entities.PbsOrtbBid, auction *auction) (string, bool) {
  1378  	if bid != nil && bid.Bid != nil && auction != nil {
  1379  		if id, found := auction.cacheIds[bid.Bid]; found {
  1380  			return id, true
  1381  		}
  1382  
  1383  		if id, found := auction.vastCacheIds[bid.Bid]; found {
  1384  			return id, true
  1385  		}
  1386  	}
  1387  
  1388  	return "", false
  1389  }
  1390  
  1391  func buildCacheURL(cache prebid_cache_client.Client, uuid string) string {
  1392  	scheme, host, path := cache.GetExtCacheData()
  1393  
  1394  	if host == "" || path == "" {
  1395  		return ""
  1396  	}
  1397  
  1398  	query := url.Values{"uuid": []string{uuid}}
  1399  	cacheURL := url.URL{
  1400  		Scheme:   scheme,
  1401  		Host:     host,
  1402  		Path:     path,
  1403  		RawQuery: query.Encode(),
  1404  	}
  1405  	cacheURL.Query()
  1406  
  1407  	// URLs without a scheme will begin with //, in which case we
  1408  	// want to trim it off to keep compatbile with current behavior.
  1409  	return strings.TrimPrefix(cacheURL.String(), "//")
  1410  }
  1411  
  1412  func listBiddersWithRequests(bidderRequests []BidderRequest) []openrtb_ext.BidderName {
  1413  	liveAdapters := make([]openrtb_ext.BidderName, len(bidderRequests))
  1414  	i := 0
  1415  	for _, bidderRequest := range bidderRequests {
  1416  		liveAdapters[i] = bidderRequest.BidderName
  1417  		i++
  1418  	}
  1419  	// Randomize the list of adapters to make the auction more fair
  1420  	randomizeList(liveAdapters)
  1421  
  1422  	return liveAdapters
  1423  }
  1424  
  1425  func buildStoredAuctionResponse(storedAuctionResponses map[string]json.RawMessage) (
  1426  	map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
  1427  	*openrtb_ext.Fledge,
  1428  	[]openrtb_ext.BidderName,
  1429  	error) {
  1430  
  1431  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, 0)
  1432  	var fledge *openrtb_ext.Fledge
  1433  	liveAdapters := make([]openrtb_ext.BidderName, 0)
  1434  	for impId, storedResp := range storedAuctionResponses {
  1435  		var seatBids []openrtb2.SeatBid
  1436  
  1437  		if err := json.Unmarshal(storedResp, &seatBids); err != nil {
  1438  			return nil, nil, nil, err
  1439  		}
  1440  		for _, seat := range seatBids {
  1441  			var bidsToAdd []*entities.PbsOrtbBid
  1442  			//set imp id from request
  1443  			for i := range seat.Bid {
  1444  				seat.Bid[i].ImpID = impId
  1445  				bidType, err := getMediaTypeForBid(seat.Bid[i])
  1446  				if err != nil {
  1447  					return nil, nil, nil, err
  1448  				}
  1449  				bidsToAdd = append(bidsToAdd, &entities.PbsOrtbBid{Bid: &seat.Bid[i], BidType: bidType})
  1450  			}
  1451  
  1452  			bidderName := openrtb_ext.BidderName(seat.Seat)
  1453  
  1454  			if seat.Ext != nil {
  1455  				var seatExt openrtb_ext.ExtBidResponse
  1456  				if err := json.Unmarshal(seat.Ext, &seatExt); err != nil {
  1457  					return nil, nil, nil, err
  1458  				}
  1459  				// add in FLEDGE response with impId substituted
  1460  				if seatExt.Prebid != nil &&
  1461  					seatExt.Prebid.Fledge != nil &&
  1462  					seatExt.Prebid.Fledge.AuctionConfigs != nil {
  1463  					auctionConfigs := seatExt.Prebid.Fledge.AuctionConfigs
  1464  					if fledge == nil {
  1465  						fledge = &openrtb_ext.Fledge{
  1466  							AuctionConfigs: make([]*openrtb_ext.FledgeAuctionConfig, 0, len(auctionConfigs)),
  1467  						}
  1468  					}
  1469  					for _, config := range auctionConfigs {
  1470  						newConfig := &openrtb_ext.FledgeAuctionConfig{
  1471  							ImpId:   impId,
  1472  							Bidder:  string(bidderName),
  1473  							Adapter: string(bidderName),
  1474  							Config:  config.Config,
  1475  						}
  1476  						fledge.AuctionConfigs = append(fledge.AuctionConfigs, newConfig)
  1477  					}
  1478  				}
  1479  			}
  1480  
  1481  			if _, ok := adapterBids[bidderName]; ok {
  1482  				adapterBids[bidderName].Bids = append(adapterBids[bidderName].Bids, bidsToAdd...)
  1483  
  1484  			} else {
  1485  				//create new seat bid and add it to live adapters
  1486  				liveAdapters = append(liveAdapters, bidderName)
  1487  				newSeatBid := entities.PbsOrtbSeatBid{
  1488  					Bids:     bidsToAdd,
  1489  					Currency: "",
  1490  					Seat:     "",
  1491  				}
  1492  				adapterBids[bidderName] = &newSeatBid
  1493  
  1494  			}
  1495  		}
  1496  	}
  1497  
  1498  	return adapterBids, fledge, liveAdapters, nil
  1499  }
  1500  
  1501  func isAdsCertEnabled(experiment *openrtb_ext.Experiment, info config.BidderInfo) bool {
  1502  	requestAdsCertEnabled := experiment != nil && experiment.AdsCert != nil && experiment.AdsCert.Enabled
  1503  	bidderAdsCertEnabled := info.Experiment.AdsCert.Enabled
  1504  	return requestAdsCertEnabled && bidderAdsCertEnabled
  1505  }
  1506  
  1507  func (e exchange) validateBannerCreativeSize(bid *entities.PbsOrtbBid, bidResponseExt *openrtb_ext.ExtBidResponse, adapter openrtb_ext.BidderName, pubID string, validationType string) bool {
  1508  	if bid.Bid.W > e.bidValidationEnforcement.MaxCreativeWidth || bid.Bid.H > e.bidValidationEnforcement.MaxCreativeHeight {
  1509  		// Add error to debug array
  1510  		errorMessage := setErrorMessageCreativeSize(validationType)
  1511  		bidCreativeMaxSizeError := openrtb_ext.ExtBidderMessage{
  1512  			Code:    errortypes.BadServerResponseErrorCode,
  1513  			Message: errorMessage,
  1514  		}
  1515  		bidResponseExt.Errors[adapter] = append(bidResponseExt.Errors[adapter], bidCreativeMaxSizeError)
  1516  
  1517  		// Log Metrics
  1518  		e.me.RecordBidValidationCreativeSizeError(adapter, pubID)
  1519  
  1520  		return false
  1521  	}
  1522  	return true
  1523  }
  1524  
  1525  func (e exchange) validateBidAdM(bid *entities.PbsOrtbBid, bidResponseExt *openrtb_ext.ExtBidResponse, adapter openrtb_ext.BidderName, pubID string, validationType string) bool {
  1526  	invalidAdM := []string{"http:", "http%3A"}
  1527  	requiredAdM := []string{"https:", "https%3A"}
  1528  
  1529  	if (strings.Contains(bid.Bid.AdM, invalidAdM[0]) || strings.Contains(bid.Bid.AdM, invalidAdM[1])) && (!strings.Contains(bid.Bid.AdM, requiredAdM[0]) && !strings.Contains(bid.Bid.AdM, requiredAdM[1])) {
  1530  		// Add error to debug array
  1531  		errorMessage := setErrorMessageSecureMarkup(validationType)
  1532  		bidSecureMarkupError := openrtb_ext.ExtBidderMessage{
  1533  			Code:    errortypes.BadServerResponseErrorCode,
  1534  			Message: errorMessage,
  1535  		}
  1536  		bidResponseExt.Errors[adapter] = append(bidResponseExt.Errors[adapter], bidSecureMarkupError)
  1537  
  1538  		// Log Metrics
  1539  		e.me.RecordBidValidationSecureMarkupError(adapter, pubID)
  1540  
  1541  		return false
  1542  	}
  1543  	return true
  1544  }
  1545  
  1546  func setErrorMessageCreativeSize(validationType string) string {
  1547  	if validationType == config.ValidationEnforce {
  1548  		return "bidResponse rejected: size WxH"
  1549  	} else if validationType == config.ValidationWarn {
  1550  		return "bidResponse creative size warning: size WxH larger than AdUnit sizes"
  1551  	}
  1552  	return ""
  1553  }
  1554  
  1555  func setErrorMessageSecureMarkup(validationType string) string {
  1556  	if validationType == config.ValidationEnforce {
  1557  		return "bidResponse rejected: insecure creative in secure context"
  1558  	} else if validationType == config.ValidationWarn {
  1559  		return "bidResponse secure markup warning: insecure creative in secure contexts"
  1560  	}
  1561  	return ""
  1562  }
  1563  
  1564  // setSeatNonBid adds SeatNonBids within bidResponse.Ext.Prebid.SeatNonBid
  1565  func setSeatNonBid(bidResponseExt *openrtb_ext.ExtBidResponse, seatNonBids nonBids) *openrtb_ext.ExtBidResponse {
  1566  	if len(seatNonBids.seatNonBidsMap) == 0 {
  1567  		return bidResponseExt
  1568  	}
  1569  	if bidResponseExt == nil {
  1570  		bidResponseExt = &openrtb_ext.ExtBidResponse{}
  1571  	}
  1572  	if bidResponseExt.Prebid == nil {
  1573  		bidResponseExt.Prebid = &openrtb_ext.ExtResponsePrebid{}
  1574  	}
  1575  
  1576  	bidResponseExt.Prebid.SeatNonBid = seatNonBids.get()
  1577  	return bidResponseExt
  1578  }