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