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