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