github.com/prebid/prebid-server/v2@v2.18.0/adapters/huaweiads/huaweiads.go (about)

     1  package huaweiads
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/hmac"
     6  	"crypto/sha256"
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"math"
    12  	"net/http"
    13  	"net/url"
    14  	"regexp"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/prebid/openrtb/v20/native1"
    20  	nativeRequests "github.com/prebid/openrtb/v20/native1/request"
    21  	nativeResponse "github.com/prebid/openrtb/v20/native1/response"
    22  	"github.com/prebid/openrtb/v20/openrtb2"
    23  	"github.com/prebid/prebid-server/v2/adapters"
    24  	"github.com/prebid/prebid-server/v2/config"
    25  	"github.com/prebid/prebid-server/v2/errortypes"
    26  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    27  	"github.com/prebid/prebid-server/v2/util/ptrutil"
    28  )
    29  
    30  const huaweiAdxApiVersion = "3.4"
    31  const defaultCountryName = "ZA"
    32  const defaultUnknownNetworkType = 0
    33  const timeFormat = "2006-01-02 15:04:05.000"
    34  const defaultTimeZone = "+0200"
    35  const defaultModelName = "HUAWEI"
    36  const chineseSiteEndPoint = "https://acd.op.hicloud.com/ppsadx/getResult"
    37  const europeanSiteEndPoint = "https://adx-dre.op.hicloud.com/ppsadx/getResult"
    38  const asianSiteEndPoint = "https://adx-dra.op.hicloud.com/ppsadx/getResult"
    39  const russianSiteEndPoint = "https://adx-drru.op.hicloud.com/ppsadx/getResult"
    40  
    41  // creative type
    42  const (
    43  	text                   int32 = 1
    44  	bigPicture             int32 = 2
    45  	bigPicture2            int32 = 3
    46  	gif                    int32 = 4
    47  	videoText              int32 = 6
    48  	smallPicture           int32 = 7
    49  	threeSmallPicturesText int32 = 8
    50  	video                  int32 = 9
    51  	iconText               int32 = 10
    52  	videoWithPicturesText  int32 = 11
    53  )
    54  
    55  // interaction type
    56  const (
    57  	appPromotion int32 = 3
    58  )
    59  
    60  // ads type
    61  const (
    62  	banner       int32 = 8
    63  	native       int32 = 3
    64  	roll         int32 = 60
    65  	interstitial int32 = 12
    66  	rewarded     int32 = 7
    67  	splash       int32 = 1
    68  	magazinelock int32 = 2
    69  	audio        int32 = 17
    70  )
    71  
    72  type huaweiAdsRequest struct {
    73  	Version           string     `json:"version"`
    74  	Multislot         []adslot30 `json:"multislot"`
    75  	App               app        `json:"app"`
    76  	Device            device     `json:"device"`
    77  	Network           network    `json:"network,omitempty"`
    78  	Regs              regs       `json:"regs,omitempty"`
    79  	Geo               geo        `json:"geo,omitempty"`
    80  	Consent           string     `json:"consent,omitempty"`
    81  	ClientAdRequestId string     `json:"clientAdRequestId,omitempty"`
    82  }
    83  
    84  type adslot30 struct {
    85  	Slotid                   string   `json:"slotid"`
    86  	Adtype                   int32    `json:"adtype"`
    87  	Test                     int32    `json:"test"`
    88  	TotalDuration            int32    `json:"totalDuration,omitempty"`
    89  	Orientation              int32    `json:"orientation,omitempty"`
    90  	W                        int64    `json:"w,omitempty"`
    91  	H                        int64    `json:"h,omitempty"`
    92  	Format                   []format `json:"format,omitempty"`
    93  	DetailedCreativeTypeList []string `json:"detailedCreativeTypeList,omitempty"`
    94  }
    95  
    96  type format struct {
    97  	W int64 `json:"w,omitempty"`
    98  	H int64 `json:"h,omitempty"`
    99  }
   100  
   101  type app struct {
   102  	Version string `json:"version,omitempty"`
   103  	Name    string `json:"name,omitempty"`
   104  	Pkgname string `json:"pkgname"`
   105  	Lang    string `json:"lang,omitempty"`
   106  	Country string `json:"country,omitempty"`
   107  }
   108  
   109  type device struct {
   110  	Type                int32   `json:"type,omitempty"`
   111  	Useragent           string  `json:"useragent,omitempty"`
   112  	Os                  string  `json:"os,omitempty"`
   113  	Version             string  `json:"version,omitempty"`
   114  	Maker               string  `json:"maker,omitempty"`
   115  	Model               string  `json:"model,omitempty"`
   116  	Width               int32   `json:"width,omitempty"`
   117  	Height              int32   `json:"height,omitempty"`
   118  	Language            string  `json:"language,omitempty"`
   119  	BuildVersion        string  `json:"buildVersion,omitempty"`
   120  	Dpi                 int32   `json:"dpi,omitempty"`
   121  	Pxratio             float32 `json:"pxratio,omitempty"`
   122  	Imei                string  `json:"imei,omitempty"`
   123  	Oaid                string  `json:"oaid,omitempty"`
   124  	IsTrackingEnabled   string  `json:"isTrackingEnabled,omitempty"`
   125  	EmuiVer             string  `json:"emuiVer,omitempty"`
   126  	LocaleCountry       string  `json:"localeCountry"`
   127  	BelongCountry       string  `json:"belongCountry"`
   128  	GaidTrackingEnabled string  `json:"gaidTrackingEnabled,omitempty"`
   129  	Gaid                string  `json:"gaid,omitempty"`
   130  	ClientTime          string  `json:"clientTime"`
   131  	Ip                  string  `json:"ip,omitempty"`
   132  }
   133  
   134  type network struct {
   135  	Type     int32      `json:"type"`
   136  	Carrier  int32      `json:"carrier,omitempty"`
   137  	CellInfo []cellInfo `json:"cellInfo,omitempty"`
   138  }
   139  
   140  type regs struct {
   141  	Coppa int32 `json:"coppa,omitempty"`
   142  }
   143  
   144  type geo struct {
   145  	Lon      float32 `json:"lon,omitempty"`
   146  	Lat      float32 `json:"lat,omitempty"`
   147  	Accuracy int32   `json:"accuracy,omitempty"`
   148  	Lastfix  int32   `json:"lastfix,omitempty"`
   149  }
   150  
   151  type cellInfo struct {
   152  	Mcc string `json:"mcc,omitempty"`
   153  	Mnc string `json:"mnc,omitempty"`
   154  }
   155  
   156  type huaweiAdsResponse struct {
   157  	Retcode int32  `json:"retcode"`
   158  	Reason  string `json:"reason"`
   159  	Multiad []ad30 `json:"multiad"`
   160  }
   161  
   162  type ad30 struct {
   163  	AdType    int32     `json:"adtype"`
   164  	Slotid    string    `json:"slotid"`
   165  	Retcode30 int32     `json:"retcode30"`
   166  	Content   []content `json:"content"`
   167  }
   168  
   169  type content struct {
   170  	Contentid       string    `json:"contentid"`
   171  	Interactiontype int32     `json:"interactiontype"`
   172  	Creativetype    int32     `json:"creativetype"`
   173  	MetaData        metaData  `json:"metaData"`
   174  	Monitor         []monitor `json:"monitor"`
   175  	Cur             string    `json:"cur"`
   176  	Price           float64   `json:"price"`
   177  }
   178  
   179  type metaData struct {
   180  	Title       string      `json:"title"`
   181  	Description string      `json:"description"`
   182  	ImageInfo   []imageInfo `json:"imageInfo"`
   183  	Icon        []icon      `json:"icon"`
   184  	ClickUrl    string      `json:"clickUrl"`
   185  	Intent      string      `json:"intent"`
   186  	VideoInfo   videoInfo   `json:"videoInfo"`
   187  	ApkInfo     apkInfo     `json:"apkInfo"`
   188  	Duration    int64       `json:"duration"`
   189  	MediaFile   mediaFile   `json:"mediaFile"`
   190  	Cta         string      `json:"cta"`
   191  }
   192  
   193  type imageInfo struct {
   194  	Url       string `json:"url"`
   195  	Height    int64  `json:"height"`
   196  	FileSize  int64  `json:"fileSize"`
   197  	Sha256    string `json:"sha256"`
   198  	ImageType string `json:"imageType"`
   199  	Width     int64  `json:"width"`
   200  }
   201  
   202  type icon struct {
   203  	Url       string `json:"url"`
   204  	Height    int64  `json:"height"`
   205  	FileSize  int64  `json:"fileSize"`
   206  	Sha256    string `json:"sha256"`
   207  	ImageType string `json:"imageType"`
   208  	Width     int64  `json:"width"`
   209  }
   210  
   211  type videoInfo struct {
   212  	VideoDownloadUrl string  `json:"videoDownloadUrl"`
   213  	VideoDuration    int32   `json:"videoDuration"`
   214  	VideoFileSize    int32   `json:"videoFileSize"`
   215  	Sha256           string  `json:"sha256"`
   216  	VideoRatio       float32 `json:"videoRatio"`
   217  	Width            int32   `json:"width"`
   218  	Height           int32   `json:"height"`
   219  }
   220  
   221  type apkInfo struct {
   222  	Url         string `json:"url"`
   223  	FileSize    int64  `json:"fileSize"`
   224  	Sha256      string `json:"sha256"`
   225  	PackageName string `json:"packageName"`
   226  	SecondUrl   string `json:"secondUrl"`
   227  	AppName     string `json:"appName"`
   228  	VersionName string `json:"versionName"`
   229  	AppDesc     string `json:"appDesc"`
   230  	AppIcon     string `json:"appIcon"`
   231  }
   232  
   233  type mediaFile struct {
   234  	Mime     string `json:"mime"`
   235  	Width    int64  `json:"width"`
   236  	Height   int64  `json:"height"`
   237  	FileSize int64  `json:"fileSize"`
   238  	Url      string `json:"url"`
   239  	Sha256   string `json:"sha256"`
   240  }
   241  
   242  type monitor struct {
   243  	EventType string   `json:"eventType"`
   244  	Url       []string `json:"url"`
   245  }
   246  
   247  type adapter struct {
   248  	endpoint  string
   249  	extraInfo ExtraInfo
   250  }
   251  
   252  type ExtraInfo struct {
   253  	PkgNameConvert              []pkgNameConvert `json:"pkgNameConvert,omitempty"`
   254  	CloseSiteSelectionByCountry string           `json:"closeSiteSelectionByCountry,omitempty"`
   255  }
   256  
   257  type pkgNameConvert struct {
   258  	ConvertedPkgName           string   `json:"convertedPkgName,omitempty"`
   259  	UnconvertedPkgNames        []string `json:"unconvertedPkgNames,omitempty"`
   260  	UnconvertedPkgNameKeyWords []string `json:"unconvertedPkgNameKeyWords,omitempty"`
   261  	UnconvertedPkgNamePrefixs  []string `json:"unconvertedPkgNamePrefixs,omitempty"`
   262  	ExceptionPkgNames          []string `json:"exceptionPkgNames,omitempty"`
   263  }
   264  
   265  type empty struct {
   266  }
   267  
   268  func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest,
   269  	reqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) {
   270  	// the upstream code already confirms that there is a non-zero number of impressions
   271  	numRequests := len(openRTBRequest.Imp)
   272  	var request huaweiAdsRequest
   273  	var header http.Header
   274  	var multislot = make([]adslot30, 0, numRequests)
   275  
   276  	var huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds
   277  	for index := 0; index < numRequests; index++ {
   278  		var err1 error
   279  		huaweiAdsImpExt, err1 = unmarshalExtImpHuaweiAds(&openRTBRequest.Imp[index])
   280  		if err1 != nil {
   281  			return nil, []error{err1}
   282  		}
   283  
   284  		if huaweiAdsImpExt == nil {
   285  			return nil, []error{errors.New("Unmarshal ExtImpHuaweiAds failed: huaweiAdsImpExt is nil.")}
   286  		}
   287  
   288  		adslot30, err := getReqAdslot30(huaweiAdsImpExt, &openRTBRequest.Imp[index])
   289  		if err != nil {
   290  			return nil, []error{err}
   291  		}
   292  
   293  		multislot = append(multislot, adslot30)
   294  	}
   295  	request.Multislot = multislot
   296  	request.ClientAdRequestId = openRTBRequest.ID
   297  
   298  	countryCode, err := getReqJson(&request, openRTBRequest, a.extraInfo)
   299  	if err != nil {
   300  		return nil, []error{err}
   301  	}
   302  
   303  	reqJSON, err := json.Marshal(request)
   304  	if err != nil {
   305  		return nil, []error{err}
   306  	}
   307  
   308  	//	our request header's Authorization is changing by time, cannot verify by a certain string,
   309  	//	use isTestAuthorization = true only when run testcase
   310  	var isTestAuthorization = false
   311  	if huaweiAdsImpExt != nil && huaweiAdsImpExt.IsTestAuthorization == "true" {
   312  		isTestAuthorization = true
   313  	}
   314  	header = getHeaders(huaweiAdsImpExt, openRTBRequest, isTestAuthorization)
   315  	bidRequest := &adapters.RequestData{
   316  		Method:  http.MethodPost,
   317  		Uri:     getFinalEndPoint(countryCode, a.endpoint, a.extraInfo),
   318  		Body:    reqJSON,
   319  		Headers: header,
   320  		ImpIDs:  openrtb_ext.GetImpIDs(openRTBRequest.Imp),
   321  	}
   322  
   323  	return []*adapters.RequestData{bidRequest}, nil
   324  }
   325  
   326  // countryCode is alpha2, choose the corresponding site end point
   327  func getFinalEndPoint(countryCode string, defaultEndpoint string, extraInfo ExtraInfo) string {
   328  	// closeSiteSelectionByCountry == 1, close site selection, use the defaultEndpoint
   329  	if "1" == extraInfo.CloseSiteSelectionByCountry {
   330  		return defaultEndpoint
   331  	}
   332  
   333  	if countryCode == "" || len(countryCode) > 2 {
   334  		return defaultEndpoint
   335  	}
   336  	var europeanSiteCountryCodeGroup = map[string]empty{"AX": {}, "AL": {}, "AD": {}, "AU": {}, "AT": {}, "BE": {},
   337  		"BA": {}, "BG": {}, "CA": {}, "HR": {}, "CY": {}, "CZ": {}, "DK": {}, "EE": {}, "FO": {}, "FI": {},
   338  		"FR": {}, "DE": {}, "GI": {}, "GR": {}, "GL": {}, "GG": {}, "VA": {}, "HU": {}, "IS": {}, "IE": {},
   339  		"IM": {}, "IL": {}, "IT": {}, "JE": {}, "YK": {}, "LV": {}, "LI": {}, "LT": {}, "LU": {}, "MT": {},
   340  		"MD": {}, "MC": {}, "ME": {}, "NL": {}, "AN": {}, "NZ": {}, "NO": {}, "PL": {}, "PT": {}, "RO": {},
   341  		"MF": {}, "VC": {}, "SM": {}, "RS": {}, "SX": {}, "SK": {}, "SI": {}, "ES": {}, "SE": {}, "CH": {},
   342  		"TR": {}, "UA": {}, "GB": {}, "US": {}, "MK": {}, "SJ": {}, "BQ": {}, "PM": {}, "CW": {}}
   343  	var russianSiteCountryCodeGroup = map[string]empty{"RU": {}}
   344  	var chineseSiteCountryCodeGroup = map[string]empty{"CN": {}}
   345  	// choose site
   346  	if _, exists := chineseSiteCountryCodeGroup[countryCode]; exists {
   347  		return chineseSiteEndPoint
   348  	} else if _, exists := russianSiteCountryCodeGroup[countryCode]; exists {
   349  		return russianSiteEndPoint
   350  	} else if _, exists := europeanSiteCountryCodeGroup[countryCode]; exists {
   351  		return europeanSiteEndPoint
   352  	} else {
   353  		return asianSiteEndPoint
   354  	}
   355  }
   356  
   357  func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData,
   358  	bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) {
   359  	httpStatusError := checkRespStatusCode(bidderRawResponse)
   360  	if httpStatusError != nil {
   361  		return nil, []error{httpStatusError}
   362  	}
   363  
   364  	var huaweiAdsResponse huaweiAdsResponse
   365  	if err := json.Unmarshal(bidderRawResponse.Body, &huaweiAdsResponse); err != nil {
   366  		return nil, []error{&errortypes.BadServerResponse{
   367  			Message: "Unable to parse server response",
   368  		}}
   369  	}
   370  
   371  	if err := checkHuaweiAdsResponseRetcode(huaweiAdsResponse); err != nil {
   372  		return nil, []error{err}
   373  	}
   374  
   375  	bidderResponse, err := a.convertHuaweiAdsRespToBidderResp(&huaweiAdsResponse, openRTBRequest)
   376  	if err != nil {
   377  		return nil, []error{err}
   378  	}
   379  
   380  	return bidderResponse, nil
   381  }
   382  
   383  // Builder builds a new instance of the HuaweiAds adapter for the given bidder with the given config.
   384  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   385  	extraInfo, err := getExtraInfo(config.ExtraAdapterInfo)
   386  	if err != nil {
   387  		return nil, err
   388  	}
   389  
   390  	bidder := &adapter{
   391  		endpoint:  config.Endpoint,
   392  		extraInfo: extraInfo,
   393  	}
   394  	return bidder, nil
   395  }
   396  
   397  func getExtraInfo(v string) (ExtraInfo, error) {
   398  	var extraInfo ExtraInfo
   399  	if len(v) == 0 {
   400  		return extraInfo, nil
   401  	}
   402  
   403  	if err := json.Unmarshal([]byte(v), &extraInfo); err != nil {
   404  		return extraInfo, fmt.Errorf("invalid extra info: %v , pls check", err)
   405  	}
   406  
   407  	for _, convert := range extraInfo.PkgNameConvert {
   408  		if convert.ConvertedPkgName == "" {
   409  			return extraInfo, fmt.Errorf("invalid extra info: ConvertedPkgName is empty, pls check")
   410  		}
   411  
   412  		if convert.UnconvertedPkgNameKeyWords != nil {
   413  			for _, keyword := range convert.UnconvertedPkgNameKeyWords {
   414  				if keyword == "" {
   415  					return extraInfo, fmt.Errorf("invalid extra info: UnconvertedPkgNameKeyWords has a empty keyword, pls check")
   416  				}
   417  			}
   418  		}
   419  
   420  		if convert.UnconvertedPkgNamePrefixs != nil {
   421  			for _, prefix := range convert.UnconvertedPkgNamePrefixs {
   422  				if prefix == "" {
   423  					return extraInfo, fmt.Errorf("invalid extra info: UnconvertedPkgNamePrefixs has a empty value, pls check")
   424  				}
   425  			}
   426  		}
   427  	}
   428  	return extraInfo, nil
   429  }
   430  
   431  // getHeaders: get request header, Authorization -> digest
   432  func getHeaders(huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds, request *openrtb2.BidRequest, isTestAuthorization bool) http.Header {
   433  	headers := http.Header{}
   434  	headers.Add("Content-Type", "application/json;charset=utf-8")
   435  	headers.Add("Accept", "application/json")
   436  	if huaweiAdsImpExt == nil {
   437  		return headers
   438  	}
   439  	headers.Add("Authorization", getDigestAuthorization(huaweiAdsImpExt, isTestAuthorization))
   440  
   441  	if request.Device != nil && len(request.Device.UA) > 0 {
   442  		headers.Add("User-Agent", request.Device.UA)
   443  	}
   444  	return headers
   445  }
   446  
   447  // getReqJson: get body json for HuaweiAds request
   448  func getReqJson(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest, extraInfo ExtraInfo) (countryCode string, err error) {
   449  	request.Version = huaweiAdxApiVersion
   450  	if countryCode, err = getReqAppInfo(request, openRTBRequest, extraInfo); err != nil {
   451  		return "", err
   452  	}
   453  	if err = getReqDeviceInfo(request, openRTBRequest); err != nil {
   454  		return "", err
   455  	}
   456  	getReqNetWorkInfo(request, openRTBRequest)
   457  	getReqRegsInfo(request, openRTBRequest)
   458  	getReqGeoInfo(request, openRTBRequest)
   459  	getReqConsentInfo(request, openRTBRequest)
   460  	return countryCode, nil
   461  }
   462  
   463  func getReqAdslot30(huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds,
   464  	openRTBImp *openrtb2.Imp) (adslot30, error) {
   465  	adtype := convertAdtypeStringToInteger(strings.ToLower(huaweiAdsImpExt.Adtype))
   466  	testStatus := 0
   467  	if huaweiAdsImpExt.IsTestAuthorization == "true" {
   468  		testStatus = 1
   469  	}
   470  	var adslot30 = adslot30{
   471  		Slotid: huaweiAdsImpExt.SlotId,
   472  		Adtype: adtype,
   473  		Test:   int32(testStatus),
   474  	}
   475  	if err := checkAndExtractOpenrtbFormat(&adslot30, adtype, huaweiAdsImpExt.Adtype, openRTBImp); err != nil {
   476  		return adslot30, err
   477  	}
   478  	return adslot30, nil
   479  }
   480  
   481  // opentrb :  huawei adtype
   482  // banner <-> banner, interstitial
   483  // native <-> native
   484  // video  <->  banner, roll, interstitial, rewarded
   485  func checkAndExtractOpenrtbFormat(adslot30 *adslot30, adtype int32, yourAdtype string, openRTBImp *openrtb2.Imp) error {
   486  	if openRTBImp.Banner != nil {
   487  		if adtype != banner && adtype != interstitial {
   488  			return errors.New("check openrtb format: request has banner, doesn't correspond to huawei adtype " + yourAdtype)
   489  		}
   490  		getBannerFormat(adslot30, openRTBImp)
   491  	} else if openRTBImp.Native != nil {
   492  		if adtype != native {
   493  			return errors.New("check openrtb format: request has native, doesn't correspond to huawei adtype " + yourAdtype)
   494  		}
   495  		if err := getNativeFormat(adslot30, openRTBImp); err != nil {
   496  			return err
   497  		}
   498  	} else if openRTBImp.Video != nil {
   499  		if adtype != banner && adtype != interstitial && adtype != rewarded && adtype != roll {
   500  			return errors.New("check openrtb format: request has video, doesn't correspond to huawei adtype " + yourAdtype)
   501  		}
   502  		if err := getVideoFormat(adslot30, adtype, openRTBImp); err != nil {
   503  			return err
   504  		}
   505  	} else if openRTBImp.Audio != nil {
   506  		return errors.New("check openrtb format: request has audio, not currently supported")
   507  	} else {
   508  		return errors.New("check openrtb format: please choose one of our supported type banner, native, or video")
   509  	}
   510  	return nil
   511  }
   512  
   513  func getBannerFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) {
   514  	if openRTBImp.Banner.W != nil && openRTBImp.Banner.H != nil {
   515  		adslot30.W = *openRTBImp.Banner.W
   516  		adslot30.H = *openRTBImp.Banner.H
   517  	}
   518  	if len(openRTBImp.Banner.Format) != 0 {
   519  		var formats = make([]format, 0, len(openRTBImp.Banner.Format))
   520  		for _, f := range openRTBImp.Banner.Format {
   521  			if f.H != 0 && f.W != 0 {
   522  				formats = append(formats, format{f.W, f.H})
   523  			}
   524  		}
   525  		adslot30.Format = formats
   526  	}
   527  }
   528  
   529  func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error {
   530  	if openRTBImp.Native.Request == "" {
   531  		return errors.New("extract openrtb native failed: imp.Native.Request is empty")
   532  	}
   533  
   534  	var nativePayload nativeRequests.Request
   535  	if err := json.Unmarshal(json.RawMessage(openRTBImp.Native.Request), &nativePayload); err != nil {
   536  		return err
   537  	}
   538  
   539  	//popular size for native ads
   540  	popularSizes := []format{{W: 225, H: 150}, {W: 1080, H: 607}, {W: 300, H: 250}, {W: 1080, H: 1620}, {W: 1280, H: 720}, {W: 640, H: 360}, {W: 1080, H: 1920}, {W: 720, H: 1280}}
   541  
   542  	// only compute the main image number, type = native1.ImageAssetTypeMain
   543  	var numMainImage = 0
   544  	var numVideo = 0
   545  	var formats = make([]format, 0)
   546  	var numFormat = 0
   547  	var detailedCreativeTypeList = make([]string, 0, 2)
   548  
   549  	//number of the requested image size
   550  	for _, asset := range nativePayload.Assets {
   551  		if numFormat > 1 {
   552  			break
   553  		}
   554  		if asset.Img != nil {
   555  			if asset.Img.Type == native1.ImageAssetTypeMain {
   556  				numFormat++
   557  			}
   558  		}
   559  	}
   560  
   561  	sizeMap := make(map[format]struct{})
   562  	for _, size := range popularSizes {
   563  		sizeMap[size] = struct{}{}
   564  	}
   565  
   566  	for _, asset := range nativePayload.Assets {
   567  		// Only one of the {title,img,video,data} objects should be present in each object.
   568  		if asset.Video != nil {
   569  			numVideo++
   570  			formats = popularSizes
   571  
   572  			w := ptrutil.ValueOrDefault(asset.Video.W)
   573  			h := ptrutil.ValueOrDefault(asset.Video.H)
   574  
   575  			_, ok := sizeMap[format{W: w, H: h}]
   576  			if (w != 0 && h != 0) && !ok {
   577  				formats = append(formats, format{w, h})
   578  			}
   579  		}
   580  		// every image has the same W, H.
   581  		if asset.Img != nil {
   582  			if asset.Img.Type == native1.ImageAssetTypeMain {
   583  				numMainImage++
   584  				if numFormat > 1 && asset.Img.H != 0 && asset.Img.W != 0 && asset.Img.WMin != 0 && asset.Img.HMin != 0 {
   585  					formats = append(formats, format{asset.Img.W, asset.Img.H})
   586  				}
   587  				if numFormat == 1 && asset.Img.H != 0 && asset.Img.W != 0 && asset.Img.WMin != 0 && asset.Img.HMin != 0 {
   588  					result := filterPopularSizes(popularSizes, asset.Img.W, asset.Img.H, "ratio")
   589  					formats = append(formats, result...)
   590  				}
   591  				if numFormat == 1 && asset.Img.H == 0 && asset.Img.W == 0 && asset.Img.WMin != 0 && asset.Img.HMin != 0 {
   592  					result := filterPopularSizes(popularSizes, asset.Img.WMin, asset.Img.HMin, "range")
   593  					formats = append(formats, result...)
   594  				}
   595  			}
   596  		}
   597  		adslot30.Format = formats
   598  	}
   599  	if numVideo >= 1 {
   600  		detailedCreativeTypeList = append(detailedCreativeTypeList, "903")
   601  	}
   602  	if numMainImage >= 1 {
   603  		detailedCreativeTypeList = append(detailedCreativeTypeList, "901", "904", "905")
   604  	}
   605  	adslot30.DetailedCreativeTypeList = detailedCreativeTypeList
   606  	return nil
   607  }
   608  
   609  // filter popular size by range or ratio to append format array
   610  func filterPopularSizes(sizes []format, width int64, height int64, byWhat string) []format {
   611  
   612  	filtered := []format{}
   613  	for _, size := range sizes {
   614  		w := size.W
   615  		h := size.H
   616  
   617  		if byWhat == "ratio" {
   618  			ratio := float64(width) / float64(height)
   619  			diff := math.Abs(float64(w)/float64(h) - ratio)
   620  			if diff <= 0.5 {
   621  				filtered = append(filtered, size)
   622  			}
   623  		}
   624  		if byWhat == "range" && w > width && h > height {
   625  			filtered = append(filtered, size)
   626  		}
   627  	}
   628  	return filtered
   629  }
   630  
   631  // roll ad need TotalDuration
   632  func getVideoFormat(adslot30 *adslot30, adtype int32, openRTBImp *openrtb2.Imp) error {
   633  	adslot30.W = ptrutil.ValueOrDefault(openRTBImp.Video.W)
   634  	adslot30.H = ptrutil.ValueOrDefault(openRTBImp.Video.H)
   635  
   636  	if adtype == roll {
   637  		if openRTBImp.Video.MaxDuration == 0 {
   638  			return errors.New("extract openrtb video failed: MaxDuration is empty when huaweiads adtype is roll.")
   639  		}
   640  		adslot30.TotalDuration = int32(openRTBImp.Video.MaxDuration)
   641  	}
   642  	return nil
   643  }
   644  
   645  func convertAdtypeStringToInteger(adtypeLower string) int32 {
   646  	switch adtypeLower {
   647  	case "banner":
   648  		return banner
   649  	case "native":
   650  		return native
   651  	case "rewarded":
   652  		return rewarded
   653  	case "interstitial":
   654  		return interstitial
   655  	case "roll":
   656  		return roll
   657  	case "splash":
   658  		return splash
   659  	case "magazinelock":
   660  		return magazinelock
   661  	case "audio":
   662  		return audio
   663  	default:
   664  		return banner
   665  	}
   666  }
   667  
   668  // getReqAppInfo: get app information for HuaweiAds request
   669  func getReqAppInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest, extraInfo ExtraInfo) (countryCode string, err error) {
   670  	var app app
   671  	if openRTBRequest.App != nil {
   672  		if openRTBRequest.App.Ver != "" {
   673  			app.Version = openRTBRequest.App.Ver
   674  		}
   675  		if openRTBRequest.App.Name != "" {
   676  			app.Name = openRTBRequest.App.Name
   677  		}
   678  
   679  		// bundle cannot be empty, we need package name.
   680  		if openRTBRequest.App.Bundle != "" {
   681  			app.Pkgname = getFinalPkgName(openRTBRequest.App.Bundle, extraInfo)
   682  		} else {
   683  			return "", errors.New("generate HuaweiAds AppInfo failed: openrtb BidRequest.App.Bundle is empty.")
   684  		}
   685  
   686  		if openRTBRequest.App.Content != nil && openRTBRequest.App.Content.Language != "" {
   687  			app.Lang = openRTBRequest.App.Content.Language
   688  		} else {
   689  			app.Lang = "en"
   690  		}
   691  	}
   692  	countryCode = getCountryCode(openRTBRequest)
   693  	app.Country = countryCode
   694  	request.App = app
   695  	return countryCode, nil
   696  }
   697  
   698  // when it has pkgNameConvert (include different rules)
   699  // 1. when bundleName in ExceptionPkgNames, finalPkgname = bundleName
   700  // 2. when bundleName conform UnconvertedPkgNames, finalPkgname = ConvertedPkgName
   701  // 3. when bundleName conform keyword, finalPkgname = ConvertedPkgName
   702  // 4. when bundleName conform prefix, finalPkgname = ConvertedPkgName
   703  func getFinalPkgName(bundleName string, extraInfo ExtraInfo) string {
   704  	for _, convert := range extraInfo.PkgNameConvert {
   705  		if convert.ConvertedPkgName == "" {
   706  			continue
   707  		}
   708  
   709  		for _, name := range convert.ExceptionPkgNames {
   710  			if name == bundleName {
   711  				return bundleName
   712  			}
   713  		}
   714  
   715  		for _, name := range convert.UnconvertedPkgNames {
   716  			if name == bundleName || name == "*" {
   717  				return convert.ConvertedPkgName
   718  			}
   719  		}
   720  
   721  		for _, keyword := range convert.UnconvertedPkgNameKeyWords {
   722  			if strings.Index(bundleName, keyword) > 0 {
   723  				return convert.ConvertedPkgName
   724  			}
   725  		}
   726  
   727  		for _, prefix := range convert.UnconvertedPkgNamePrefixs {
   728  			if strings.HasPrefix(bundleName, prefix) {
   729  				return convert.ConvertedPkgName
   730  			}
   731  		}
   732  	}
   733  	return bundleName
   734  }
   735  
   736  // getClientTime: get field clientTime, format: 2006-01-02 15:04:05.000+0200
   737  // If this parameter is not passed, the server time is used
   738  func getClientTime(clientTime string) (newClientTime string) {
   739  	var zone = defaultTimeZone
   740  	t := time.Now().Local().Format(time.RFC822Z)
   741  	index := strings.IndexAny(t, "-+")
   742  	if index > 0 && len(t)-index == 5 {
   743  		zone = t[index:]
   744  	}
   745  	if clientTime == "" {
   746  		return time.Now().Format(timeFormat) + zone
   747  	}
   748  	if isMatched, _ := regexp.MatchString("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}[+-]{1}\\d{4}$", clientTime); isMatched {
   749  		return clientTime
   750  	}
   751  	if isMatched, _ := regexp.MatchString("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}$", clientTime); isMatched {
   752  		return clientTime + zone
   753  	}
   754  	return time.Now().Format(timeFormat) + zone
   755  }
   756  
   757  // getReqDeviceInfo: get device information for HuaweiAds request
   758  func getReqDeviceInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) (err error) {
   759  	var device device
   760  	if openRTBRequest.Device != nil {
   761  		device.Type = int32(openRTBRequest.Device.DeviceType)
   762  		device.Useragent = openRTBRequest.Device.UA
   763  		device.Os = openRTBRequest.Device.OS
   764  		device.Version = openRTBRequest.Device.OSV
   765  		device.Maker = openRTBRequest.Device.Make
   766  		device.Model = openRTBRequest.Device.Model
   767  		if device.Model == "" {
   768  			device.Model = defaultModelName
   769  		}
   770  		device.Height = int32(openRTBRequest.Device.H)
   771  		device.Width = int32(openRTBRequest.Device.W)
   772  		device.Language = openRTBRequest.Device.Language
   773  		device.Pxratio = float32(openRTBRequest.Device.PxRatio)
   774  		var country = getCountryCode(openRTBRequest)
   775  		device.BelongCountry = country
   776  		device.LocaleCountry = country
   777  		device.Ip = openRTBRequest.Device.IP
   778  		device.Gaid = openRTBRequest.Device.IFA
   779  		device.ClientTime = getClientTime("")
   780  	}
   781  
   782  	// get oaid gaid imei in openRTBRequest.User.Ext.Data
   783  	if err = getDeviceIDFromUserExt(&device, openRTBRequest); err != nil {
   784  		return err
   785  	}
   786  
   787  	// IsTrackingEnabled = 1 - DNT
   788  	if openRTBRequest.Device != nil && openRTBRequest.Device.DNT != nil {
   789  		if device.Oaid != "" {
   790  			device.IsTrackingEnabled = strconv.Itoa(1 - int(*openRTBRequest.Device.DNT))
   791  		}
   792  		if device.Gaid != "" {
   793  			device.GaidTrackingEnabled = strconv.Itoa(1 - int(*openRTBRequest.Device.DNT))
   794  		}
   795  	}
   796  
   797  	request.Device = device
   798  	return nil
   799  }
   800  
   801  func getCountryCode(openRTBRequest *openrtb2.BidRequest) string {
   802  	if openRTBRequest.Device != nil && openRTBRequest.Device.Geo != nil && openRTBRequest.Device.Geo.Country != "" {
   803  		return convertCountryCode(openRTBRequest.Device.Geo.Country)
   804  	} else if openRTBRequest.User != nil && openRTBRequest.User.Geo != nil && openRTBRequest.User.Geo.Country != "" {
   805  		return convertCountryCode(openRTBRequest.User.Geo.Country)
   806  	} else if openRTBRequest.Device != nil && openRTBRequest.Device.MCCMNC != "" {
   807  		return getCountryCodeFromMCC(openRTBRequest.Device.MCCMNC)
   808  	} else {
   809  		return defaultCountryName
   810  	}
   811  }
   812  
   813  // convertCountryCode: ISO 3166-1 Alpha3 -> Alpha2, Some countries may use
   814  func convertCountryCode(country string) (out string) {
   815  	if country == "" {
   816  		return defaultCountryName
   817  	}
   818  	var mapCountryCodeAlpha3ToAlpha2 = map[string]string{"AND": "AD", "AGO": "AO", "AUT": "AT", "BGD": "BD",
   819  		"BLR": "BY", "CAF": "CF", "TCD": "TD", "CHL": "CL", "CHN": "CN", "COG": "CG", "COD": "CD", "DNK": "DK",
   820  		"GNQ": "GQ", "EST": "EE", "GIN": "GN", "GNB": "GW", "GUY": "GY", "IRQ": "IQ", "IRL": "IE", "ISR": "IL",
   821  		"KAZ": "KZ", "LBY": "LY", "MDG": "MG", "MDV": "MV", "MEX": "MX", "MNE": "ME", "MOZ": "MZ", "PAK": "PK",
   822  		"PNG": "PG", "PRY": "PY", "POL": "PL", "PRT": "PT", "SRB": "RS", "SVK": "SK", "SVN": "SI", "SWE": "SE",
   823  		"TUN": "TN", "TUR": "TR", "TKM": "TM", "UKR": "UA", "ARE": "AE", "URY": "UY"}
   824  	if mappedCountry, exists := mapCountryCodeAlpha3ToAlpha2[country]; exists {
   825  		return mappedCountry
   826  	}
   827  
   828  	if len(country) >= 2 {
   829  		return country[0:2]
   830  	}
   831  
   832  	return defaultCountryName
   833  }
   834  
   835  func getCountryCodeFromMCC(MCC string) (out string) {
   836  	var countryMCC = strings.Split(MCC, "-")[0]
   837  	intVar, err := strconv.Atoi(countryMCC)
   838  
   839  	if err != nil {
   840  		return defaultCountryName
   841  	} else {
   842  		if result, found := MccList[intVar]; found {
   843  			return strings.ToUpper(result)
   844  		} else {
   845  			return defaultCountryName
   846  		}
   847  	}
   848  }
   849  
   850  // getDeviceID include oaid gaid imei. In prebid mobile, use TargetingParams.addUserData("imei", "imei-test");
   851  // When ifa: gaid exists, other device id can be passed by TargetingParams.addUserData("oaid", "oaid-test");
   852  func getDeviceIDFromUserExt(device *device, openRTBRequest *openrtb2.BidRequest) (err error) {
   853  	var userObjExist = true
   854  	if openRTBRequest.User == nil || openRTBRequest.User.Ext == nil {
   855  		userObjExist = false
   856  	}
   857  	if userObjExist {
   858  		var extUserDataHuaweiAds openrtb_ext.ExtUserDataHuaweiAds
   859  		if err := json.Unmarshal(openRTBRequest.User.Ext, &extUserDataHuaweiAds); err != nil {
   860  			return errors.New("get gaid from openrtb Device.IFA failed, and get device id failed: Unmarshal openRTBRequest.User.Ext -> extUserDataHuaweiAds. Error: " + err.Error())
   861  		}
   862  
   863  		var deviceId = extUserDataHuaweiAds.Data
   864  		isValidDeviceId := false
   865  
   866  		if len(deviceId.Oaid) > 0 {
   867  			device.Oaid = deviceId.Oaid[0]
   868  			isValidDeviceId = true
   869  		}
   870  		if len(deviceId.Gaid) > 0 {
   871  			device.Gaid = deviceId.Gaid[0]
   872  			isValidDeviceId = true
   873  		}
   874  		if len(device.Gaid) > 0 {
   875  			isValidDeviceId = true
   876  		}
   877  		if len(deviceId.Imei) > 0 {
   878  			device.Imei = deviceId.Imei[0]
   879  			isValidDeviceId = true
   880  		}
   881  
   882  		if !isValidDeviceId {
   883  			return errors.New("getDeviceID: Imei ,Oaid, Gaid are all empty.")
   884  		}
   885  		if len(deviceId.ClientTime) > 0 {
   886  			device.ClientTime = getClientTime(deviceId.ClientTime[0])
   887  		}
   888  	} else {
   889  		if len(device.Gaid) == 0 {
   890  			return errors.New("getDeviceID: openRTBRequest.User.Ext is nil and device.Gaid is not specified.")
   891  		}
   892  	}
   893  	return nil
   894  }
   895  
   896  // getReqNetWorkInfo: for HuaweiAds request, include Carrier, Mcc, Mnc
   897  func getReqNetWorkInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) {
   898  	if openRTBRequest.Device != nil {
   899  		var network network
   900  		if openRTBRequest.Device.ConnectionType != nil {
   901  			network.Type = int32(*openRTBRequest.Device.ConnectionType)
   902  		} else {
   903  			network.Type = defaultUnknownNetworkType
   904  		}
   905  
   906  		var cellInfos []cellInfo
   907  		if openRTBRequest.Device.MCCMNC != "" {
   908  			var arr = strings.Split(openRTBRequest.Device.MCCMNC, "-")
   909  			network.Carrier = 0
   910  			if len(arr) >= 2 {
   911  				cellInfos = append(cellInfos, cellInfo{
   912  					Mcc: arr[0],
   913  					Mnc: arr[1],
   914  				})
   915  				var str = arr[0] + arr[1]
   916  				if str == "46000" || str == "46002" || str == "46007" {
   917  					network.Carrier = 2
   918  				} else if str == "46001" || str == "46006" {
   919  					network.Carrier = 1
   920  				} else if str == "46003" || str == "46005" || str == "46011" {
   921  					network.Carrier = 3
   922  				} else {
   923  					network.Carrier = 99
   924  				}
   925  			}
   926  		}
   927  		network.CellInfo = cellInfos
   928  		request.Network = network
   929  	}
   930  }
   931  
   932  // getReqRegsInfo: get regs information for HuaweiAds request, include Coppa
   933  func getReqRegsInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) {
   934  	if openRTBRequest.Regs != nil && openRTBRequest.Regs.COPPA >= 0 {
   935  		var regs regs
   936  		regs.Coppa = int32(openRTBRequest.Regs.COPPA)
   937  		request.Regs = regs
   938  	}
   939  }
   940  
   941  // getReqGeoInfo: get geo information for HuaweiAds request, include Lon, Lat, Accuracy, Lastfix
   942  func getReqGeoInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) {
   943  	if openRTBRequest.Device != nil && openRTBRequest.Device.Geo != nil {
   944  		request.Geo = geo{
   945  			Lon:      float32(ptrutil.ValueOrDefault(openRTBRequest.Device.Geo.Lon)),
   946  			Lat:      float32(ptrutil.ValueOrDefault(openRTBRequest.Device.Geo.Lat)),
   947  			Accuracy: int32(openRTBRequest.Device.Geo.Accuracy),
   948  			Lastfix:  int32(openRTBRequest.Device.Geo.LastFix),
   949  		}
   950  	}
   951  }
   952  
   953  // getReqGeoInfo: get GDPR consent
   954  func getReqConsentInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) {
   955  	if openRTBRequest.User != nil && openRTBRequest.User.Ext != nil {
   956  		var extUser openrtb_ext.ExtUser
   957  		if err := json.Unmarshal(openRTBRequest.User.Ext, &extUser); err != nil {
   958  			return
   959  		}
   960  		request.Consent = extUser.Consent
   961  	}
   962  }
   963  
   964  func unmarshalExtImpHuaweiAds(openRTBImp *openrtb2.Imp) (*openrtb_ext.ExtImpHuaweiAds, error) {
   965  	var bidderExt adapters.ExtImpBidder
   966  	var huaweiAdsImpExt openrtb_ext.ExtImpHuaweiAds
   967  	if err := json.Unmarshal(openRTBImp.Ext, &bidderExt); err != nil {
   968  		return nil, errors.New("Unmarshal: openRTBImp.Ext -> bidderExt failed")
   969  	}
   970  	if err := json.Unmarshal(bidderExt.Bidder, &huaweiAdsImpExt); err != nil {
   971  		return nil, errors.New("Unmarshal: bidderExt.Bidder -> huaweiAdsImpExt failed")
   972  	}
   973  	if huaweiAdsImpExt.SlotId == "" {
   974  		return nil, errors.New("ExtImpHuaweiAds: slotid is empty.")
   975  	}
   976  	if huaweiAdsImpExt.Adtype == "" {
   977  		return nil, errors.New("ExtImpHuaweiAds: adtype is empty.")
   978  	}
   979  	if huaweiAdsImpExt.PublisherId == "" {
   980  		return nil, errors.New("ExtHuaweiAds: publisherid is empty.")
   981  	}
   982  	if huaweiAdsImpExt.SignKey == "" {
   983  		return nil, errors.New("ExtHuaweiAds: signkey is empty.")
   984  	}
   985  	if huaweiAdsImpExt.KeyId == "" {
   986  		return nil, errors.New("ExtImpHuaweiAds: keyid is empty.")
   987  	}
   988  	return &huaweiAdsImpExt, nil
   989  }
   990  
   991  func checkRespStatusCode(response *adapters.ResponseData) error {
   992  	if response.StatusCode == http.StatusNoContent {
   993  		return nil
   994  	}
   995  
   996  	if response.StatusCode == http.StatusServiceUnavailable {
   997  		return &errortypes.BadInput{
   998  			Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode),
   999  		}
  1000  	}
  1001  
  1002  	if response.StatusCode != http.StatusOK {
  1003  		return &errortypes.BadInput{
  1004  			Message: fmt.Sprintf("Unexpected status code: [ %d ]. Run with request.debug = 1 for more info", response.StatusCode),
  1005  		}
  1006  	}
  1007  
  1008  	if response.Body == nil {
  1009  		return errors.New("bidderRawResponse body is empty")
  1010  	}
  1011  	return nil
  1012  }
  1013  
  1014  func checkHuaweiAdsResponseRetcode(response huaweiAdsResponse) error {
  1015  	if response.Retcode == 200 || response.Retcode == 206 {
  1016  		return nil
  1017  	}
  1018  	if response.Retcode == 204 {
  1019  		return &errortypes.BadInput{
  1020  			Message: fmt.Sprintf("HuaweiAdsResponse retcode: %d , reason: The request packet is correct, but no advertisement was found for this request.", response.Retcode),
  1021  		}
  1022  	}
  1023  	if (response.Retcode < 600 && response.Retcode >= 400) || (response.Retcode < 300 && response.Retcode > 200) {
  1024  		return &errortypes.BadInput{
  1025  			Message: fmt.Sprintf("HuaweiAdsResponse retcode: %d , reason: %s", response.Retcode, response.Reason),
  1026  		}
  1027  	}
  1028  	return nil
  1029  }
  1030  
  1031  // convertHuaweiAdsRespToBidderResp: convert HuaweiAds' response into bidder's response
  1032  func (a *adapter) convertHuaweiAdsRespToBidderResp(huaweiAdsResponse *huaweiAdsResponse, openRTBRequest *openrtb2.BidRequest) (bidderResponse *adapters.BidderResponse, err error) {
  1033  	if len(huaweiAdsResponse.Multiad) == 0 {
  1034  		return nil, errors.New("convert huaweiads response to bidder response failed: multiad length is 0, get no ads from huawei side.")
  1035  	}
  1036  	bidderResponse = adapters.NewBidderResponseWithBidsCapacity(len(huaweiAdsResponse.Multiad))
  1037  	// Default Currency: CNY
  1038  	bidderResponse.Currency = "CNY"
  1039  
  1040  	// record request Imp (slotid->imp, slotid->openrtb_ext.bidtype)
  1041  	mapSlotid2Imp := make(map[string]openrtb2.Imp, len(openRTBRequest.Imp))
  1042  	mapSlotid2MediaType := make(map[string]openrtb_ext.BidType, len(openRTBRequest.Imp))
  1043  	for _, imp := range openRTBRequest.Imp {
  1044  		huaweiAdsExt, err := unmarshalExtImpHuaweiAds(&imp)
  1045  		if err != nil {
  1046  			continue
  1047  		}
  1048  		mapSlotid2Imp[huaweiAdsExt.SlotId] = imp
  1049  
  1050  		var mediaType = openrtb_ext.BidTypeBanner
  1051  		if imp.Video != nil {
  1052  			mediaType = openrtb_ext.BidTypeVideo
  1053  		} else if imp.Native != nil {
  1054  			mediaType = openrtb_ext.BidTypeNative
  1055  		} else if imp.Audio != nil {
  1056  			mediaType = openrtb_ext.BidTypeAudio
  1057  		}
  1058  		mapSlotid2MediaType[huaweiAdsExt.SlotId] = mediaType
  1059  	}
  1060  
  1061  	if len(mapSlotid2MediaType) < 1 || len(mapSlotid2Imp) < 1 {
  1062  		return nil, errors.New("convert huaweiads response to bidder response failed: openRTBRequest.imp is nil")
  1063  	}
  1064  
  1065  	for _, ad30 := range huaweiAdsResponse.Multiad {
  1066  		if mapSlotid2Imp[ad30.Slotid].ID == "" {
  1067  			continue
  1068  		}
  1069  
  1070  		if ad30.Retcode30 != 200 {
  1071  			continue
  1072  		}
  1073  
  1074  		for _, content := range ad30.Content {
  1075  			var bid openrtb2.Bid
  1076  			bid.ID = mapSlotid2Imp[ad30.Slotid].ID
  1077  			bid.ImpID = mapSlotid2Imp[ad30.Slotid].ID
  1078  			// The bidder has already helped us automatically convert the currency price, here only the CNY price is filled in
  1079  			bid.Price = content.Price
  1080  			bid.CrID = content.Contentid
  1081  			// All currencies should be the same
  1082  			if content.Cur != "" {
  1083  				bidderResponse.Currency = content.Cur
  1084  			}
  1085  
  1086  			bid.AdM, bid.W, bid.H, err = a.handleHuaweiAdsContent(ad30.AdType, &content, mapSlotid2MediaType[ad30.Slotid], mapSlotid2Imp[ad30.Slotid])
  1087  			if err != nil {
  1088  				return nil, err
  1089  			}
  1090  			bid.ADomain = append(bid.ADomain, "huaweiads")
  1091  			bid.NURL = getNurl(content)
  1092  			bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
  1093  				Bid:     &bid,
  1094  				BidType: mapSlotid2MediaType[ad30.Slotid],
  1095  			})
  1096  		}
  1097  	}
  1098  	return bidderResponse, nil
  1099  }
  1100  
  1101  func getNurl(content content) string {
  1102  	if len(content.Monitor) == 0 {
  1103  		return ""
  1104  	}
  1105  	for _, monitor := range content.Monitor {
  1106  		if monitor.EventType == "win" && len(monitor.Url) != 0 {
  1107  			return monitor.Url[0]
  1108  		}
  1109  	}
  1110  	return ""
  1111  }
  1112  
  1113  // handleHuaweiAdsContent: get field Adm, Width, Height
  1114  func (a *adapter) handleHuaweiAdsContent(adType int32, content *content, bidType openrtb_ext.BidType, imp openrtb2.Imp) (
  1115  	adm string, adWidth int64, adHeight int64, err error) {
  1116  	switch bidType {
  1117  	case openrtb_ext.BidTypeBanner:
  1118  		adm, adWidth, adHeight, err = a.extractAdmBanner(adType, content, bidType, imp)
  1119  	case openrtb_ext.BidTypeNative:
  1120  		adm, adWidth, adHeight, err = a.extractAdmNative(adType, content, bidType, imp)
  1121  	case openrtb_ext.BidTypeVideo:
  1122  		adm, adWidth, adHeight, err = a.extractAdmVideo(adType, content, bidType, imp)
  1123  	default:
  1124  		return "", 0, 0, errors.New("no support bidtype: audio")
  1125  	}
  1126  
  1127  	if err != nil {
  1128  		return "", 0, 0, fmt.Errorf("generate Adm field from HuaweiAds response failed: %s", err)
  1129  	}
  1130  	return adm, adWidth, adHeight, nil
  1131  }
  1132  
  1133  // extractAdmBanner: banner ad
  1134  func (a *adapter) extractAdmBanner(adType int32, content *content, bidType openrtb_ext.BidType, imp openrtb2.Imp) (adm string,
  1135  	adWidth int64, adHeight int64, err error) {
  1136  	// support openrtb: banner  <=> huawei adtype: banner, interstitial
  1137  	if adType != banner && adType != interstitial {
  1138  		return "", 0, 0, errors.New("openrtb banner should correspond to huaweiads adtype: banner or interstitial")
  1139  	}
  1140  	var creativeType = content.Creativetype
  1141  	if content.Creativetype > 100 {
  1142  		creativeType = creativeType - 100
  1143  	}
  1144  	if creativeType == text || creativeType == bigPicture || creativeType == bigPicture2 ||
  1145  		creativeType == smallPicture || creativeType == threeSmallPicturesText ||
  1146  		creativeType == iconText || creativeType == gif {
  1147  		return a.extractAdmPicture(content)
  1148  	} else if creativeType == videoText || creativeType == video || creativeType == videoWithPicturesText {
  1149  		return a.extractAdmVideo(adType, content, bidType, imp)
  1150  	} else {
  1151  		return "", 0, 0, errors.New("no banner support creativetype")
  1152  	}
  1153  }
  1154  
  1155  // extractAdmNative: native ad
  1156  func (a *adapter) extractAdmNative(adType int32, content *content, bidType openrtb_ext.BidType, openrtb2Imp openrtb2.Imp) (adm string,
  1157  	adWidth int64, adHeight int64, err error) {
  1158  	if adType != native {
  1159  		return "", 0, 0, errors.New("extract Adm for Native ad: huaweiads response is not a native ad")
  1160  	}
  1161  	if openrtb2Imp.Native == nil {
  1162  		return "", 0, 0, errors.New("extract Adm for Native ad: imp.Native is nil")
  1163  	}
  1164  	if openrtb2Imp.Native.Request == "" {
  1165  		return "", 0, 0, errors.New("extract Adm for Native ad: imp.Native.Request is empty")
  1166  	}
  1167  
  1168  	var nativePayload nativeRequests.Request
  1169  	if err := json.Unmarshal(json.RawMessage(openrtb2Imp.Native.Request), &nativePayload); err != nil {
  1170  		return "", 0, 0, err
  1171  	}
  1172  
  1173  	var nativeResult nativeResponse.Response
  1174  	var linkObject nativeResponse.Link
  1175  	linkObject.URL, err = a.getClickUrl(content)
  1176  	if err != nil {
  1177  		return "", 0, 0, err
  1178  	}
  1179  
  1180  	nativeResult.Assets = make([]nativeResponse.Asset, 0, len(nativePayload.Assets))
  1181  	var imgIndex = 0
  1182  	var iconIndex = 0
  1183  	for _, asset := range nativePayload.Assets {
  1184  		var responseAsset nativeResponse.Asset
  1185  		if asset.Title != nil {
  1186  			var titleObject nativeResponse.Title
  1187  			titleObject.Text = getDecodeValue(content.MetaData.Title)
  1188  			titleObject.Len = int64(len(titleObject.Text))
  1189  			responseAsset.Title = &titleObject
  1190  		} else if asset.Video != nil {
  1191  			var videoObject nativeResponse.Video
  1192  			var err error
  1193  			if videoObject.VASTTag, adWidth, adHeight, err = a.extractAdmVideo(adType, content, bidType, openrtb2Imp); err != nil {
  1194  				return "", 0, 0, err
  1195  			}
  1196  			responseAsset.Video = &videoObject
  1197  		} else if asset.Img != nil {
  1198  			if len(content.MetaData.ImageInfo) == imgIndex && asset.Img.Type == native1.ImageAssetTypeMain {
  1199  				continue
  1200  			}
  1201  			var imgObject nativeResponse.Image
  1202  			imgObject.URL = ""
  1203  			imgObject.Type = asset.Img.Type
  1204  			if asset.Img.Type == native1.ImageAssetTypeIcon {
  1205  				if len(content.MetaData.Icon) > iconIndex {
  1206  					imgObject.URL = content.MetaData.Icon[iconIndex].Url
  1207  					imgObject.W = content.MetaData.Icon[iconIndex].Width
  1208  					imgObject.H = content.MetaData.Icon[iconIndex].Height
  1209  					iconIndex++
  1210  				}
  1211  			} else {
  1212  				if len(content.MetaData.ImageInfo) > imgIndex {
  1213  					imgObject.URL = content.MetaData.ImageInfo[imgIndex].Url
  1214  					imgObject.W = content.MetaData.ImageInfo[imgIndex].Width
  1215  					imgObject.H = content.MetaData.ImageInfo[imgIndex].Height
  1216  					imgIndex++
  1217  				}
  1218  			}
  1219  			if adHeight == 0 && adWidth == 0 {
  1220  				adHeight = imgObject.H
  1221  				adWidth = imgObject.W
  1222  			}
  1223  			responseAsset.Img = &imgObject
  1224  		} else if asset.Data != nil {
  1225  			var dataObject nativeResponse.Data
  1226  			dataObject.Label = ""
  1227  			dataObject.Value = ""
  1228  			if asset.Data.Type == native1.DataAssetTypeDesc || asset.Data.Type == native1.DataAssetTypeDesc2 {
  1229  				dataObject.Label = "desc"
  1230  				dataObject.Value = getDecodeValue(content.MetaData.Description)
  1231  			}
  1232  
  1233  			if asset.Data.Type == native1.DataAssetTypeCTAText {
  1234  				dataObject.Type = native1.DataAssetTypeCTAText
  1235  				dataObject.Value = getDecodeValue(content.MetaData.Cta)
  1236  			}
  1237  			responseAsset.Data = &dataObject
  1238  		}
  1239  		var id = asset.ID
  1240  		responseAsset.ID = &id
  1241  		nativeResult.Assets = append(nativeResult.Assets, responseAsset)
  1242  	}
  1243  
  1244  	// dsp imp click tracking + imp click tracking
  1245  	var eventTrackers []nativeResponse.EventTracker
  1246  	if content.Monitor != nil {
  1247  		for _, monitor := range content.Monitor {
  1248  			if len(monitor.Url) == 0 {
  1249  				continue
  1250  			}
  1251  			if monitor.EventType == "click" {
  1252  				linkObject.ClickTrackers = append(linkObject.ClickTrackers, monitor.Url...)
  1253  			}
  1254  			if monitor.EventType == "imp" {
  1255  				for i := range monitor.Url {
  1256  					var eventTracker nativeResponse.EventTracker
  1257  					eventTracker.Event = native1.EventTypeImpression
  1258  					eventTracker.Method = native1.EventTrackingMethodImage
  1259  					eventTracker.URL = monitor.Url[i]
  1260  					eventTrackers = append(eventTrackers, eventTracker)
  1261  				}
  1262  			}
  1263  		}
  1264  	}
  1265  	nativeResult.EventTrackers = eventTrackers
  1266  	nativeResult.Link = linkObject
  1267  	nativeResult.Ver = "1.1"
  1268  	if nativePayload.Ver != "" {
  1269  		nativeResult.Ver = nativePayload.Ver
  1270  	}
  1271  
  1272  	var result []byte
  1273  	if result, err = jsonEncode(nativeResult); err != nil {
  1274  		return "", 0, 0, err
  1275  	}
  1276  	return strings.Replace(string(result), "\n", "", -1), adWidth, adHeight, nil
  1277  }
  1278  
  1279  func getDecodeValue(str string) string {
  1280  	if str == "" {
  1281  		return ""
  1282  	}
  1283  	if decodeValue, err := url.QueryUnescape(str); err == nil {
  1284  		return decodeValue
  1285  	} else {
  1286  		return ""
  1287  	}
  1288  }
  1289  
  1290  func jsonEncode(nativeResult nativeResponse.Response) ([]byte, error) {
  1291  	buffer := &bytes.Buffer{}
  1292  	encoder := json.NewEncoder(buffer)
  1293  	encoder.SetEscapeHTML(false)
  1294  	err := encoder.Encode(nativeResult)
  1295  	return buffer.Bytes(), err
  1296  }
  1297  
  1298  // extractAdmPicture: For banner single picture
  1299  func (a *adapter) extractAdmPicture(content *content) (adm string, adWidth int64, adHeight int64, err error) {
  1300  	if content == nil {
  1301  		return "", 0, 0, errors.New("extract Adm failed: content is empty")
  1302  	}
  1303  
  1304  	var clickUrl = ""
  1305  	clickUrl, err = a.getClickUrl(content)
  1306  	if err != nil {
  1307  		return "", 0, 0, err
  1308  	}
  1309  
  1310  	var imageInfoUrl string
  1311  	if content.MetaData.ImageInfo != nil {
  1312  		imageInfoUrl = content.MetaData.ImageInfo[0].Url
  1313  		adHeight = content.MetaData.ImageInfo[0].Height
  1314  		adWidth = content.MetaData.ImageInfo[0].Width
  1315  	} else {
  1316  		return "", 0, 0, errors.New("content.MetaData.ImageInfo is empty")
  1317  	}
  1318  
  1319  	var imageTitle = ""
  1320  	imageTitle = getDecodeValue(content.MetaData.Title)
  1321  	// dspImp, Imp, dspClick, Click tracking all can be found in MonitorUrl(imp ,click)
  1322  	dspImpTrackings, dspClickTrackings := getDspImpClickTrackings(content)
  1323  	var dspImpTrackings2StrImg strings.Builder
  1324  	for i := 0; i < len(dspImpTrackings); i++ {
  1325  		dspImpTrackings2StrImg.WriteString(`<img height="1" width="1" src='`)
  1326  		dspImpTrackings2StrImg.WriteString(dspImpTrackings[i])
  1327  		dspImpTrackings2StrImg.WriteString(`' >  `)
  1328  	}
  1329  
  1330  	adm = "<style> html, body  " +
  1331  		"{ margin: 0; padding: 0; width: 100%; height: 100%; vertical-align: middle; }  " +
  1332  		"html  " +
  1333  		"{ display: table; }  " +
  1334  		"body { display: table-cell; vertical-align: middle; text-align: center; -webkit-text-size-adjust: none; }  " +
  1335  		"</style> " +
  1336  		`<span class="title-link advertiser_label">` + imageTitle + "</span> " +
  1337  		"<a href='" + clickUrl + `' style="text-decoration:none" ` +
  1338  		"onclick=sendGetReq()> " +
  1339  		"<img src='" + imageInfoUrl + "' width='" + strconv.Itoa(int(adWidth)) + "' height='" + strconv.Itoa(int(adHeight)) + "'/> " +
  1340  		"</a> " +
  1341  		dspImpTrackings2StrImg.String() +
  1342  		`<script type="text/javascript">` +
  1343  		"var dspClickTrackings = [" + dspClickTrackings + "];" +
  1344  		"function sendGetReq() {" +
  1345  		"sendSomeGetReq(dspClickTrackings)" +
  1346  		"}" +
  1347  		"function sendOneGetReq(url) {" +
  1348  		"var req = new XMLHttpRequest();" +
  1349  		"req.open('GET', url, true);" +
  1350  		"req.send(null);" +
  1351  		"}" +
  1352  		"function sendSomeGetReq(urls) {" +
  1353  		"for (var i = 0; i < urls.length; i++) {" +
  1354  		"sendOneGetReq(urls[i]);" +
  1355  		"}" +
  1356  		"}" +
  1357  		"</script>"
  1358  	return adm, adWidth, adHeight, nil
  1359  }
  1360  
  1361  // for Interactiontype == appPromotion, clickUrl is intent
  1362  func (a *adapter) getClickUrl(content *content) (clickUrl string, err error) {
  1363  	if content.Interactiontype == appPromotion {
  1364  		if content.MetaData.Intent != "" {
  1365  			clickUrl = getDecodeValue(content.MetaData.Intent)
  1366  		} else {
  1367  			return "", errors.New("content.MetaData.Intent in huaweiads resopnse is empty when interactiontype is appPromotion")
  1368  		}
  1369  	} else {
  1370  		if content.MetaData.ClickUrl != "" {
  1371  			clickUrl = content.MetaData.ClickUrl
  1372  		} else if content.MetaData.Intent != "" {
  1373  			clickUrl = getDecodeValue(content.MetaData.Intent)
  1374  		}
  1375  	}
  1376  	return clickUrl, nil
  1377  }
  1378  
  1379  func getDspImpClickTrackings(content *content) (dspImpTrackings []string, dspClickTrackings string) {
  1380  	for _, monitor := range content.Monitor {
  1381  		if len(monitor.Url) != 0 {
  1382  			switch monitor.EventType {
  1383  			case "imp":
  1384  				dspImpTrackings = monitor.Url
  1385  			case "click":
  1386  				dspClickTrackings = getStrings(monitor.Url)
  1387  			}
  1388  		}
  1389  	}
  1390  	return dspImpTrackings, dspClickTrackings
  1391  }
  1392  
  1393  func getStrings(eles []string) string {
  1394  	if len(eles) == 0 {
  1395  		return ""
  1396  	}
  1397  	var strs strings.Builder
  1398  	for i := 0; i < len(eles); i++ {
  1399  		strs.WriteString("\"" + eles[i] + "\"")
  1400  		if i < len(eles)-1 {
  1401  			strs.WriteString(",")
  1402  		}
  1403  	}
  1404  	return strs.String()
  1405  }
  1406  
  1407  // getDuration: millisecond -> format: 00:00:00.000
  1408  func getDuration(duration int64) string {
  1409  	var dur time.Duration = time.Duration(duration) * time.Millisecond
  1410  	t := time.Time{}.Add(dur)
  1411  	return t.Format("15:04:05.000")
  1412  }
  1413  
  1414  // extractAdmVideo: get field adm for video, vast 3.0
  1415  func (a *adapter) extractAdmVideo(adType int32, content *content, bidType openrtb_ext.BidType, opentrb2Imp openrtb2.Imp) (adm string,
  1416  	adWidth int64, adHeight int64, err error) {
  1417  	if content == nil {
  1418  		return "", 0, 0, errors.New("extract Adm for video failed: content is empty")
  1419  	}
  1420  
  1421  	var clickUrl = ""
  1422  	clickUrl, err = a.getClickUrl(content)
  1423  	if err != nil {
  1424  		return "", 0, 0, err
  1425  	}
  1426  
  1427  	var mime = "video/mp4"
  1428  	var resourceUrl = ""
  1429  	var duration = ""
  1430  	if adType == roll {
  1431  		// roll ad get information from mediafile
  1432  		if content.MetaData.MediaFile.Mime != "" {
  1433  			mime = content.MetaData.MediaFile.Mime
  1434  		}
  1435  		adWidth = content.MetaData.MediaFile.Width
  1436  		adHeight = content.MetaData.MediaFile.Height
  1437  		if content.MetaData.MediaFile.Url != "" {
  1438  			resourceUrl = content.MetaData.MediaFile.Url
  1439  		} else {
  1440  			return "", 0, 0, errors.New("extract Adm for video failed: Content.MetaData.MediaFile.Url is empty")
  1441  		}
  1442  		duration = getDuration(content.MetaData.Duration)
  1443  	} else {
  1444  		if content.MetaData.VideoInfo.VideoDownloadUrl != "" {
  1445  			resourceUrl = content.MetaData.VideoInfo.VideoDownloadUrl
  1446  		} else {
  1447  			return "", 0, 0, errors.New("extract Adm for video failed: content.MetaData.VideoInfo.VideoDownloadUrl is empty")
  1448  		}
  1449  		if content.MetaData.VideoInfo.Width != 0 && content.MetaData.VideoInfo.Height != 0 {
  1450  			adWidth = int64(content.MetaData.VideoInfo.Width)
  1451  			adHeight = int64(content.MetaData.VideoInfo.Height)
  1452  		} else if bidType == openrtb_ext.BidTypeVideo {
  1453  			if opentrb2Imp.Video != nil && opentrb2Imp.Video.W != nil && *opentrb2Imp.Video.W != 0 && opentrb2Imp.Video.H != nil && *opentrb2Imp.Video.H != 0 {
  1454  				adWidth = *opentrb2Imp.Video.W
  1455  				adHeight = *opentrb2Imp.Video.H
  1456  			}
  1457  		} else {
  1458  			return "", 0, 0, errors.New("extract Adm for video failed: cannot get video width, height")
  1459  		}
  1460  		duration = getDuration(int64(content.MetaData.VideoInfo.VideoDuration))
  1461  	}
  1462  
  1463  	var adTitle = getDecodeValue(content.MetaData.Title)
  1464  	var adId = content.Contentid
  1465  	var creativeId = content.Contentid
  1466  	var trackingEvents strings.Builder
  1467  	var dspImpTracking2Str = ""
  1468  	var dspClickTracking2Str = ""
  1469  	var errorTracking2Str = ""
  1470  	for _, monitor := range content.Monitor {
  1471  		if len(monitor.Url) == 0 {
  1472  			continue
  1473  		}
  1474  		var event = ""
  1475  		switch monitor.EventType {
  1476  		case "vastError":
  1477  			errorTracking2Str = getVastImpClickErrorTrackingUrls(monitor.Url, "vastError")
  1478  		case "imp":
  1479  			dspImpTracking2Str = getVastImpClickErrorTrackingUrls(monitor.Url, "imp")
  1480  		case "click":
  1481  			dspClickTracking2Str = getVastImpClickErrorTrackingUrls(monitor.Url, "click")
  1482  		case "userclose":
  1483  			event = "skip&closeLinear"
  1484  		case "playStart":
  1485  			event = "start"
  1486  		case "playEnd":
  1487  			event = "complete"
  1488  		case "playResume":
  1489  			event = "resume"
  1490  		case "playPause":
  1491  			event = "pause"
  1492  		case "soundClickOff":
  1493  			event = "mute"
  1494  		case "soundClickOn":
  1495  			event = "unmute"
  1496  		default:
  1497  		}
  1498  		if event != "" {
  1499  			if event != "skip&closeLinear" {
  1500  				trackingEvents.WriteString(getVastEventTrackingUrls(monitor.Url, event))
  1501  			} else {
  1502  				trackingEvents.WriteString(getVastEventTrackingUrls(monitor.Url, "skip&closeLinear"))
  1503  			}
  1504  		}
  1505  	}
  1506  
  1507  	// Only for rewarded video
  1508  	var rewardedVideoPart = ""
  1509  	var isAddRewardedVideoPart = true
  1510  	if adType == rewarded {
  1511  		var staticImageUrl = ""
  1512  		var staticImageHeight = ""
  1513  		var staticImageWidth = ""
  1514  		var staticImageType = "image/png"
  1515  		if len(content.MetaData.Icon) > 0 && content.MetaData.Icon[0].Url != "" {
  1516  			staticImageUrl = content.MetaData.Icon[0].Url
  1517  			if content.MetaData.Icon[0].Height > 0 && content.MetaData.Icon[0].Width > 0 {
  1518  				staticImageHeight = strconv.Itoa(int(content.MetaData.Icon[0].Height))
  1519  				staticImageWidth = strconv.Itoa(int(content.MetaData.Icon[0].Width))
  1520  			} else {
  1521  				staticImageHeight = strconv.Itoa(int(adHeight))
  1522  				staticImageWidth = strconv.Itoa(int(adWidth))
  1523  			}
  1524  		} else if len(content.MetaData.ImageInfo) > 0 && content.MetaData.ImageInfo[0].Url != "" {
  1525  			staticImageUrl = content.MetaData.ImageInfo[0].Url
  1526  			if content.MetaData.ImageInfo[0].Height > 0 && content.MetaData.ImageInfo[0].Width > 0 {
  1527  				staticImageHeight = strconv.Itoa(int(content.MetaData.ImageInfo[0].Height))
  1528  				staticImageWidth = strconv.Itoa(int(content.MetaData.ImageInfo[0].Width))
  1529  			} else {
  1530  				staticImageHeight = strconv.Itoa(int(adHeight))
  1531  				staticImageWidth = strconv.Itoa(int(adWidth))
  1532  			}
  1533  		} else {
  1534  			isAddRewardedVideoPart = false
  1535  		}
  1536  		if isAddRewardedVideoPart {
  1537  			rewardedVideoPart = `<Creative adId="` + adId + `" id="` + creativeId + `">` +
  1538  				"<CompanionAds>" +
  1539  				`<Companion width="` + staticImageWidth + `" height="` + staticImageHeight + `">` +
  1540  				`<StaticResource creativeType="` + staticImageType + `"><![CDATA[` + staticImageUrl + `]]></StaticResource>` +
  1541  				"<CompanionClickThrough><![CDATA[" + clickUrl + "]]></CompanionClickThrough>" +
  1542  				"</Companion>" +
  1543  				"</CompanionAds>" +
  1544  				"</Creative>"
  1545  		}
  1546  	}
  1547  
  1548  	adm = `<?xml version="1.0" encoding="UTF-8"?>` +
  1549  		`<VAST version="3.0">` +
  1550  		`<Ad id="` + adId + `"><InLine>` +
  1551  		"<AdSystem>HuaweiAds</AdSystem>" +
  1552  		"<AdTitle>" + adTitle + "</AdTitle>" +
  1553  		errorTracking2Str + dspImpTracking2Str +
  1554  		"<Creatives>" +
  1555  		`<Creative adId="` + adId + `" id="` + creativeId + `">` +
  1556  		"<Linear>" +
  1557  		"<Duration>" + duration + "</Duration>" +
  1558  		"<TrackingEvents>" + trackingEvents.String() + "</TrackingEvents>" +
  1559  		"<VideoClicks>" +
  1560  		"<ClickThrough><![CDATA[" + clickUrl + "]]></ClickThrough>" +
  1561  		dspClickTracking2Str +
  1562  		"</VideoClicks>" +
  1563  		"<MediaFiles>" +
  1564  		`<MediaFile delivery="progressive" type="` + mime + `" width="` + strconv.Itoa(int(adWidth)) + `" ` +
  1565  		`height="` + strconv.Itoa(int(adHeight)) + `" scalable="true" maintainAspectRatio="true"> ` +
  1566  		"<![CDATA[" + resourceUrl + "]]>" +
  1567  		"</MediaFile>" +
  1568  		"</MediaFiles>" +
  1569  		"</Linear>" +
  1570  		"</Creative>" + rewardedVideoPart +
  1571  		"</Creatives>" +
  1572  		"</InLine>" +
  1573  		"</Ad>" +
  1574  		"</VAST>"
  1575  	return adm, adWidth, adHeight, nil
  1576  }
  1577  
  1578  func getVastImpClickErrorTrackingUrls(urls []string, eventType string) (result string) {
  1579  	var trackingUrls strings.Builder
  1580  	for _, url := range urls {
  1581  		if eventType == "click" {
  1582  			trackingUrls.WriteString("<ClickTracking><![CDATA[")
  1583  			trackingUrls.WriteString(url)
  1584  			trackingUrls.WriteString("]]></ClickTracking>")
  1585  		} else if eventType == "imp" {
  1586  			trackingUrls.WriteString("<Impression><![CDATA[")
  1587  			trackingUrls.WriteString(url)
  1588  			trackingUrls.WriteString("]]></Impression>")
  1589  		} else if eventType == "vastError" {
  1590  			trackingUrls.WriteString("<Error><![CDATA[")
  1591  			trackingUrls.WriteString(url)
  1592  			trackingUrls.WriteString("&et=[ERRORCODE]]]></Error>")
  1593  		}
  1594  	}
  1595  	return trackingUrls.String()
  1596  }
  1597  
  1598  func getVastEventTrackingUrls(urls []string, eventType string) (result string) {
  1599  	var trackingUrls strings.Builder
  1600  	for _, eventUrl := range urls {
  1601  		if eventType == "skip&closeLinear" {
  1602  			trackingUrls.WriteString(`<Tracking event="skip"><![CDATA[`)
  1603  			trackingUrls.WriteString(eventUrl)
  1604  			trackingUrls.WriteString(`]]></Tracking><Tracking event="closeLinear"><![CDATA[`)
  1605  			trackingUrls.WriteString(eventUrl)
  1606  			trackingUrls.WriteString("]]></Tracking>")
  1607  		} else {
  1608  			trackingUrls.WriteString(`<Tracking event="`)
  1609  			trackingUrls.WriteString(eventType)
  1610  			trackingUrls.WriteString(`"><![CDATA[`)
  1611  			trackingUrls.WriteString(eventUrl)
  1612  			trackingUrls.WriteString("]]></Tracking>")
  1613  		}
  1614  	}
  1615  	return trackingUrls.String()
  1616  }
  1617  
  1618  func computeHmacSha256(message string, signKey string) string {
  1619  	h := hmac.New(sha256.New, []byte(signKey))
  1620  	h.Write([]byte(message))
  1621  	return hex.EncodeToString(h.Sum(nil))
  1622  }
  1623  
  1624  // getDigestAuthorization: get digest authorization for request header
  1625  func getDigestAuthorization(huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds, isTestAuthorization bool) string {
  1626  	var nonce = strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
  1627  	// this is for test case, time 2021/8/20 19:30
  1628  	if isTestAuthorization {
  1629  		nonce = "1629473330823"
  1630  	}
  1631  	publisher_id := strings.TrimSpace(huaweiAdsImpExt.PublisherId)
  1632  	sign_key := strings.TrimSpace(huaweiAdsImpExt.SignKey)
  1633  	key_id := strings.TrimSpace(huaweiAdsImpExt.KeyId)
  1634  
  1635  	var apiKey = publisher_id + ":ppsadx/getResult:" + sign_key
  1636  	return "Digest username=" + publisher_id + "," +
  1637  		"realm=ppsadx/getResult," +
  1638  		"nonce=" + nonce + "," +
  1639  		"response=" + computeHmacSha256(nonce+":POST:/ppsadx/getResult", apiKey) + "," +
  1640  		"algorithm=HmacSHA256,usertype=1,keyid=" + key_id
  1641  }