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