• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 com.android.adservices.service.measurement.noising.Combinatorics;
20 
21 import org.json.JSONArray;
22 import org.json.JSONException;
23 import org.json.JSONObject;
24 
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Objects;
30 import java.util.Set;
31 
32 /**
33  * A class wrapper for the trigger specification from the input argument during source registration
34  */
35 public class ReportSpec {
36     private final TriggerSpec[] mTriggerSpecs;
37     private final int mMaxBucketIncrements;
38     private PrivacyComputationParams mPrivacyParams = null;
39 
ReportSpec(JSONArray json, int maxBucketIncrements, boolean shouldValidateAndCompute)40     public ReportSpec(JSONArray json, int maxBucketIncrements, boolean shouldValidateAndCompute)
41             throws JSONException {
42         mMaxBucketIncrements = maxBucketIncrements;
43         mTriggerSpecs = new TriggerSpec[json.length()];
44         for (int i = 0; i < json.length(); i++) {
45             mTriggerSpecs[i] = new TriggerSpec.Builder(json.getJSONObject(i)).build();
46         }
47         if (shouldValidateAndCompute) {
48             mPrivacyParams = new PrivacyComputationParams();
49         }
50     }
51 
52     /** @return the probability to use fake report */
getFlipProbability()53     public double getFlipProbability() {
54         return getPrivacyParams().getFlipProbability();
55     }
56 
57     /** @return the number of states */
getNumberState()58     public int getNumberState() {
59         return getPrivacyParams().getNumStates();
60     }
61 
62     /**
63      * Get the parameters for the privacy computation. 1st element: total report cap, an array with
64      * 1 element is used to store the integer; 2nd element: number of windows per trigger data type;
65      * 3rd element: number of report cap per trigger data type.
66      *
67      * @return the parameters to computer number of states and fake report
68      */
getPrivacyParamsForComputation()69     public int[][] getPrivacyParamsForComputation() {
70         int[][] params = new int[3][];
71         params[0] = new int[] {mMaxBucketIncrements};
72         params[1] = mPrivacyParams.getPerTypeNumWindowList();
73         params[2] = mPrivacyParams.getPerTypeCapList();
74         return params;
75     }
76 
getPrivacyParams()77     private PrivacyComputationParams getPrivacyParams() {
78         return mPrivacyParams;
79     }
80 
81     /**
82      * getter method for mTriggerSpecs
83      *
84      * @return the array of TriggerSpec
85      */
getTriggerSpecs()86     public TriggerSpec[] getTriggerSpecs() {
87         return mTriggerSpecs;
88     }
89 
90     /** @return Max bucket increments. (a.k.a max number of reports) */
getMaxReports()91     public int getMaxReports() {
92         return mMaxBucketIncrements;
93     }
94 
95     /**
96      * Get the trigger data type given a trigger data index. In the flexible event API, the trigger
97      * data is not necessary input as [0, 1, 2..]
98      *
99      * @param triggerDataIndex The index of the triggerData
100      * @return the value of the trigger data
101      */
getTriggerDataValue(int triggerDataIndex)102     public int getTriggerDataValue(int triggerDataIndex) {
103         for (TriggerSpec triggerSpec : mTriggerSpecs) {
104             int prevTriggerDataIndex = triggerDataIndex;
105             triggerDataIndex -= triggerSpec.getTriggerData().size();
106             if (triggerDataIndex < 0) {
107                 return triggerSpec.getTriggerData().get(prevTriggerDataIndex);
108             }
109         }
110         // will not reach here
111         return -1;
112     }
113 
114     /**
115      * Get the reporting window end time given a trigger data and window index
116      *
117      * @param triggerDataIndex The index of the triggerData
118      * @param windowIndex the window index, not the actual window end time
119      * @return the report window end time
120      */
getWindowEndTime(int triggerDataIndex, int windowIndex)121     public long getWindowEndTime(int triggerDataIndex, int windowIndex) {
122         for (TriggerSpec triggerSpec : mTriggerSpecs) {
123             triggerDataIndex -= triggerSpec.getTriggerData().size();
124             if (triggerDataIndex < 0) {
125                 return triggerSpec.getEventReportWindowsEnd().get(windowIndex);
126             }
127         }
128         // will not reach here
129         return -1;
130     }
131 
computerPerTypeNumWindowList()132     private int[] computerPerTypeNumWindowList() {
133         List<Integer> list = new ArrayList<>();
134         for (TriggerSpec triggerSpec : mTriggerSpecs) {
135             for (int ignored : triggerSpec.getTriggerData()) {
136                 list.add(triggerSpec.getEventReportWindowsEnd().size());
137             }
138         }
139         return list.stream().mapToInt(Integer::intValue).toArray();
140     }
141 
computerPerTypeCapList()142     private int[] computerPerTypeCapList() {
143         List<Integer> list = new ArrayList<>();
144         for (TriggerSpec triggerSpec : mTriggerSpecs) {
145             for (int ignored : triggerSpec.getTriggerData()) {
146                 list.add(triggerSpec.getSummaryBucket().size());
147             }
148         }
149         return list.stream().mapToInt(Integer::intValue).toArray();
150     }
151 
152     @Override
equals(Object obj)153     public boolean equals(Object obj) {
154         if (!(obj instanceof ReportSpec)) {
155             return false;
156         }
157         ReportSpec t = (ReportSpec) obj;
158 
159         if (mTriggerSpecs.length != t.mTriggerSpecs.length) {
160             return false;
161         }
162         for (int i = 0; i < mTriggerSpecs.length; i++) {
163             if (!mTriggerSpecs[i].equals(t.mTriggerSpecs[i])) {
164                 return false;
165             }
166         }
167         return mMaxBucketIncrements == t.mMaxBucketIncrements;
168     }
169 
170     @Override
hashCode()171     public int hashCode() {
172         return Objects.hash(Arrays.hashCode(mTriggerSpecs), mMaxBucketIncrements);
173     }
174 
175     /**
176      * Encode the all parameter to JSON
177      *
178      * @return json object encode this class
179      */
encodeTriggerSpecsToJSON()180     public JSONArray encodeTriggerSpecsToJSON() throws JSONException {
181         JSONObject[] triggerSpecsArray = new JSONObject[mTriggerSpecs.length];
182         for (int i = 0; i < mTriggerSpecs.length; i++) {
183             triggerSpecsArray[i] = mTriggerSpecs[i].encodeJSON();
184         }
185         return new JSONArray(triggerSpecsArray);
186     }
187 
188     private class PrivacyComputationParams {
189         private final int[] mPerTypeNumWindowList;
190         private final int[] mPerTypeCapList;
191         private final int mNumStates;
192         private final double mFlipProbability;
193         private final double mInformationGain;
194 
PrivacyComputationParams()195         PrivacyComputationParams() throws JSONException {
196             mPerTypeNumWindowList = computerPerTypeNumWindowList();
197             mPerTypeCapList = computerPerTypeCapList();
198 
199             // Check the upper bound of the parameters
200             if (Math.min(mMaxBucketIncrements, Arrays.stream(mPerTypeCapList).sum())
201                     > PrivacyParams.getMaxFlexibleEventReports()) {
202                 throw new IllegalArgumentException(
203                         "Max Event Reports Exceeds " + PrivacyParams.getMaxFlexibleEventReports());
204             }
205             if (mPerTypeNumWindowList.length
206                     > PrivacyParams.getMaxFlexibleEventTriggerDataCardinality()) {
207                 throw new IllegalArgumentException(
208                         "Trigger Data Cardinality Exceeds "
209                                 + PrivacyParams.getMaxFlexibleEventTriggerDataCardinality());
210             }
211 
212             // check duplication of the trigger data
213             Set<Integer> seen = new HashSet<>();
214             for (TriggerSpec triggerSpec : mTriggerSpecs) {
215                 for (int num : triggerSpec.getTriggerData()) {
216                     if (!seen.add(num)) {
217                         throw new IllegalArgumentException("Duplication in Trigger Data");
218                     }
219                 }
220             }
221 
222             // compute number of state and other privacy parameters
223             mNumStates =
224                     Combinatorics.getNumStatesFlexAPI(
225                             mMaxBucketIncrements, mPerTypeNumWindowList, mPerTypeCapList);
226             mFlipProbability = Combinatorics.getFlipProbability(mNumStates);
227             mInformationGain = Combinatorics.getInformationGain(mNumStates, mFlipProbability);
228             if (mInformationGain > PrivacyParams.getMaxFlexibleEventInformationGain()) {
229                 throw new IllegalArgumentException(
230                         "Information Gain Exceeds "
231                                 + PrivacyParams.getMaxFlexibleEventInformationGain());
232             }
233         }
234 
getFlipProbability()235         private double getFlipProbability() {
236             return mFlipProbability;
237         }
238 
getNumStates()239         private int getNumStates() {
240             return mNumStates;
241         }
242 
getPerTypeNumWindowList()243         private int[] getPerTypeNumWindowList() {
244             return mPerTypeNumWindowList;
245         }
246         ;
247 
getPerTypeCapList()248         private int[] getPerTypeCapList() {
249             return mPerTypeCapList;
250         }
251         ;
252     }
253 }
254