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