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.customaudience; 18 19 import static com.android.adservices.service.customaudience.CustomAudienceUpdatableDataReader.FIELD_FOUND_LOG_FORMAT; 20 import static com.android.adservices.service.customaudience.CustomAudienceUpdatableDataReader.FIELD_NOT_FOUND_LOG_FORMAT; 21 import static com.android.adservices.service.customaudience.CustomAudienceUpdatableDataReader.STRING_ERROR_FORMAT; 22 23 import android.adservices.common.AdSelectionSignals; 24 import android.adservices.common.AdTechIdentifier; 25 import android.net.Uri; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 30 import com.android.adservices.LoggerFactory; 31 import com.android.adservices.data.common.DBAdData; 32 import com.android.adservices.data.customaudience.DBTrustedBiddingData; 33 import com.android.adservices.service.common.JsonUtils; 34 35 import org.json.JSONException; 36 import org.json.JSONObject; 37 38 import java.time.Instant; 39 import java.util.List; 40 import java.util.Objects; 41 42 // TODO(b/283857101): Delete and use CustomAudienceBlob instead. 43 /** A parser and validator for a JSON representation of a Custom Audience. */ 44 public class FetchCustomAudienceReader { 45 private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); 46 public static final String NAME_KEY = "name"; 47 public static final String ACTIVATION_TIME_KEY = "activation_time"; 48 public static final String EXPIRATION_TIME_KEY = "expiration_time"; 49 public static final String DAILY_UPDATE_URI_KEY = "daily_update_uri"; 50 public static final String BIDDING_LOGIC_URI_KEY = "bidding_logic_uri"; 51 private final JSONObject mResponseObject; 52 private final String mResponseHash; 53 private final AdTechIdentifier mBuyer; 54 private final CustomAudienceUpdatableDataReader mCustomAudienceUpdatableDataReader; 55 56 /** 57 * Creates a {@link FetchCustomAudienceReader} that will read updatable data from a given {@link 58 * JSONObject} and log with the given identifying {@code responseHash}. 59 * 60 * @param responseObject a {@link JSONObject} that may contain user bidding signals, trusted 61 * bidding data, and/or a list of ads 62 * @param responseHash a String that uniquely identifies the response which is used in logging 63 * @param buyer the buyer ad tech's eTLD+1 64 * @param maxUserBiddingSignalsSizeB the configured maximum size in bytes allocated for user 65 * bidding signals 66 * @param maxTrustedBiddingDataSizeB the configured maximum size in bytes allocated for trusted 67 * bidding data 68 * @param maxAdsSizeB the configured maximum size in bytes allocated for ads 69 * @param maxNumAds the configured maximum number of ads allowed per update 70 * @param frequencyCapFilteringEnabled whether or not frequency cap filtering fields should be 71 * read 72 * @param appInstallFilteringEnabled whether or not app install filtering fields should be read 73 */ FetchCustomAudienceReader( @onNull JSONObject responseObject, @NonNull String responseHash, @NonNull AdTechIdentifier buyer, int maxUserBiddingSignalsSizeB, int maxTrustedBiddingDataSizeB, int maxAdsSizeB, int maxNumAds, boolean frequencyCapFilteringEnabled, boolean appInstallFilteringEnabled, boolean adRenderIdEnabled, long adRenderIdMaxLength)74 protected FetchCustomAudienceReader( 75 @NonNull JSONObject responseObject, 76 @NonNull String responseHash, 77 @NonNull AdTechIdentifier buyer, 78 int maxUserBiddingSignalsSizeB, 79 int maxTrustedBiddingDataSizeB, 80 int maxAdsSizeB, 81 int maxNumAds, 82 boolean frequencyCapFilteringEnabled, 83 boolean appInstallFilteringEnabled, 84 boolean adRenderIdEnabled, 85 long adRenderIdMaxLength) { 86 Objects.requireNonNull(responseObject); 87 Objects.requireNonNull(responseHash); 88 Objects.requireNonNull(buyer); 89 90 mResponseObject = responseObject; 91 mResponseHash = responseHash; 92 mBuyer = buyer; 93 mCustomAudienceUpdatableDataReader = 94 new CustomAudienceUpdatableDataReader( 95 responseObject, 96 responseHash, 97 buyer, 98 maxUserBiddingSignalsSizeB, 99 maxTrustedBiddingDataSizeB, 100 maxAdsSizeB, 101 maxNumAds, 102 frequencyCapFilteringEnabled, 103 appInstallFilteringEnabled, 104 adRenderIdEnabled, 105 adRenderIdMaxLength); 106 } 107 108 /** 109 * Returns the user bidding signals extracted from the input object, if found. 110 * 111 * @throws JSONException if the key is found but the schema is incorrect 112 * @throws NullPointerException if the key found by the field is null 113 * @throws IllegalArgumentException if the extracted signals fail data validation 114 */ 115 @Nullable getNameFromJsonObject()116 public String getNameFromJsonObject() 117 throws JSONException, NullPointerException, IllegalArgumentException { 118 if (mResponseObject.has(NAME_KEY)) { 119 sLogger.v(FIELD_FOUND_LOG_FORMAT, mResponseHash, NAME_KEY); 120 121 String name = 122 JsonUtils.getStringFromJson( 123 mResponseObject, 124 NAME_KEY, 125 String.format(STRING_ERROR_FORMAT, NAME_KEY, mResponseHash)); 126 127 // TODO(b/282018172): Validate name field 128 // sLogger.v(VALIDATED_FIELD_LOG_FORMAT, mResponseHash, NAME_KEY); 129 return name; 130 } else { 131 sLogger.v(FIELD_NOT_FOUND_LOG_FORMAT, mResponseHash, NAME_KEY); 132 return null; 133 } 134 } 135 136 /** 137 * Returns the user bidding signals extracted from the input object, if found. 138 * 139 * @throws JSONException if the key is found but the schema is incorrect 140 * @throws NullPointerException if the key found by the field is null 141 * @throws IllegalArgumentException if the extracted signals fail data validation 142 */ 143 @Nullable getActivationTimeFromJsonObject()144 public Instant getActivationTimeFromJsonObject() 145 throws JSONException, NullPointerException, IllegalArgumentException { 146 if (mResponseObject.has(ACTIVATION_TIME_KEY)) { 147 sLogger.v(FIELD_FOUND_LOG_FORMAT, mResponseHash, ACTIVATION_TIME_KEY); 148 149 Instant activationTime = 150 Instant.ofEpochMilli(mResponseObject.getLong(ACTIVATION_TIME_KEY)); 151 152 // TODO(b/282018172): Validate activation_time field 153 // sLogger.v(VALIDATED_FIELD_LOG_FORMAT, mResponseHash, ACTIVATION_TIME_KEY); 154 return activationTime; 155 } else { 156 sLogger.v(FIELD_NOT_FOUND_LOG_FORMAT, mResponseHash, ACTIVATION_TIME_KEY); 157 return null; 158 } 159 } 160 161 /** 162 * Returns the user bidding signals extracted from the input object, if found. 163 * 164 * @throws JSONException if the key is found but the schema is incorrect 165 * @throws NullPointerException if the key found by the field is null 166 * @throws IllegalArgumentException if the extracted signals fail data validation 167 */ 168 @Nullable getExpirationTimeFromJsonObject()169 public Instant getExpirationTimeFromJsonObject() 170 throws JSONException, NullPointerException, IllegalArgumentException { 171 if (mResponseObject.has(EXPIRATION_TIME_KEY)) { 172 sLogger.v(FIELD_FOUND_LOG_FORMAT, mResponseHash, EXPIRATION_TIME_KEY); 173 Instant expirationTime = 174 Instant.ofEpochMilli(mResponseObject.getLong(EXPIRATION_TIME_KEY)); 175 176 // TODO(b/282018172): Validate expiration_time field 177 // sLogger.v(VALIDATED_FIELD_LOG_FORMAT, mResponseHash, EXPIRATION_TIME_KEY); 178 return expirationTime; 179 } else { 180 sLogger.v(FIELD_NOT_FOUND_LOG_FORMAT, mResponseHash, EXPIRATION_TIME_KEY); 181 return null; 182 } 183 } 184 185 /** 186 * Returns the user bidding signals extracted from the input object, if found. 187 * 188 * @throws JSONException if the key is found but the schema is incorrect 189 * @throws NullPointerException if the key found by the field is null 190 * @throws IllegalArgumentException if the extracted signals fail data validation 191 */ 192 @Nullable getDailyUpdateUriFromJsonObject()193 public Uri getDailyUpdateUriFromJsonObject() 194 throws JSONException, NullPointerException, IllegalArgumentException { 195 if (mResponseObject.has(DAILY_UPDATE_URI_KEY)) { 196 sLogger.v(FIELD_FOUND_LOG_FORMAT, mResponseHash, DAILY_UPDATE_URI_KEY); 197 198 String uri = 199 JsonUtils.getStringFromJson( 200 mResponseObject, 201 DAILY_UPDATE_URI_KEY, 202 String.format( 203 STRING_ERROR_FORMAT, DAILY_UPDATE_URI_KEY, mResponseHash)); 204 Uri parsedUri = Uri.parse(uri); 205 206 // TODO(b/282018172): Validate daily_update_uri field 207 // sLogger.v(VALIDATED_FIELD_LOG_FORMAT, mResponseHash, DAILY_UPDATE_URI_KEY); 208 return parsedUri; 209 } else { 210 sLogger.v(FIELD_NOT_FOUND_LOG_FORMAT, mResponseHash, DAILY_UPDATE_URI_KEY); 211 return null; 212 } 213 } 214 215 /** 216 * Returns the user bidding signals extracted from the input object, if found. 217 * 218 * @throws JSONException if the key is found but the schema is incorrect 219 * @throws NullPointerException if the key found by the field is null 220 * @throws IllegalArgumentException if the extracted signals fail data validation 221 */ 222 @Nullable getBiddingLogicUriFromJsonObject()223 public Uri getBiddingLogicUriFromJsonObject() 224 throws JSONException, NullPointerException, IllegalArgumentException { 225 if (mResponseObject.has(BIDDING_LOGIC_URI_KEY)) { 226 sLogger.v(FIELD_FOUND_LOG_FORMAT, mResponseHash, BIDDING_LOGIC_URI_KEY); 227 228 String uri = 229 JsonUtils.getStringFromJson( 230 mResponseObject, 231 BIDDING_LOGIC_URI_KEY, 232 String.format( 233 STRING_ERROR_FORMAT, BIDDING_LOGIC_URI_KEY, mResponseHash)); 234 Uri parsedUri = Uri.parse(uri); 235 236 // TODO(b/282018172): Validate bidding_logic_uri field 237 // sLogger.v(VALIDATED_FIELD_LOG_FORMAT, mResponseHash, BIDDING_LOGIC_URI_KEY); 238 return parsedUri; 239 } else { 240 sLogger.v(FIELD_NOT_FOUND_LOG_FORMAT, mResponseHash, BIDDING_LOGIC_URI_KEY); 241 return null; 242 } 243 } 244 245 /** 246 * Returns the user bidding signals extracted from the input object, if found. 247 * 248 * @throws JSONException if the key is found but the schema is incorrect 249 * @throws NullPointerException if the key found by the field is null 250 * @throws IllegalArgumentException if the extracted signals fail data validation 251 */ 252 @Nullable getUserBiddingSignalsFromJsonObject()253 public AdSelectionSignals getUserBiddingSignalsFromJsonObject() 254 throws JSONException, NullPointerException, IllegalArgumentException { 255 return mCustomAudienceUpdatableDataReader.getUserBiddingSignalsFromJsonObject(); 256 } 257 258 /** 259 * Returns the trusted bidding data extracted from the input object, if found. 260 * 261 * @throws JSONException if the key is found but the schema is incorrect 262 * @throws NullPointerException if the key found by the field is null 263 * @throws IllegalArgumentException if the extracted data fails data validation 264 */ 265 @Nullable getTrustedBiddingDataFromJsonObject()266 public DBTrustedBiddingData getTrustedBiddingDataFromJsonObject() 267 throws JSONException, NullPointerException, IllegalArgumentException { 268 return mCustomAudienceUpdatableDataReader.getTrustedBiddingDataFromJsonObject(); 269 } 270 271 /** 272 * Returns the list of ads extracted from the input object, if found. 273 * 274 * @throws JSONException if the key is found but the schema is incorrect 275 * @throws NullPointerException if the key found by the field is null 276 * @throws IllegalArgumentException if the extracted ads fail data validation 277 */ 278 @Nullable getAdsFromJsonObject()279 public List<DBAdData> getAdsFromJsonObject() 280 throws JSONException, NullPointerException, IllegalArgumentException { 281 return mCustomAudienceUpdatableDataReader.getAdsFromJsonObject(); 282 } 283 } 284