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