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 int STATUS = Source.Status.ACTIVE; 157 public static final String AGGREGATE_DEBUG_REPORT = 158 "{\"budget\":1024," 159 + "\"key_piece\":\"0x100\"," 160 + "\"debug_data\":[" 161 + "{" 162 + "\"types\": [\"source-destination-limit\"]," 163 + "\"key_piece\": \"0x111\"," 164 + "\"value\": 111" 165 + "}," 166 + "{" 167 + "\"types\": [\"unspecified\"]," 168 + "\"key_piece\": \"0x222\"," 169 + "\"value\": 222" 170 + "}" 171 + "]}"; 172 public static final int AGGREGATE_DEBUG_REPORT_CONTRIBUTIONS = 100; 173 public static final int MAX_EVENT_LEVEL_REPORTS = 5; 174 public static final Source.TriggerDataMatching TRIGGER_DATA_MATCHING = 175 Source.TriggerDataMatching.MODULUS; 176 buildAggregateSource()177 public static final String buildAggregateSource() { 178 try { 179 JSONObject jsonObject = new JSONObject(); 180 jsonObject.put("campaignCounts", "0x456"); 181 jsonObject.put("geoValue", "0x159"); 182 return jsonObject.toString(); 183 } catch (JSONException e) { 184 LogUtil.e("JSONException when building aggregate source."); 185 } 186 return null; 187 } 188 189 /** Creates a filter data string */ buildFilterDataString()190 public static final String buildFilterDataString() { 191 try { 192 JSONObject filterMap = new JSONObject(); 193 filterMap.put( 194 "conversion_subdomain", 195 new JSONArray(Collections.singletonList("electronics.megastore"))); 196 filterMap.put("product", new JSONArray(Arrays.asList("1234", "2345"))); 197 return filterMap.toString(); 198 } catch (JSONException e) { 199 LogUtil.e("JSONException when building aggregate filter data."); 200 } 201 return null; 202 } 203 buildAggregatableAttributionSource()204 public static final AggregatableAttributionSource buildAggregatableAttributionSource() { 205 TreeMap<String, BigInteger> aggregateSourceMap = new TreeMap<>(); 206 aggregateSourceMap.put("5", new BigInteger("345")); 207 return new AggregatableAttributionSource.Builder() 208 .setAggregatableSource(aggregateSourceMap) 209 .setFilterMap( 210 new FilterMap.Builder() 211 .setAttributionFilterMap( 212 Map.of( 213 "product", 214 List.of("1234", "4321"), 215 "conversion_subdomain", 216 List.of("electronics.megastore"))) 217 .build()) 218 .build(); 219 } 220 } 221 222 /** Provides a count-based valid TriggerSpecs. */ getValidTriggerSpecsCountBased()223 public static TriggerSpecs getValidTriggerSpecsCountBased() throws JSONException { 224 String triggerSpecsString = 225 "[{\"trigger_data\": [1, 2]," 226 + "\"event_report_windows\": { " 227 + "\"start_time\": 0, " 228 + String.format( 229 "\"end_times\": [%s, %s]}, ", 230 TimeUnit.DAYS.toMillis(2), TimeUnit.DAYS.toMillis(7)) 231 + "\"summary_operator\": \"count\", " 232 + "\"summary_buckets\": [1, 2]}]"; 233 Source source = 234 getMinimalValidSourceBuilder() 235 .setAttributedTriggers(new ArrayList<>()) 236 .build(); 237 TriggerSpecs triggerSpecs = new TriggerSpecs( 238 triggerSpecArrayFrom(triggerSpecsString), 3, source); 239 // Oblige building privacy parameters for the trigger specs 240 triggerSpecs.getInformationGain(source, FakeFlagsFactory.getFlagsForTest()); 241 return triggerSpecs; 242 } 243 244 /** Provides a count-based valid TriggerSpecs with smaller state space. */ getValidTriggerSpecsCountBasedWithFewerState()245 public static TriggerSpecs getValidTriggerSpecsCountBasedWithFewerState() throws JSONException { 246 String triggerSpecsString = 247 "[{\"trigger_data\": [1]," 248 + "\"event_report_windows\": { " 249 + "\"start_time\": 0, " 250 + String.format("\"end_times\": [%s]}, ", TimeUnit.DAYS.toMillis(2)) 251 + "\"summary_operator\": \"count\", " 252 + "\"summary_buckets\": [1]}]"; 253 Source source = getMinimalValidSourceBuilder().build(); 254 TriggerSpecs triggerSpecs = new TriggerSpecs( 255 triggerSpecArrayFrom(triggerSpecsString), 1, source); 256 // Oblige building privacy parameters for the trigger specs 257 triggerSpecs.getInformationGain(source, FakeFlagsFactory.getFlagsForTest()); 258 return triggerSpecs; 259 } 260 261 /** Provides a valid TriggerSpecs with hardcoded epsilon. */ getValidTriggerSpecsWithNonDefaultEpsilon()262 public static TriggerSpecs getValidTriggerSpecsWithNonDefaultEpsilon() throws JSONException { 263 String triggerSpecsString = 264 "[{\"trigger_data\": [1]," 265 + "\"event_report_windows\": { " 266 + "\"start_time\": 0, " 267 + String.format("\"end_times\": [%s]}, ", TimeUnit.DAYS.toMillis(2)) 268 + "\"summary_operator\": \"count\", " 269 + "\"summary_buckets\": [1]}]"; 270 Source source = getMinimalValidSourceBuilder().build(); 271 double mockFlipProbability = Combinatorics.getFlipProbability(5, 3); 272 String privacyParametersString = "{\"flip_probability\": " + mockFlipProbability + "}"; 273 TriggerSpecs triggerSpecs = 274 new TriggerSpecs(triggerSpecsString, "1", source, privacyParametersString); 275 return triggerSpecs; 276 } 277 278 /** Provides a value-sum-based valid TriggerSpecs. */ getValidTriggerSpecsValueSum()279 public static TriggerSpecs getValidTriggerSpecsValueSum() throws JSONException { 280 return getValidTriggerSpecsValueSum(3); 281 } 282 283 /** Provides a value-sum-based valid TriggerSpecs. */ getValidTriggerSpecsValueSum(int maxReports)284 public static TriggerSpecs getValidTriggerSpecsValueSum(int maxReports) throws JSONException { 285 Source source = 286 getMinimalValidSourceBuilder() 287 .setAttributedTriggers(new ArrayList<>()) 288 .build(); 289 TriggerSpecs triggerSpecs = new TriggerSpecs( 290 getTriggerSpecValueSumArrayValidBaseline(), 291 maxReports, 292 source); 293 // Oblige building privacy parameters for the trigger specs 294 triggerSpecs.getInformationGain(source, FakeFlagsFactory.getFlagsForTest()); 295 return triggerSpecs; 296 } 297 298 /** Provides a value-sum-based valid TriggerSpecs. */ getValidTriggerSpecsValueSumWithStartTime(long startTime)299 public static TriggerSpecs getValidTriggerSpecsValueSumWithStartTime(long startTime) 300 throws JSONException { 301 Source source = 302 getMinimalValidSourceBuilder() 303 .setAttributedTriggers(new ArrayList<>()) 304 .build(); 305 TriggerSpecs triggerSpecs = new TriggerSpecs( 306 getTriggerSpecValueSumArrayValidBaseline(startTime), 307 /* maxReports */ 3, 308 source); 309 // Oblige building privacy parameters for the trigger specs 310 triggerSpecs.getInformationGain(source, FakeFlagsFactory.getFlagsForTest()); 311 return triggerSpecs; 312 } 313 getValidSourceWithFlexEventReport()314 public static Source getValidSourceWithFlexEventReport() { 315 try { 316 return getValidSourceBuilder() 317 .setAttributedTriggers(new ArrayList<>()) 318 .setTriggerSpecs(getValidTriggerSpecsCountBased()) 319 .setMaxEventLevelReports(getValidTriggerSpecsCountBased().getMaxReports()) 320 .build(); 321 } catch (JSONException e) { 322 return null; 323 } 324 } 325 getValidSourceWithFlexEventReportWithFewerState()326 public static Source getValidSourceWithFlexEventReportWithFewerState() { 327 try { 328 return getMinimalValidSourceBuilder() 329 .setAttributedTriggers(new ArrayList<>()) 330 .setTriggerSpecs(getValidTriggerSpecsCountBasedWithFewerState()) 331 .setMaxEventLevelReports( 332 getValidTriggerSpecsCountBasedWithFewerState().getMaxReports()) 333 .build(); 334 } catch (JSONException e) { 335 return null; 336 } 337 } 338 339 /** Provide a Source with hardcoded epsilon in TriggerSpecs. */ getValidFullFlexSourceWithNonDefaultEpsilon()340 public static Source getValidFullFlexSourceWithNonDefaultEpsilon() { 341 try { 342 return getMinimalValidSourceBuilder() 343 .setAttributedTriggers(new ArrayList<>()) 344 .setTriggerSpecs(getValidTriggerSpecsWithNonDefaultEpsilon()) 345 .setMaxEventLevelReports( 346 getValidTriggerSpecsCountBasedWithFewerState().getMaxReports()) 347 .build(); 348 } catch (JSONException e) { 349 LoggerFactory.getMeasurementLogger() 350 .e(e, "Unable to build Source with non default epsilon."); 351 return null; 352 } 353 } 354 getValidFullSourceBuilderWithFlexEventReportValueSum()355 public static Source.Builder getValidFullSourceBuilderWithFlexEventReportValueSum() { 356 try { 357 return getValidSourceBuilder() 358 .setAttributedTriggers(new ArrayList<>()) 359 .setTriggerSpecs(getValidTriggerSpecsValueSum()); 360 } catch (JSONException e) { 361 return null; 362 } 363 } 364 getValidSourceBuilderWithFlexEventReportValueSum()365 public static Source.Builder getValidSourceBuilderWithFlexEventReportValueSum() 366 throws JSONException { 367 TriggerSpecs triggerSpecs = getValidTriggerSpecsValueSum(); 368 return getMinimalValidSourceBuilder() 369 .setId(UUID.randomUUID().toString()) 370 .setTriggerSpecsString(triggerSpecs.encodeToJson()) 371 .setMaxEventLevelReports(triggerSpecs.getMaxReports()) 372 .setEventAttributionStatus(null) 373 .setPrivacyParameters(triggerSpecs.encodePrivacyParametersToJsonString()); 374 } 375 getValidSourceBuilderWithFlexEventReport()376 public static Source.Builder getValidSourceBuilderWithFlexEventReport() throws JSONException { 377 TriggerSpecs triggerSpecs = getValidTriggerSpecsCountBased(); 378 return getMinimalValidSourceBuilder() 379 .setId(UUID.randomUUID().toString()) 380 .setTriggerSpecsString(triggerSpecs.encodeToJson()) 381 .setMaxEventLevelReports(triggerSpecs.getMaxReports()) 382 .setEventAttributionStatus(null) 383 .setPrivacyParameters(triggerSpecs.encodePrivacyParametersToJsonString()); 384 } 385 getTriggerSpecCountEncodedJsonValidBaseline()386 public static String getTriggerSpecCountEncodedJsonValidBaseline() { 387 return "[{\"trigger_data\": [1, 2, 3]," 388 + "\"event_report_windows\": { " 389 + "\"start_time\": 0, " 390 + String.format( 391 "\"end_times\": [%s, %s, %s]}, ", 392 TimeUnit.DAYS.toMillis(2), 393 TimeUnit.DAYS.toMillis(7), 394 TimeUnit.DAYS.toMillis(30)) 395 + "\"summary_operator\": \"count\", " 396 + "\"summary_buckets\": [1, 2, 3, 4]}]"; 397 } 398 getTriggerSpecArrayCountValidBaseline()399 public static TriggerSpec[] getTriggerSpecArrayCountValidBaseline() { 400 return triggerSpecArrayFrom(getTriggerSpecCountEncodedJsonValidBaseline()); 401 } 402 getTriggerSpecValueSumEncodedJsonValidBaseline()403 public static String getTriggerSpecValueSumEncodedJsonValidBaseline() { 404 return getTriggerSpecValueSumEncodedJsonValidBaseline(0L); 405 } 406 407 /** Provides baseline trigger specs JSON given a start time. */ getTriggerSpecValueSumEncodedJsonValidBaseline(long startTime)408 public static String getTriggerSpecValueSumEncodedJsonValidBaseline(long startTime) { 409 return "[{\"trigger_data\": [1, 2]," 410 + "\"event_report_windows\": { " 411 + "\"start_time\": " + String.valueOf(startTime) + ", " 412 + String.format( 413 "\"end_times\": [%s, %s]}, ", 414 TimeUnit.DAYS.toMillis(2), TimeUnit.DAYS.toMillis(7)) 415 + "\"summary_operator\": \"value_sum\", " 416 + "\"summary_buckets\": [10, 100]}]"; 417 } 418 getTriggerSpecValueSumArrayValidBaseline()419 public static TriggerSpec[] getTriggerSpecValueSumArrayValidBaseline() { 420 return triggerSpecArrayFrom(getTriggerSpecValueSumEncodedJsonValidBaseline(0L)); 421 } 422 423 /** Provides value-sum trigger specs given a start time. */ getTriggerSpecValueSumArrayValidBaseline(long startTime)424 public static TriggerSpec[] getTriggerSpecValueSumArrayValidBaseline(long startTime) { 425 return triggerSpecArrayFrom(getTriggerSpecValueSumEncodedJsonValidBaseline(startTime)); 426 } 427 getTriggerSpecValueCountJsonTwoTriggerSpecs()428 public static TriggerSpec[] getTriggerSpecValueCountJsonTwoTriggerSpecs() { 429 return triggerSpecArrayFrom( 430 "[{\"trigger_data\": [1, 2, 3]," 431 + "\"event_report_windows\": { " 432 + "\"start_time\": 0, " 433 + String.format( 434 "\"end_times\": [%s, %s, %s]}, ", 435 TimeUnit.DAYS.toMillis(2), 436 TimeUnit.DAYS.toMillis(7), 437 TimeUnit.DAYS.toMillis(30)) 438 + "\"summary_operator\": \"count\", " 439 + "\"summary_buckets\": [1, 2, 3, 4]}, " 440 + "{\"trigger_data\": [4, 5, 6, 7]," 441 + "\"event_report_windows\": { " 442 + "\"start_time\": 0, " 443 + String.format("\"end_times\": [%s]}, ", TimeUnit.DAYS.toMillis(3)) 444 + "\"summary_operator\": \"count\", " 445 + "\"summary_buckets\": [1,5,7]} " 446 + "]"); 447 } 448 triggerSpecArrayFrom(String json)449 private static TriggerSpec[] triggerSpecArrayFrom(String json) { 450 try { 451 JSONArray jsonArray = new JSONArray(json); 452 TriggerSpec[] triggerSpecArray = new TriggerSpec[jsonArray.length()]; 453 for (int i = 0; i < jsonArray.length(); i++) { 454 triggerSpecArray[i] = new TriggerSpec.Builder(jsonArray.getJSONObject(i)).build(); 455 } 456 return triggerSpecArray; 457 } catch (JSONException ignored) { 458 return null; 459 } 460 } 461 } 462