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