1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.adservices.test.scenario.adservices.utils;
18 
19 import android.adservices.adselection.AdSelectionConfig;
20 import android.adservices.common.AdData;
21 import android.adservices.common.AdSelectionSignals;
22 import android.adservices.common.AdTechIdentifier;
23 import android.adservices.customaudience.CustomAudience;
24 import android.adservices.customaudience.TrustedBiddingData;
25 import android.net.Uri;
26 import android.util.Log;
27 
28 import com.google.common.collect.ImmutableList;
29 
30 import java.io.IOException;
31 import java.net.HttpURLConnection;
32 import java.net.MalformedURLException;
33 import java.net.ProtocolException;
34 import java.net.URL;
35 import java.time.Duration;
36 import java.time.Instant;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 
43 public class StaticAdTechServerUtils {
44     private static final String TAG = "StaticAdTechServerUtils";
45 
46     private static final String SERVER_BASE_ADDRESS_FORMAT = "https://%s";
47     private static final List<String> BUYER_BASE_DOMAINS =
48             ImmutableList.of(
49                     "performance-fledge-static-5jyy5ulagq-uc.a.run.app",
50                     "performance-fledge-static-2-5jyy5ulagq-uc.a.run.app",
51                     "performance-fledge-static-3-5jyy5ulagq-uc.a.run.app",
52                     "performance-fledge-static-4-5jyy5ulagq-uc.a.run.app",
53                     "performance-fledge-static-5-5jyy5ulagq-uc.a.run.app");
54 
55     // All details needed to create AdSelectionConfig
56     private static final String SELLER_BASE_DOMAIN =
57             "performance-fledge-static-5jyy5ulagq-uc.a.run.app";
58     private static final AdTechIdentifier SELLER = AdTechIdentifier.fromString(SELLER_BASE_DOMAIN);
59     private static final String DECISION_LOGIC_PATH = "/seller/decision/simple_logic";
60     private static final String TRUSTED_SCORING_SIGNALS_PATH = "/trusted/scoringsignals/simple";
61     private static final AdSelectionSignals AD_SELECTION_SIGNALS =
62             AdSelectionSignals.fromString("{\"ad_selection_signals\":1}");
63     private static final AdSelectionSignals SELLER_SIGNALS =
64             AdSelectionSignals.fromString("{\"test_seller_signals\":1}");
65 
66     // All details needed to create custom audiences
67     private static final AdSelectionSignals VALID_USER_BIDDING_SIGNALS =
68             AdSelectionSignals.fromString("{'valid': 'yep', 'opaque': 'definitely'}");
69     private static final String AD_URI_PATH_FORMAT = "/render/%s/%s";
70     private static final String DAILY_UPDATE_PATH_FORMAT = "/dailyupdate/%s";
71     private static final String BIDDING_LOGIC_PATH = "/buyer/bidding/simple_logic";
72     private static final String TRUSTED_BIDDING_SIGNALS_PATH = "/trusted/biddingsignals/simple";
73     private static final String CUSTOM_AUDIENCE_PREFIX = "GENERIC_CA_";
74     private static final Duration CUSTOM_AUDIENCE_EXPIRE_IN = Duration.ofDays(1);
75     private static final Instant VALID_ACTIVATION_TIME = Instant.now();
76     private static final Instant VALID_EXPIRATION_TIME =
77             VALID_ACTIVATION_TIME.plus(CUSTOM_AUDIENCE_EXPIRE_IN);
78     private static final ArrayList<String> VALID_TRUSTED_BIDDING_KEYS =
79             new ArrayList<>(Arrays.asList("example", "valid", "list", "of", "keys"));
80 
81     private final List<AdTechIdentifier> mCustomAudienceBuyers;
82     private final Map<AdTechIdentifier, AdSelectionSignals> mPerBuyerSignals;
83     private final int mNumberOfBuyers;
84 
StaticAdTechServerUtils( int numberOfBuyers, List<AdTechIdentifier> customAudienceBuyers, Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals)85     private StaticAdTechServerUtils(
86             int numberOfBuyers,
87             List<AdTechIdentifier> customAudienceBuyers,
88             Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals) {
89         this.mNumberOfBuyers = numberOfBuyers;
90         this.mCustomAudienceBuyers = customAudienceBuyers;
91         this.mPerBuyerSignals = perBuyerSignals;
92     }
93 
94     /**
95      * Makes a warmup call to all the servers so that servers don't have cold start latency during
96      * test runs.
97      */
warmupServers()98     public static void warmupServers() {
99         for (String domain : BUYER_BASE_DOMAINS) {
100             String buyerBaseAddress = String.format("https://%s", domain);
101             try {
102                 URL url = new URL(buyerBaseAddress);
103                 HttpURLConnection connection = (HttpURLConnection) url.openConnection();
104                 connection.setRequestMethod("GET");
105                 connection.getInputStream();
106             } catch (MalformedURLException e) {
107                 Log.e(TAG, "Parsing ad render url failed", e);
108             } catch (ProtocolException e) {
109                 Log.e(TAG, "Invalid protocol for http call", e);
110             } catch (IOException e) {
111                 Log.e(TAG, "Ad rendering call failed with exception", e);
112             }
113         }
114     }
115 
withNumberOfBuyers(int numberOfBuyers)116     public static StaticAdTechServerUtils withNumberOfBuyers(int numberOfBuyers) {
117         if (numberOfBuyers > BUYER_BASE_DOMAINS.size()) {
118             throw new IllegalArgumentException(
119                     "Number of buyers should be less than available domains : "
120                             + BUYER_BASE_DOMAINS.size());
121         }
122 
123         List<AdTechIdentifier> customAudienceBuyers = new ArrayList<>(numberOfBuyers);
124         Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals = new HashMap<>(numberOfBuyers);
125         for (int i = 0; i < numberOfBuyers; i++) {
126             AdTechIdentifier buyer = AdTechIdentifier.fromString(BUYER_BASE_DOMAINS.get(i));
127             customAudienceBuyers.add(buyer);
128             perBuyerSignals.put(buyer, AdSelectionSignals.fromString("{\"buyer_signals\":1}"));
129         }
130 
131         return new StaticAdTechServerUtils(numberOfBuyers, customAudienceBuyers, perBuyerSignals);
132     }
133 
createAndGetAdSelectionConfig()134     public AdSelectionConfig createAndGetAdSelectionConfig() {
135         String sellerBaseAddress = String.format(SERVER_BASE_ADDRESS_FORMAT, SELLER_BASE_DOMAIN);
136 
137         return new AdSelectionConfig.Builder()
138                 .setSeller(SELLER)
139                 .setDecisionLogicUri(Uri.parse(sellerBaseAddress + DECISION_LOGIC_PATH))
140                 .setCustomAudienceBuyers(mCustomAudienceBuyers)
141                 .setAdSelectionSignals(AD_SELECTION_SIGNALS)
142                 .setSellerSignals(SELLER_SIGNALS)
143                 .setPerBuyerSignals(mPerBuyerSignals)
144                 .setTrustedScoringSignalsUri(
145                         Uri.parse(sellerBaseAddress + TRUSTED_SCORING_SIGNALS_PATH))
146                 .build();
147     }
148 
createAndGetCustomAudiences( int numberOfCustomAudiencesPerBuyer, int numberOfAdsPerCustomAudiences)149     public List<CustomAudience> createAndGetCustomAudiences(
150             int numberOfCustomAudiencesPerBuyer, int numberOfAdsPerCustomAudiences) {
151         List<CustomAudience> customAudiences = new ArrayList<>();
152         List<Double> bidsForBuyer = new ArrayList<>();
153 
154         for (int i = 1; i <= numberOfAdsPerCustomAudiences; i++) {
155             bidsForBuyer.add(i + 0.1);
156         }
157 
158         for (int buyerIndex = 0; buyerIndex < mNumberOfBuyers; buyerIndex++) {
159             for (int customAudienceIndex = 1;
160                     customAudienceIndex <= numberOfCustomAudiencesPerBuyer;
161                     customAudienceIndex++) {
162                 CustomAudience customAudience =
163                         createCustomAudience(
164                                 buyerIndex,
165                                 customAudienceIndex,
166                                 bidsForBuyer,
167                                 VALID_ACTIVATION_TIME,
168                                 VALID_EXPIRATION_TIME);
169                 customAudiences.add(customAudience);
170             }
171         }
172 
173         return customAudiences;
174     }
175 
createCustomAudience( int buyerIndex, int customAudienceIndex, List<Double> bids, Instant activationTime, Instant expirationTime)176     private CustomAudience createCustomAudience(
177             int buyerIndex,
178             int customAudienceIndex,
179             List<Double> bids,
180             Instant activationTime,
181             Instant expirationTime) {
182         // Generate ads for with bids provided
183         List<AdData> ads = new ArrayList<>();
184         String customAudienceName = CUSTOM_AUDIENCE_PREFIX + customAudienceIndex;
185 
186         // Create ads with the custom audience name and bid number as the ad URI
187         // Add the bid value to the metadata
188         for (int i = 0; i < bids.size(); i++) {
189             String adRenderUri = getAdRenderUri(buyerIndex, customAudienceName, i + 1);
190 
191             ads.add(
192                     new AdData.Builder()
193                             .setRenderUri(Uri.parse(adRenderUri))
194                             .setMetadata("{\"bid\":" + (bids.get(i) + buyerIndex * 0.01) + "}")
195                             .build());
196         }
197 
198         AdTechIdentifier buyerIdentifier = mCustomAudienceBuyers.get(buyerIndex);
199         String dailyUpdatePath = String.format(DAILY_UPDATE_PATH_FORMAT, customAudienceName);
200         String buyerBaseAddress = String.format("https://%s", BUYER_BASE_DOMAINS.get(buyerIndex));
201         return new CustomAudience.Builder()
202                 .setBuyer(buyerIdentifier)
203                 .setName(customAudienceName)
204                 .setActivationTime(activationTime)
205                 .setExpirationTime(expirationTime)
206                 .setDailyUpdateUri(Uri.parse(buyerBaseAddress + dailyUpdatePath))
207                 .setUserBiddingSignals(VALID_USER_BIDDING_SIGNALS)
208                 .setTrustedBiddingData(
209                         getValidTrustedBiddingDataByBuyer(
210                                 Uri.parse(buyerBaseAddress + TRUSTED_BIDDING_SIGNALS_PATH)))
211                 .setBiddingLogicUri(Uri.parse(buyerBaseAddress + BIDDING_LOGIC_PATH))
212                 .setAds(ads)
213                 .build();
214     }
215 
getAdRenderUri(int buyerIndex, String ca, int adId)216     public static String getAdRenderUri(int buyerIndex, String ca, int adId) {
217         String adPath = String.format(AD_URI_PATH_FORMAT, ca, adId);
218         String adRenderUri =
219                 String.format(SERVER_BASE_ADDRESS_FORMAT, BUYER_BASE_DOMAINS.get(buyerIndex))
220                         + adPath;
221         return adRenderUri;
222     }
223 
getValidTrustedBiddingDataByBuyer( Uri validTrustedBiddingUri)224     private static TrustedBiddingData getValidTrustedBiddingDataByBuyer(
225             Uri validTrustedBiddingUri) {
226         return new TrustedBiddingData.Builder()
227                 .setTrustedBiddingKeys(getValidTrustedBiddingKeys())
228                 .setTrustedBiddingUri(validTrustedBiddingUri)
229                 .build();
230     }
231 
getValidTrustedBiddingKeys()232     private static ImmutableList<String> getValidTrustedBiddingKeys() {
233         return ImmutableList.copyOf(VALID_TRUSTED_BIDDING_KEYS);
234     }
235 }
236