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.aggregation; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.net.Uri; 22 23 import androidx.annotation.Nullable; 24 25 import com.android.adservices.LoggerFactory; 26 import com.android.adservices.service.AdServicesConfig; 27 import com.android.adservices.service.measurement.EventSurfaceType; 28 import com.android.adservices.service.measurement.Trigger; 29 import com.android.adservices.service.measurement.util.UnsignedLong; 30 31 32 import org.json.JSONArray; 33 import org.json.JSONException; 34 import org.json.JSONObject; 35 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Objects; 42 import java.util.UUID; 43 44 /** 45 * Class that contains all the real data needed after aggregation, it is not encrypted. 46 */ 47 public class AggregateReport { 48 static final String OPERATION = "operation"; 49 static final String HISTOGRAM = "histogram"; 50 static final String DATA = "data"; 51 52 private String mId; 53 private Uri mPublisher; 54 private Uri mAttributionDestination; 55 @Nullable private Long mSourceRegistrationTime; 56 private long mScheduledReportTime; // triggerTime + random([10min, 1hour]) 57 private String mEnrollmentId; 58 private String mDebugCleartextPayload; 59 private AggregateAttributionData mAggregateAttributionData; 60 private @Status int mStatus; 61 private @DebugReportStatus int mDebugReportStatus; 62 private String mApiVersion; 63 @Nullable private UnsignedLong mSourceDebugKey; 64 @Nullable private UnsignedLong mTriggerDebugKey; 65 private String mSourceId; 66 private String mTriggerId; 67 private UnsignedLong mDedupKey; 68 private Uri mRegistrationOrigin; 69 private Uri mAggregationCoordinatorOrigin; 70 private boolean mIsFakeReport; 71 @Nullable private String mTriggerContextId; 72 73 @IntDef(value = {Status.PENDING, Status.DELIVERED, Status.MARKED_TO_DELETE}) 74 @Retention(RetentionPolicy.SOURCE) 75 public @interface Status { 76 int PENDING = 0; 77 int DELIVERED = 1; 78 int MARKED_TO_DELETE = 2; 79 } 80 81 @IntDef( 82 value = { 83 DebugReportStatus.NONE, 84 DebugReportStatus.PENDING, 85 DebugReportStatus.DELIVERED, 86 }) 87 @Retention(RetentionPolicy.SOURCE) 88 public @interface DebugReportStatus { 89 int NONE = 0; 90 int PENDING = 1; 91 int DELIVERED = 2; 92 } 93 AggregateReport()94 private AggregateReport() { 95 mId = null; 96 mPublisher = null; 97 mAttributionDestination = null; 98 mSourceRegistrationTime = null; 99 mScheduledReportTime = 0L; 100 mEnrollmentId = null; 101 mDebugCleartextPayload = null; 102 mAggregateAttributionData = null; 103 mStatus = Status.PENDING; 104 mDebugReportStatus = DebugReportStatus.NONE; 105 mSourceDebugKey = null; 106 mTriggerDebugKey = null; 107 mDedupKey = null; 108 mRegistrationOrigin = null; 109 mAggregationCoordinatorOrigin = null; 110 mTriggerContextId = null; 111 } 112 113 @Override equals(Object obj)114 public boolean equals(Object obj) { 115 if (!(obj instanceof AggregateReport)) { 116 return false; 117 } 118 AggregateReport aggregateReport = (AggregateReport) obj; 119 return Objects.equals(mPublisher, aggregateReport.mPublisher) 120 && Objects.equals(mAttributionDestination, aggregateReport.mAttributionDestination) 121 && Objects.equals(mSourceRegistrationTime, aggregateReport.mSourceRegistrationTime) 122 && mScheduledReportTime == aggregateReport.mScheduledReportTime 123 && Objects.equals(mEnrollmentId, aggregateReport.mEnrollmentId) 124 && Objects.equals(mDebugCleartextPayload, aggregateReport.mDebugCleartextPayload) 125 && Objects.equals( 126 mAggregateAttributionData, aggregateReport.mAggregateAttributionData) 127 && mStatus == aggregateReport.mStatus 128 && mDebugReportStatus == aggregateReport.mDebugReportStatus 129 && Objects.equals(mApiVersion, aggregateReport.mApiVersion) 130 && Objects.equals(mSourceDebugKey, aggregateReport.mSourceDebugKey) 131 && Objects.equals(mTriggerDebugKey, aggregateReport.mTriggerDebugKey) 132 && Objects.equals(mSourceId, aggregateReport.mSourceId) 133 && Objects.equals(mTriggerId, aggregateReport.mTriggerId) 134 && Objects.equals(mDedupKey, aggregateReport.mDedupKey) 135 && Objects.equals(mRegistrationOrigin, aggregateReport.mRegistrationOrigin) 136 && Objects.equals( 137 mAggregationCoordinatorOrigin, 138 aggregateReport.mAggregationCoordinatorOrigin) 139 && mIsFakeReport == aggregateReport.mIsFakeReport 140 && Objects.equals(mTriggerContextId, aggregateReport.mTriggerContextId); 141 } 142 143 @Override hashCode()144 public int hashCode() { 145 return Objects.hash( 146 mId, 147 mPublisher, 148 mAttributionDestination, 149 mSourceRegistrationTime, 150 mScheduledReportTime, 151 mEnrollmentId, 152 mDebugCleartextPayload, 153 mAggregateAttributionData, 154 mStatus, 155 mDebugReportStatus, 156 mSourceDebugKey, 157 mTriggerDebugKey, 158 mSourceId, 159 mTriggerId, 160 mDedupKey, 161 mRegistrationOrigin, 162 mAggregationCoordinatorOrigin, 163 mIsFakeReport, 164 mTriggerContextId); 165 } 166 167 /** 168 * Unique identifier for the {@link AggregateReport}. 169 */ getId()170 public String getId() { 171 return mId; 172 } 173 174 /** 175 * Uri for publisher of this source, primarily an App. 176 */ getPublisher()177 public Uri getPublisher() { 178 return mPublisher; 179 } 180 181 /** 182 * Uri for attribution destination of source. 183 */ getAttributionDestination()184 public Uri getAttributionDestination() { 185 return mAttributionDestination; 186 } 187 188 /** Source registration time. */ 189 @Nullable getSourceRegistrationTime()190 public Long getSourceRegistrationTime() { 191 return mSourceRegistrationTime; 192 } 193 194 /** 195 * Scheduled report time for aggregate report . 196 */ getScheduledReportTime()197 public long getScheduledReportTime() { 198 return mScheduledReportTime; 199 } 200 201 /** 202 * Ad-tech enrollment ID. 203 */ getEnrollmentId()204 public String getEnrollmentId() { 205 return mEnrollmentId; 206 } 207 208 /** 209 * Unencrypted aggregate payload string, convert from List of AggregateHistogramContribution. 210 */ getDebugCleartextPayload()211 public String getDebugCleartextPayload() { 212 return mDebugCleartextPayload; 213 } 214 215 /** Source Debug Key */ 216 @Nullable getSourceDebugKey()217 public UnsignedLong getSourceDebugKey() { 218 return mSourceDebugKey; 219 } 220 221 /** Trigger Debug Key */ 222 @Nullable getTriggerDebugKey()223 public UnsignedLong getTriggerDebugKey() { 224 return mTriggerDebugKey; 225 } 226 227 /** 228 * Contains the data specific to the aggregate report. 229 * 230 * @deprecated use {@link #getDebugCleartextPayload()} instead 231 */ 232 @Deprecated getAggregateAttributionData()233 public AggregateAttributionData getAggregateAttributionData() { 234 return mAggregateAttributionData; 235 } 236 237 /** 238 * Current {@link Status} of the report. 239 */ getStatus()240 public @Status int getStatus() { 241 return mStatus; 242 } 243 244 /** Current {@link DebugReportStatus} of the report. */ getDebugReportStatus()245 public @DebugReportStatus int getDebugReportStatus() { 246 return mDebugReportStatus; 247 } 248 249 /** 250 * Api version when the report was issued. 251 */ getApiVersion()252 public String getApiVersion() { 253 return mApiVersion; 254 } 255 256 /** Deduplication key assigned to theis aggregate report. */ 257 @Nullable getDedupKey()258 public UnsignedLong getDedupKey() { 259 return mDedupKey; 260 } 261 262 /** Returns registration origin used to register the source */ getRegistrationOrigin()263 public Uri getRegistrationOrigin() { 264 return mRegistrationOrigin; 265 } 266 267 /** Returns coordinator origin for aggregatable reports */ getAggregationCoordinatorOrigin()268 public Uri getAggregationCoordinatorOrigin() { 269 return mAggregationCoordinatorOrigin; 270 } 271 272 /** Is the report a null report. */ isFakeReport()273 public boolean isFakeReport() { 274 return mIsFakeReport; 275 } 276 277 /** Returns the trigger's context id. */ 278 @Nullable getTriggerContextId()279 public String getTriggerContextId() { 280 return mTriggerContextId; 281 } 282 283 /** 284 * Generates String for debugCleartextPayload. JSON for format : { "operation": "histogram", 285 * "data": [{ "bucket": 1369, "value": 32768 }, { "bucket": 3461, "value": 1664 }] } 286 */ 287 @NonNull generateDebugPayload(List<AggregateHistogramContribution> contributions)288 public static String generateDebugPayload(List<AggregateHistogramContribution> contributions) 289 throws JSONException { 290 JSONArray jsonArray = new JSONArray(); 291 for (AggregateHistogramContribution contribution : contributions) { 292 jsonArray.put(contribution.toJSONObject()); 293 } 294 JSONObject debugPayload = new JSONObject(); 295 debugPayload.put(OPERATION, HISTOGRAM); 296 debugPayload.put(DATA, jsonArray); 297 return debugPayload.toString(); 298 } 299 300 /** 301 * It deserializes the debug cleartext payload into {@link AggregateHistogramContribution}s. 302 * 303 * @return list of {@link AggregateHistogramContribution}s 304 */ 305 @NonNull extractAggregateHistogramContributions()306 public List<AggregateHistogramContribution> extractAggregateHistogramContributions() { 307 try { 308 ArrayList<AggregateHistogramContribution> aggregateHistogramContributions = 309 new ArrayList<>(); 310 JSONObject debugCleartextPayload = new JSONObject(mDebugCleartextPayload); 311 JSONArray contributionsArray = debugCleartextPayload.getJSONArray(DATA); 312 for (int i = 0; i < contributionsArray.length(); i++) { 313 AggregateHistogramContribution aggregateHistogramContribution = 314 new AggregateHistogramContribution.Builder() 315 .fromJsonObject(contributionsArray.getJSONObject(i)); 316 aggregateHistogramContributions.add(aggregateHistogramContribution); 317 } 318 return aggregateHistogramContributions; 319 } catch (JSONException e) { 320 LoggerFactory.getMeasurementLogger() 321 .e(e, "Failed to parse contributions on Aggregate report."); 322 return Collections.emptyList(); 323 } 324 } 325 326 /** Source ID */ getSourceId()327 public String getSourceId() { 328 return mSourceId; 329 } 330 331 /** Trigger ID */ getTriggerId()332 public String getTriggerId() { 333 return mTriggerId; 334 } 335 336 /** 337 * Builder for {@link AggregateReport}. 338 */ 339 public static final class Builder { 340 private final AggregateReport mAttributionReport; 341 Builder()342 public Builder() { 343 mAttributionReport = new AggregateReport(); 344 } 345 346 /** 347 * See {@link AggregateReport#getId()}. 348 */ setId(String id)349 public Builder setId(String id) { 350 mAttributionReport.mId = id; 351 return this; 352 } 353 354 /** 355 * See {@link AggregateReport#getPublisher()}. 356 */ setPublisher(Uri publisher)357 public Builder setPublisher(Uri publisher) { 358 mAttributionReport.mPublisher = publisher; 359 return this; 360 } 361 362 /** 363 * See {@link AggregateReport#getAttributionDestination()}. 364 */ setAttributionDestination(Uri attributionDestination)365 public Builder setAttributionDestination(Uri attributionDestination) { 366 mAttributionReport.mAttributionDestination = attributionDestination; 367 return this; 368 } 369 370 /** See {@link AggregateReport#getSourceRegistrationTime()}. */ setSourceRegistrationTime(@ullable Long sourceRegistrationTime)371 public Builder setSourceRegistrationTime(@Nullable Long sourceRegistrationTime) { 372 mAttributionReport.mSourceRegistrationTime = sourceRegistrationTime; 373 return this; 374 } 375 376 /** 377 * See {@link AggregateReport#getScheduledReportTime()}. 378 */ setScheduledReportTime(long scheduledReportTime)379 public Builder setScheduledReportTime(long scheduledReportTime) { 380 mAttributionReport.mScheduledReportTime = scheduledReportTime; 381 return this; 382 } 383 384 /** 385 * See {@link AggregateReport#getEnrollmentId()}. 386 */ setEnrollmentId(String enrollmentId)387 public Builder setEnrollmentId(String enrollmentId) { 388 mAttributionReport.mEnrollmentId = enrollmentId; 389 return this; 390 } 391 392 /** 393 * See {@link AggregateReport#getDebugCleartextPayload()}. 394 */ setDebugCleartextPayload(String debugCleartextPayload)395 public Builder setDebugCleartextPayload(String debugCleartextPayload) { 396 mAttributionReport.mDebugCleartextPayload = debugCleartextPayload; 397 return this; 398 } 399 400 /** 401 * See {@link AggregateReport#getAggregateAttributionData()}. 402 * 403 * @deprecated use {@link #getDebugCleartextPayload()} instead 404 */ 405 @Deprecated setAggregateAttributionData( AggregateAttributionData aggregateAttributionData)406 public Builder setAggregateAttributionData( 407 AggregateAttributionData aggregateAttributionData) { 408 mAttributionReport.mAggregateAttributionData = aggregateAttributionData; 409 return this; 410 } 411 412 /** 413 * See {@link AggregateReport#getStatus()} 414 */ setStatus(@tatus int status)415 public Builder setStatus(@Status int status) { 416 mAttributionReport.mStatus = status; 417 return this; 418 } 419 420 /** See {@link AggregateReport#getDebugReportStatus()} */ setDebugReportStatus(@ebugReportStatus int debugReportStatus)421 public Builder setDebugReportStatus(@DebugReportStatus int debugReportStatus) { 422 mAttributionReport.mDebugReportStatus = debugReportStatus; 423 return this; 424 } 425 426 /** 427 * See {@link AggregateReport#getApiVersion()} 428 */ setApiVersion(String version)429 public Builder setApiVersion(String version) { 430 mAttributionReport.mApiVersion = version; 431 return this; 432 } 433 434 /** See {@link AggregateReport#getSourceDebugKey()} */ setSourceDebugKey(UnsignedLong sourceDebugKey)435 public Builder setSourceDebugKey(UnsignedLong sourceDebugKey) { 436 mAttributionReport.mSourceDebugKey = sourceDebugKey; 437 return this; 438 } 439 440 /** See {@link AggregateReport#getTriggerDebugKey()} */ setTriggerDebugKey(UnsignedLong triggerDebugKey)441 public Builder setTriggerDebugKey(UnsignedLong triggerDebugKey) { 442 mAttributionReport.mTriggerDebugKey = triggerDebugKey; 443 return this; 444 } 445 446 /** See {@link AggregateReport#getSourceId()} */ setSourceId(String sourceId)447 public Builder setSourceId(String sourceId) { 448 mAttributionReport.mSourceId = sourceId; 449 return this; 450 } 451 452 /** See {@link AggregateReport#getTriggerId()} */ setTriggerId(String triggerId)453 public Builder setTriggerId(String triggerId) { 454 mAttributionReport.mTriggerId = triggerId; 455 return this; 456 } 457 458 /** See {@link AggregateReport#getDedupKey()} */ 459 @NonNull setDedupKey(@ullable UnsignedLong dedupKey)460 public Builder setDedupKey(@Nullable UnsignedLong dedupKey) { 461 mAttributionReport.mDedupKey = dedupKey; 462 return this; 463 } 464 465 /** See {@link AggregateReport#getRegistrationOrigin()} */ 466 @NonNull setRegistrationOrigin(Uri registrationOrigin)467 public Builder setRegistrationOrigin(Uri registrationOrigin) { 468 mAttributionReport.mRegistrationOrigin = registrationOrigin; 469 return this; 470 } 471 472 /** See {@link AggregateReport#getAggregationCoordinatorOrigin()} */ setAggregationCoordinatorOrigin(Uri aggregationCoordinatorOrigin)473 public Builder setAggregationCoordinatorOrigin(Uri aggregationCoordinatorOrigin) { 474 mAttributionReport.mAggregationCoordinatorOrigin = aggregationCoordinatorOrigin; 475 return this; 476 } 477 478 /** See {@link AggregateReport#isFakeReport()} */ setIsFakeReport(boolean isFakeReport)479 public Builder setIsFakeReport(boolean isFakeReport) { 480 mAttributionReport.mIsFakeReport = isFakeReport; 481 return this; 482 } 483 484 /** See {@link AggregateReport#getTriggerContextId()} */ setTriggerContextId(@ullable String triggerContextId)485 public Builder setTriggerContextId(@Nullable String triggerContextId) { 486 mAttributionReport.mTriggerContextId = triggerContextId; 487 return this; 488 } 489 490 /** 491 * Given a {@link Trigger} trigger, source registration time, reporting delay, and the api 492 * version, initialize an {@link AggregateReport.Builder} builder that builds a null 493 * aggregate report. A null aggregate report is used to obscure the number of real aggregate 494 * reports. 495 * 496 * @param trigger the trigger 497 * @param fakeSourceTime a fake source registration time 498 * @param delay amount of delay in ms to wait before sending out report. Only applicable if 499 * the trigger's trigger context id is null 500 * @param apiVersion api version string that is sent to aggregate service 501 * @return builder initialized to build a null aggregate report 502 * @throws JSONException thrown if fake contributions create invalid JSON. Fake 503 * contributions are hardcoded, so this should not be thrown. 504 */ getNullAggregateReportBuilder( Trigger trigger, @Nullable Long fakeSourceTime, long delay, String apiVersion)505 public Builder getNullAggregateReportBuilder( 506 Trigger trigger, @Nullable Long fakeSourceTime, long delay, String apiVersion) 507 throws JSONException { 508 mAttributionReport.mId = UUID.randomUUID().toString(); 509 long reportTime = trigger.getTriggerTime(); 510 if (trigger.getTriggerContextId() == null) { 511 reportTime += delay; 512 } 513 AggregateHistogramContribution paddingContribution = 514 new AggregateHistogramContribution.Builder().setPaddingContribution().build(); 515 516 String debugPayload = 517 AggregateReport.generateDebugPayload(List.of(paddingContribution)); 518 519 mAttributionReport.mApiVersion = apiVersion; 520 mAttributionReport.mPublisher = Uri.EMPTY; 521 mAttributionReport.mRegistrationOrigin = trigger.getRegistrationOrigin(); 522 mAttributionReport.mAttributionDestination = trigger.getAttributionDestinationBaseUri(); 523 mAttributionReport.mSourceRegistrationTime = fakeSourceTime; 524 mAttributionReport.mScheduledReportTime = reportTime; 525 mAttributionReport.mDebugCleartextPayload = debugPayload; 526 mAttributionReport.mSourceDebugKey = null; 527 mAttributionReport.mIsFakeReport = true; 528 mAttributionReport.mTriggerId = trigger.getId(); 529 mAttributionReport.mTriggerContextId = trigger.getTriggerContextId(); 530 531 if (trigger.getAggregationCoordinatorOrigin() != null) { 532 mAttributionReport.mAggregationCoordinatorOrigin = 533 trigger.getAggregationCoordinatorOrigin(); 534 } else { 535 mAttributionReport.mAggregationCoordinatorOrigin = 536 Uri.parse( 537 AdServicesConfig 538 .getMeasurementDefaultAggregationCoordinatorOrigin()); 539 } 540 541 if ((trigger.getDestinationType() == EventSurfaceType.APP 542 && trigger.hasAdIdPermission()) 543 || (trigger.getDestinationType() == EventSurfaceType.WEB 544 && trigger.hasArDebugPermission())) { 545 mAttributionReport.mTriggerDebugKey = trigger.getDebugKey(); 546 } 547 548 return this; 549 } 550 551 /** Build the {@link AggregateReport}. */ build()552 public AggregateReport build() { 553 return mAttributionReport; 554 } 555 } 556 } 557