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 com.android.adservices.service.measurement;
18 
19 import android.net.Uri;
20 
21 import com.android.adservices.LogUtil;
22 import com.android.adservices.common.WebUtil;
23 import com.android.adservices.service.FakeFlagsFactory;
24 import com.android.adservices.service.measurement.aggregation.AggregatableAttributionSource;
25 import com.android.adservices.service.measurement.noising.Combinatorics;
26 import com.android.adservices.service.measurement.util.UnsignedLong;
27 
28 import org.json.JSONArray;
29 import org.json.JSONException;
30 import org.json.JSONObject;
31 
32 import java.math.BigInteger;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.TreeMap;
39 import java.util.UUID;
40 import java.util.concurrent.TimeUnit;
41 
42 public final class SourceFixture {
SourceFixture()43     private SourceFixture() { }
44 
45     // Assume the field values in this Source.Builder have no relation to the field values in
46     // {@link ValidSourceParams}
getMinimalValidSourceBuilder()47     public static Source.Builder getMinimalValidSourceBuilder() {
48         return new Source.Builder()
49                 .setPublisher(ValidSourceParams.PUBLISHER)
50                 .setAppDestinations(ValidSourceParams.ATTRIBUTION_DESTINATIONS)
51                 .setEnrollmentId(ValidSourceParams.ENROLLMENT_ID)
52                 .setRegistrant(ValidSourceParams.REGISTRANT)
53                 .setRegistrationOrigin(ValidSourceParams.REGISTRATION_ORIGIN);
54     }
55 
56     /**
57      * @return The minimum valid source with attribiton scopes.
58      */
getMinimalValidSourceWithAttributionScope()59     public static Source.Builder getMinimalValidSourceWithAttributionScope() {
60         return getMinimalValidSourceBuilder()
61                 .setAttributionScopes(ValidSourceParams.ATTRIBUTION_SCOPES)
62                 .setAttributionScopeLimit(ValidSourceParams.ATTRIBUTION_SCOPE_LIMIT)
63                 .setMaxEventStates(ValidSourceParams.MAX_NUM_VIEW_STATES);
64     }
65 
66     // Assume the field values in this Source have no relation to the field values in
67     // {@link ValidSourceParams}
getValidSource()68     public static Source getValidSource() {
69         return getValidSourceBuilder().build();
70     }
71 
getValidSourceBuilder()72     public static Source.Builder getValidSourceBuilder() {
73         return new Source.Builder()
74                 .setId(UUID.randomUUID().toString())
75                 .setEventId(ValidSourceParams.SOURCE_EVENT_ID)
76                 .setPublisher(ValidSourceParams.PUBLISHER)
77                 .setAppDestinations(ValidSourceParams.ATTRIBUTION_DESTINATIONS)
78                 .setWebDestinations(ValidSourceParams.WEB_DESTINATIONS)
79                 .setEnrollmentId(ValidSourceParams.ENROLLMENT_ID)
80                 .setRegistrant(ValidSourceParams.REGISTRANT)
81                 .setEventTime(ValidSourceParams.SOURCE_EVENT_TIME)
82                 .setExpiryTime(ValidSourceParams.EXPIRY_TIME)
83                 .setEventReportWindow(ValidSourceParams.EXPIRY_TIME)
84                 .setAggregatableReportWindow(ValidSourceParams.EXPIRY_TIME)
85                 .setPriority(ValidSourceParams.PRIORITY)
86                 .setSourceType(ValidSourceParams.SOURCE_TYPE)
87                 .setInstallAttributionWindow(ValidSourceParams.INSTALL_ATTRIBUTION_WINDOW)
88                 .setInstallCooldownWindow(ValidSourceParams.INSTALL_COOLDOWN_WINDOW)
89                 .setReinstallReattributionWindow(ValidSourceParams.REINSTALL_REATTRIBUTION_WINDOW)
90                 .setAttributionMode(ValidSourceParams.ATTRIBUTION_MODE)
91                 .setAggregateSource(ValidSourceParams.buildAggregateSource())
92                 .setFilterDataString(ValidSourceParams.buildFilterDataString())
93                 .setSharedFilterDataKeys(ValidSourceParams.SHARED_FILTER_DATA_KEYS)
94                 .setIsDebugReporting(true)
95                 .setRegistrationId(ValidSourceParams.REGISTRATION_ID)
96                 .setSharedAggregationKeys(ValidSourceParams.SHARED_AGGREGATE_KEYS)
97                 .setInstallTime(ValidSourceParams.INSTALL_TIME)
98                 .setPlatformAdId(ValidSourceParams.PLATFORM_AD_ID)
99                 .setDebugAdId(ValidSourceParams.DEBUG_AD_ID)
100                 .setRegistrationOrigin(ValidSourceParams.REGISTRATION_ORIGIN)
101                 .setCoarseEventReportDestinations(true)
102                 .setSharedDebugKey(ValidSourceParams.SHARED_DEBUG_KEY)
103                 .setAttributionScopes(ValidSourceParams.ATTRIBUTION_SCOPES)
104                 .setAttributionScopeLimit(ValidSourceParams.ATTRIBUTION_SCOPE_LIMIT)
105                 .setMaxEventStates(ValidSourceParams.MAX_NUM_VIEW_STATES)
106                 .setAttributedTriggers(new ArrayList<>());
107     }
108 
109     public static class ValidSourceParams {
110         public static final Long EXPIRY_TIME = 8640000010L;
111         public static final Long PRIORITY = 100L;
112         public static final UnsignedLong SOURCE_EVENT_ID = new UnsignedLong(1L);
113         public static final Long SOURCE_EVENT_TIME = 8640000000L;
114         public static final List<Uri> ATTRIBUTION_DESTINATIONS =
115                 List.of(Uri.parse("android-app://com.destination"));
116         public static List<Uri> WEB_DESTINATIONS = List.of(Uri.parse("https://destination.com"));
117         public static final Uri PUBLISHER = Uri.parse("android-app://com.publisher");
118         public static final Uri WEB_PUBLISHER = Uri.parse("https://publisher.com");
119         public static final Uri REGISTRANT = Uri.parse("android-app://com.registrant");
120         public static final String ENROLLMENT_ID = "enrollment-id";
121         public static final Source.SourceType SOURCE_TYPE = Source.SourceType.EVENT;
122         public static final Long INSTALL_ATTRIBUTION_WINDOW = 841839879274L;
123         public static final Long INSTALL_COOLDOWN_WINDOW = 8418398274L;
124         public static final UnsignedLong DEBUG_KEY = new UnsignedLong(7834690L);
125         public static final @Source.AttributionMode int ATTRIBUTION_MODE =
126                 Source.AttributionMode.TRUTHFULLY;
127         public static final int AGGREGATE_CONTRIBUTIONS = 0;
128         public static final String REGISTRATION_ID = "R1";
129         public static final String SHARED_AGGREGATE_KEYS = "[\"key1\"]";
130         public static final String SHARED_FILTER_DATA_KEYS =
131                 "[\"conversion_subdomain\", \"product\"]";
132         public static final Long INSTALL_TIME = 100L;
133         public static final String PLATFORM_AD_ID = "test-platform-ad-id";
134         public static final String DEBUG_AD_ID = "test-debug-ad-id";
135         public static final Uri REGISTRATION_ORIGIN =
136                 WebUtil.validUri("https://subdomain.example.test");
137         public static final UnsignedLong SHARED_DEBUG_KEY = new UnsignedLong(834690L);
138         public static final List<String> ATTRIBUTION_SCOPES = List.of("1", "2", "3");
139         public static final Long ATTRIBUTION_SCOPE_LIMIT = 5L;
140         public static final Long MAX_NUM_VIEW_STATES = 10L;
141         public static final Long REINSTALL_REATTRIBUTION_WINDOW = 841839879274L;
142 
buildAggregateSource()143         public static final String buildAggregateSource() {
144             try {
145                 JSONObject jsonObject = new JSONObject();
146                 jsonObject.put("campaignCounts", "0x456");
147                 jsonObject.put("geoValue", "0x159");
148                 return jsonObject.toString();
149             } catch (JSONException e) {
150                 LogUtil.e("JSONException when building aggregate source.");
151             }
152             return null;
153         }
154 
155         /** Creates a filter data string */
buildFilterDataString()156         public static final String buildFilterDataString() {
157             try {
158                 JSONObject filterMap = new JSONObject();
159                 filterMap.put("conversion_subdomain",
160                         new JSONArray(Collections.singletonList("electronics.megastore")));
161                 filterMap.put("product", new JSONArray(Arrays.asList("1234", "2345")));
162                 return filterMap.toString();
163             } catch (JSONException e) {
164                 LogUtil.e("JSONException when building aggregate filter data.");
165             }
166             return null;
167         }
168 
buildAggregatableAttributionSource()169         public static final AggregatableAttributionSource buildAggregatableAttributionSource() {
170             TreeMap<String, BigInteger> aggregateSourceMap = new TreeMap<>();
171             aggregateSourceMap.put("5", new BigInteger("345"));
172             return new AggregatableAttributionSource.Builder()
173                     .setAggregatableSource(aggregateSourceMap)
174                     .setFilterMap(
175                             new FilterMap.Builder()
176                                     .setAttributionFilterMap(
177                                             Map.of(
178                                                     "product", List.of("1234", "4321"),
179                                                     "conversion_subdomain",
180                                                             List.of("electronics.megastore")))
181                                     .build())
182                     .build();
183         }
184     }
185 
186     /** Provides a count-based valid TriggerSpecs. */
getValidTriggerSpecsCountBased()187     public static TriggerSpecs getValidTriggerSpecsCountBased() throws JSONException {
188         String triggerSpecsString =
189                 "[{\"trigger_data\": [1, 2],"
190                         + "\"event_report_windows\": { "
191                         + "\"start_time\": 0, "
192                         + String.format(
193                                 "\"end_times\": [%s, %s]}, ",
194                                 TimeUnit.DAYS.toMillis(2), TimeUnit.DAYS.toMillis(7))
195                         + "\"summary_window_operator\": \"count\", "
196                         + "\"summary_buckets\": [1, 2]}]";
197         Source source =
198                 getMinimalValidSourceBuilder()
199                         .setAttributedTriggers(new ArrayList<>())
200                         .build();
201         TriggerSpecs triggerSpecs = new TriggerSpecs(
202                 triggerSpecArrayFrom(triggerSpecsString), 3, source);
203         // Oblige building privacy parameters for the trigger specs
204         triggerSpecs.getInformationGain(source, FakeFlagsFactory.getFlagsForTest());
205         return triggerSpecs;
206     }
207 
208     /** Provides a count-based valid TriggerSpecs with smaller state space. */
getValidTriggerSpecsCountBasedWithFewerState()209     public static TriggerSpecs getValidTriggerSpecsCountBasedWithFewerState() throws JSONException {
210         String triggerSpecsString =
211                 "[{\"trigger_data\": [1],"
212                         + "\"event_report_windows\": { "
213                         + "\"start_time\": 0, "
214                         + String.format("\"end_times\": [%s]}, ", TimeUnit.DAYS.toMillis(2))
215                         + "\"summary_window_operator\": \"count\", "
216                         + "\"summary_buckets\": [1]}]";
217         Source source = getMinimalValidSourceBuilder().build();
218         TriggerSpecs triggerSpecs = new TriggerSpecs(
219                 triggerSpecArrayFrom(triggerSpecsString), 1, source);
220         // Oblige building privacy parameters for the trigger specs
221         triggerSpecs.getInformationGain(source, FakeFlagsFactory.getFlagsForTest());
222         return triggerSpecs;
223     }
224 
225     /** Provides a valid TriggerSpecs with hardcoded epsilon. */
getValidTriggerSpecsWithNonDefaultEpsilon()226     public static TriggerSpecs getValidTriggerSpecsWithNonDefaultEpsilon() throws JSONException {
227         String triggerSpecsString =
228                 "[{\"trigger_data\": [1],"
229                         + "\"event_report_windows\": { "
230                         + "\"start_time\": 0, "
231                         + String.format("\"end_times\": [%s]}, ", TimeUnit.DAYS.toMillis(2))
232                         + "\"summary_window_operator\": \"count\", "
233                         + "\"summary_buckets\": [1]}]";
234         Source source = getMinimalValidSourceBuilder().build();
235         double mockFlipProbability = Combinatorics.getFlipProbability(5, 3);
236         String jsonString = String.format("{\"flip_probability\": %f}", mockFlipProbability);
237         TriggerSpecs triggerSpecs = new TriggerSpecs(triggerSpecsString, "1", source, jsonString);
238         return triggerSpecs;
239     }
240 
241     /** Provides a value-sum-based valid TriggerSpecs. */
getValidTriggerSpecsValueSum()242     public static TriggerSpecs getValidTriggerSpecsValueSum() throws JSONException {
243         return getValidTriggerSpecsValueSum(3);
244     }
245 
246     /** Provides a value-sum-based valid TriggerSpecs. */
getValidTriggerSpecsValueSum(int maxReports)247     public static TriggerSpecs getValidTriggerSpecsValueSum(int maxReports) throws JSONException {
248         Source source =
249                 getMinimalValidSourceBuilder()
250                         .setAttributedTriggers(new ArrayList<>())
251                         .build();
252         TriggerSpecs triggerSpecs = new TriggerSpecs(
253                 getTriggerSpecValueSumArrayValidBaseline(),
254                 maxReports,
255                 source);
256         // Oblige building privacy parameters for the trigger specs
257         triggerSpecs.getInformationGain(source, FakeFlagsFactory.getFlagsForTest());
258         return triggerSpecs;
259     }
260 
261     /** Provides a value-sum-based valid TriggerSpecs. */
getValidTriggerSpecsValueSumWithStartTime(long startTime)262     public static TriggerSpecs getValidTriggerSpecsValueSumWithStartTime(long startTime)
263             throws JSONException {
264         Source source =
265                 getMinimalValidSourceBuilder()
266                         .setAttributedTriggers(new ArrayList<>())
267                         .build();
268         TriggerSpecs triggerSpecs = new TriggerSpecs(
269                 getTriggerSpecValueSumArrayValidBaseline(startTime),
270                 /* maxReports */ 3,
271                 source);
272         // Oblige building privacy parameters for the trigger specs
273         triggerSpecs.getInformationGain(source, FakeFlagsFactory.getFlagsForTest());
274         return triggerSpecs;
275     }
276 
getValidSourceWithFlexEventReport()277     public static Source getValidSourceWithFlexEventReport() {
278         try {
279             return getValidSourceBuilder()
280                     .setAttributedTriggers(new ArrayList<>())
281                     .setTriggerSpecs(getValidTriggerSpecsCountBased())
282                     .setMaxEventLevelReports(getValidTriggerSpecsCountBased().getMaxReports())
283                     .build();
284         } catch (JSONException e) {
285             return null;
286         }
287     }
288 
getValidSourceWithFlexEventReportWithFewerState()289     public static Source getValidSourceWithFlexEventReportWithFewerState() {
290         try {
291             return getMinimalValidSourceBuilder()
292                     .setAttributedTriggers(new ArrayList<>())
293                     .setTriggerSpecs(getValidTriggerSpecsCountBasedWithFewerState())
294                     .setMaxEventLevelReports(
295                             getValidTriggerSpecsCountBasedWithFewerState().getMaxReports())
296                     .build();
297         } catch (JSONException e) {
298             return null;
299         }
300     }
301 
302     /** Provide a Source with hardcoded epsilon in TriggerSpecs. */
getValidFullFlexSourceWithNonDefaultEpsilon()303     public static Source getValidFullFlexSourceWithNonDefaultEpsilon() {
304         try {
305             return getMinimalValidSourceBuilder()
306                     .setAttributedTriggers(new ArrayList<>())
307                     .setTriggerSpecs(getValidTriggerSpecsWithNonDefaultEpsilon())
308                     .setMaxEventLevelReports(
309                             getValidTriggerSpecsCountBasedWithFewerState().getMaxReports())
310                     .build();
311         } catch (JSONException e) {
312             return null;
313         }
314     }
315 
getValidFullSourceBuilderWithFlexEventReportValueSum()316     public static Source.Builder getValidFullSourceBuilderWithFlexEventReportValueSum() {
317         try {
318             return getValidSourceBuilder()
319                     .setAttributedTriggers(new ArrayList<>())
320                     .setTriggerSpecs(getValidTriggerSpecsValueSum());
321         } catch (JSONException e) {
322             return null;
323         }
324     }
325 
getValidSourceBuilderWithFlexEventReportValueSum()326     public static Source.Builder getValidSourceBuilderWithFlexEventReportValueSum()
327             throws JSONException {
328         TriggerSpecs triggerSpecs = getValidTriggerSpecsValueSum();
329         return getMinimalValidSourceBuilder()
330                 .setId(UUID.randomUUID().toString())
331                 .setTriggerSpecsString(triggerSpecs.encodeToJson())
332                 .setMaxEventLevelReports(triggerSpecs.getMaxReports())
333                 .setEventAttributionStatus(null)
334                 .setPrivacyParameters(triggerSpecs.encodePrivacyParametersToJsonString());
335     }
336 
getValidSourceBuilderWithFlexEventReport()337     public static Source.Builder getValidSourceBuilderWithFlexEventReport() throws JSONException {
338         TriggerSpecs triggerSpecs = getValidTriggerSpecsCountBased();
339         return getMinimalValidSourceBuilder()
340                 .setId(UUID.randomUUID().toString())
341                 .setTriggerSpecsString(triggerSpecs.encodeToJson())
342                 .setMaxEventLevelReports(triggerSpecs.getMaxReports())
343                 .setEventAttributionStatus(null)
344                 .setPrivacyParameters(triggerSpecs.encodePrivacyParametersToJsonString());
345     }
346 
getTriggerSpecCountEncodedJsonValidBaseline()347     public static String getTriggerSpecCountEncodedJsonValidBaseline() {
348         return "[{\"trigger_data\": [1, 2, 3],"
349                 + "\"event_report_windows\": { "
350                 + "\"start_time\": 0, "
351                 + String.format(
352                         "\"end_times\": [%s, %s, %s]}, ",
353                         TimeUnit.DAYS.toMillis(2),
354                         TimeUnit.DAYS.toMillis(7),
355                         TimeUnit.DAYS.toMillis(30))
356                 + "\"summary_window_operator\": \"count\", "
357                 + "\"summary_buckets\": [1, 2, 3, 4]}]";
358     }
359 
getTriggerSpecArrayCountValidBaseline()360     public static TriggerSpec[] getTriggerSpecArrayCountValidBaseline() {
361         return triggerSpecArrayFrom(getTriggerSpecCountEncodedJsonValidBaseline());
362     }
363 
getTriggerSpecValueSumEncodedJsonValidBaseline()364     public static String getTriggerSpecValueSumEncodedJsonValidBaseline() {
365         return getTriggerSpecValueSumEncodedJsonValidBaseline(0L);
366     }
367 
368     /** Provides baseline trigger specs JSON given a start time. */
getTriggerSpecValueSumEncodedJsonValidBaseline(long startTime)369     public static String getTriggerSpecValueSumEncodedJsonValidBaseline(long startTime) {
370         return "[{\"trigger_data\": [1, 2],"
371                 + "\"event_report_windows\": { "
372                 + "\"start_time\": " + String.valueOf(startTime) + ", "
373                 + String.format(
374                         "\"end_times\": [%s, %s]}, ",
375                         TimeUnit.DAYS.toMillis(2), TimeUnit.DAYS.toMillis(7))
376                 + "\"summary_window_operator\": \"value_sum\", "
377                 + "\"summary_buckets\": [10, 100]}]";
378     }
379 
getTriggerSpecValueSumArrayValidBaseline()380     public static TriggerSpec[] getTriggerSpecValueSumArrayValidBaseline() {
381         return triggerSpecArrayFrom(getTriggerSpecValueSumEncodedJsonValidBaseline(0L));
382     }
383 
384     /** Provides value-sum trigger specs given a start time. */
getTriggerSpecValueSumArrayValidBaseline(long startTime)385     public static TriggerSpec[] getTriggerSpecValueSumArrayValidBaseline(long startTime) {
386         return triggerSpecArrayFrom(getTriggerSpecValueSumEncodedJsonValidBaseline(startTime));
387     }
388 
getTriggerSpecValueCountJsonTwoTriggerSpecs()389     public static TriggerSpec[] getTriggerSpecValueCountJsonTwoTriggerSpecs() {
390         return triggerSpecArrayFrom(
391                 "[{\"trigger_data\": [1, 2, 3],"
392                         + "\"event_report_windows\": { "
393                         + "\"start_time\": 0, "
394                         + String.format(
395                                 "\"end_times\": [%s, %s, %s]}, ",
396                                 TimeUnit.DAYS.toMillis(2),
397                                 TimeUnit.DAYS.toMillis(7),
398                                 TimeUnit.DAYS.toMillis(30))
399                         + "\"summary_window_operator\": \"count\", "
400                         + "\"summary_buckets\": [1, 2, 3, 4]}, "
401                         + "{\"trigger_data\": [4, 5, 6, 7],"
402                         + "\"event_report_windows\": { "
403                         + "\"start_time\": 0, "
404                         + String.format("\"end_times\": [%s]}, ", TimeUnit.DAYS.toMillis(3))
405                         + "\"summary_window_operator\": \"count\", "
406                         + "\"summary_buckets\": [1,5,7]} "
407                         + "]");
408     }
409 
triggerSpecArrayFrom(String json)410     private static TriggerSpec[] triggerSpecArrayFrom(String json) {
411         try {
412             JSONArray jsonArray = new JSONArray(json);
413             TriggerSpec[] triggerSpecArray = new TriggerSpec[jsonArray.length()];
414             for (int i = 0; i < jsonArray.length(); i++) {
415                 triggerSpecArray[i] = new TriggerSpec.Builder(jsonArray.getJSONObject(i)).build();
416             }
417             return triggerSpecArray;
418         } catch (JSONException ignored) {
419             return null;
420         }
421     }
422 }
423