• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.adservices.service.measurement.registration;
17 
18 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATABLE_TRIGGER_DATA;
19 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_DEDUPLICATION_KEYS_PER_REGISTRATION;
20 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_AGGREGATE_KEYS_PER_REGISTRATION;
21 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_ATTRIBUTION_EVENT_TRIGGER_DATA;
22 
23 import android.annotation.NonNull;
24 import android.content.Context;
25 import android.net.Uri;
26 
27 import com.android.adservices.LogUtil;
28 import com.android.adservices.data.enrollment.EnrollmentDao;
29 import com.android.adservices.service.Flags;
30 import com.android.adservices.service.FlagsFactory;
31 import com.android.adservices.service.common.AllowLists;
32 import com.android.adservices.service.measurement.AttributionConfig;
33 import com.android.adservices.service.measurement.EventSurfaceType;
34 import com.android.adservices.service.measurement.MeasurementHttpClient;
35 import com.android.adservices.service.measurement.Trigger;
36 import com.android.adservices.service.measurement.XNetworkData;
37 import com.android.adservices.service.measurement.util.BaseUriExtractor;
38 import com.android.adservices.service.measurement.util.Enrollment;
39 import com.android.adservices.service.measurement.util.Filter;
40 import com.android.adservices.service.measurement.util.UnsignedLong;
41 import com.android.adservices.service.measurement.util.Web;
42 import com.android.adservices.service.stats.AdServicesLogger;
43 import com.android.adservices.service.stats.AdServicesLoggerImpl;
44 import com.android.internal.annotations.VisibleForTesting;
45 
46 import org.json.JSONArray;
47 import org.json.JSONException;
48 import org.json.JSONObject;
49 
50 import java.io.IOException;
51 import java.net.HttpURLConnection;
52 import java.net.MalformedURLException;
53 import java.net.URL;
54 import java.net.URLConnection;
55 import java.util.HashSet;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Optional;
60 import java.util.Set;
61 
62 /**
63  * Download and decode Trigger registration.
64  *
65  * @hide
66  */
67 public class AsyncTriggerFetcher {
68 
69     private final MeasurementHttpClient mNetworkConnection = new MeasurementHttpClient();
70     private final EnrollmentDao mEnrollmentDao;
71     private final Flags mFlags;
72     private final AdServicesLogger mLogger;
73     private final Context mContext;
74 
AsyncTriggerFetcher(Context context)75     public AsyncTriggerFetcher(Context context) {
76         this(
77                 context,
78                 EnrollmentDao.getInstance(context),
79                 FlagsFactory.getFlags(),
80                 AdServicesLoggerImpl.getInstance());
81     }
82 
83     @VisibleForTesting
AsyncTriggerFetcher( Context context, EnrollmentDao enrollmentDao, Flags flags, AdServicesLogger logger)84     public AsyncTriggerFetcher(
85             Context context, EnrollmentDao enrollmentDao, Flags flags, AdServicesLogger logger) {
86         mContext = context;
87         mEnrollmentDao = enrollmentDao;
88         mFlags = flags;
89         mLogger = logger;
90     }
91 
92     /**
93      * Parse a {@code Trigger}, given response headers, adding the {@code Trigger} to a given list.
94      */
95     @VisibleForTesting
parseTrigger( AsyncRegistration asyncRegistration, String enrollmentId, Map<String, List<String>> headers, AsyncFetchStatus asyncFetchStatus)96     public Optional<Trigger> parseTrigger(
97             AsyncRegistration asyncRegistration,
98             String enrollmentId,
99             Map<String, List<String>> headers,
100             AsyncFetchStatus asyncFetchStatus) {
101         boolean arDebugPermission = asyncRegistration.getDebugKeyAllowed();
102         LogUtil.d("Trigger ArDebug permission enabled %b", arDebugPermission);
103         Trigger.Builder builder = new Trigger.Builder();
104         builder.setEnrollmentId(enrollmentId);
105         builder.setAttributionDestination(
106                 getAttributionDestination(
107                         asyncRegistration.getTopOrigin(), asyncRegistration.getType()));
108         builder.setRegistrant(asyncRegistration.getRegistrant());
109         builder.setAdIdPermission(asyncRegistration.hasAdIdPermission());
110         builder.setArDebugPermission(arDebugPermission);
111         builder.setDestinationType(
112                 asyncRegistration.isWebRequest() ? EventSurfaceType.WEB : EventSurfaceType.APP);
113         builder.setTriggerTime(asyncRegistration.getRequestTime());
114         Optional<Uri> registrationUriOrigin =
115                 Web.originAndScheme(asyncRegistration.getRegistrationUri());
116         if (!registrationUriOrigin.isPresent()) {
117             LogUtil.d(
118                     "AsyncTriggerFetcher: "
119                             + "Invalid or empty registration uri - "
120                             + asyncRegistration.getRegistrationUri());
121             return Optional.empty();
122         }
123         builder.setRegistrationOrigin(registrationUriOrigin.get());
124 
125         builder.setPlatformAdId(
126                 FetcherUtil.getEncryptedPlatformAdIdIfPresent(asyncRegistration, enrollmentId));
127 
128         List<String> field =
129                 headers.get(TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER);
130         if (field == null || field.size() != 1) {
131             LogUtil.d(
132                     "AsyncTriggerFetcher: "
133                             + "Invalid "
134                             + TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER
135                             + " header.");
136             asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.HEADER_ERROR);
137             return Optional.empty();
138         }
139         try {
140             String eventTriggerData = new JSONArray().toString();
141             JSONObject json = new JSONObject(field.get(0));
142             if (!json.isNull(TriggerHeaderContract.EVENT_TRIGGER_DATA)) {
143                 Optional<String> validEventTriggerData =
144                         getValidEventTriggerData(
145                                 json.getJSONArray(TriggerHeaderContract.EVENT_TRIGGER_DATA));
146                 if (!validEventTriggerData.isPresent()) {
147                     asyncFetchStatus.setEntityStatus(
148                             AsyncFetchStatus.EntityStatus.VALIDATION_ERROR);
149                     return Optional.empty();
150                 }
151                 eventTriggerData = validEventTriggerData.get();
152             }
153             builder.setEventTriggers(eventTriggerData);
154             if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA)) {
155                 Optional<String> validAggregateTriggerData =
156                         getValidAggregateTriggerData(
157                                 json.getJSONArray(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA));
158                 if (!validAggregateTriggerData.isPresent()) {
159                     asyncFetchStatus.setEntityStatus(
160                             AsyncFetchStatus.EntityStatus.VALIDATION_ERROR);
161                     return Optional.empty();
162                 }
163                 builder.setAggregateTriggerData(validAggregateTriggerData.get());
164             }
165             if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_VALUES)) {
166                 if (!isValidAggregateValues(
167                         json.getJSONObject(TriggerHeaderContract.AGGREGATABLE_VALUES))) {
168                     asyncFetchStatus.setEntityStatus(
169                             AsyncFetchStatus.EntityStatus.VALIDATION_ERROR);
170                     return Optional.empty();
171                 }
172                 builder.setAggregateValues(
173                         json.getString(TriggerHeaderContract.AGGREGATABLE_VALUES));
174             }
175             if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS)) {
176                 Optional<String> validAggregateDeduplicationKeysString =
177                         getValidAggregateDuplicationKeysString(
178                                 json.getJSONArray(
179                                         TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS));
180                 if (!validAggregateDeduplicationKeysString.isPresent()) {
181                     LogUtil.d("parseTrigger: aggregate deduplication keys are invalid.");
182                     asyncFetchStatus.setEntityStatus(
183                             AsyncFetchStatus.EntityStatus.VALIDATION_ERROR);
184                     return Optional.empty();
185                 }
186                 builder.setAggregateDeduplicationKeys(validAggregateDeduplicationKeysString.get());
187             }
188             if (!json.isNull(TriggerHeaderContract.FILTERS)) {
189                 JSONArray filters = Filter.maybeWrapFilters(json, TriggerHeaderContract.FILTERS);
190                 if (!FetcherUtil.areValidAttributionFilters(filters)) {
191                     LogUtil.d("parseTrigger: filters are invalid.");
192                     asyncFetchStatus.setEntityStatus(
193                             AsyncFetchStatus.EntityStatus.VALIDATION_ERROR);
194                     return Optional.empty();
195                 }
196                 builder.setFilters(filters.toString());
197             }
198             if (!json.isNull(TriggerHeaderContract.NOT_FILTERS)) {
199                 JSONArray notFilters =
200                         Filter.maybeWrapFilters(json, TriggerHeaderContract.NOT_FILTERS);
201                 if (!FetcherUtil.areValidAttributionFilters(notFilters)) {
202                     LogUtil.d("parseTrigger: not-filters are invalid.");
203                     asyncFetchStatus.setEntityStatus(
204                             AsyncFetchStatus.EntityStatus.VALIDATION_ERROR);
205                     return Optional.empty();
206                 }
207                 builder.setNotFilters(notFilters.toString());
208             }
209             if (!json.isNull(TriggerHeaderContract.DEBUG_REPORTING)) {
210                 builder.setIsDebugReporting(json.optBoolean(TriggerHeaderContract.DEBUG_REPORTING));
211             }
212             if (!json.isNull(TriggerHeaderContract.DEBUG_KEY)) {
213                 try {
214                     builder.setDebugKey(
215                             new UnsignedLong(json.getString(TriggerHeaderContract.DEBUG_KEY)));
216                 } catch (NumberFormatException e) {
217                     LogUtil.e(e, "Parsing trigger debug key failed");
218                 }
219             }
220             if (mFlags.getMeasurementEnableXNA()
221                     && !json.isNull(TriggerHeaderContract.X_NETWORK_KEY_MAPPING)) {
222                 if (!isValidXNetworkKeyMapping(
223                         json.getJSONObject(TriggerHeaderContract.X_NETWORK_KEY_MAPPING))) {
224                     LogUtil.d("parseTrigger: adtech bit mapping is invalid.");
225                 } else {
226                     builder.setAdtechBitMapping(
227                             json.getString(TriggerHeaderContract.X_NETWORK_KEY_MAPPING));
228                 }
229             }
230             if (mFlags.getMeasurementEnableXNA()
231                     && isXnaAllowedForTriggerRegistrant(
232                             asyncRegistration.getRegistrant(), asyncRegistration.getType())
233                     && !json.isNull(TriggerHeaderContract.ATTRIBUTION_CONFIG)) {
234                 String attributionConfigsString =
235                         extractValidAttributionConfigs(
236                                 json.getJSONArray(TriggerHeaderContract.ATTRIBUTION_CONFIG));
237                 builder.setAttributionConfig(attributionConfigsString);
238             }
239 
240             String enrollmentBlockList =
241                     mFlags.getMeasurementPlatformDebugAdIdMatchingEnrollmentBlocklist();
242             Set<String> blockedEnrollmentsString =
243                     new HashSet<>(AllowLists.splitAllowList(enrollmentBlockList));
244             if (!AllowLists.doesAllowListAllowAll(enrollmentBlockList)
245                     && !blockedEnrollmentsString.contains(enrollmentId)
246                     && !json.isNull(TriggerHeaderContract.DEBUG_AD_ID)) {
247                 builder.setDebugAdId(json.optString(TriggerHeaderContract.DEBUG_AD_ID));
248             }
249 
250             Set<String> allowedEnrollmentsString =
251                     new HashSet<>(
252                             AllowLists.splitAllowList(
253                                     mFlags.getMeasurementDebugJoinKeyEnrollmentAllowlist()));
254             if (allowedEnrollmentsString.contains(enrollmentId)
255                     && !json.isNull(TriggerHeaderContract.DEBUG_JOIN_KEY)) {
256                 builder.setDebugJoinKey(json.optString(TriggerHeaderContract.DEBUG_JOIN_KEY));
257             }
258             asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.SUCCESS);
259             return Optional.of(builder.build());
260         } catch (JSONException e) {
261             LogUtil.e(e, "Trigger Parsing failed");
262             asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.PARSING_ERROR);
263             return Optional.empty();
264         }
265     }
266 
267     /** Provided a testing hook. */
268     @NonNull
openUrl(@onNull URL url)269     public URLConnection openUrl(@NonNull URL url) throws IOException {
270         return mNetworkConnection.setup(url);
271     }
272 
isXnaAllowedForTriggerRegistrant( Uri registrant, AsyncRegistration.RegistrationType registrationType)273     private boolean isXnaAllowedForTriggerRegistrant(
274             Uri registrant, AsyncRegistration.RegistrationType registrationType) {
275         // If the trigger is registered from web context, only allow-listed apps should be able to
276         // parse attribution config.
277         return !AsyncRegistration.RegistrationType.WEB_TRIGGER.equals(registrationType)
278                 || AllowLists.isPackageAllowListed(
279                         mFlags.getWebContextClientAppAllowList(), registrant.getAuthority());
280     }
281 
282     /**
283      * Fetch a trigger type registration.
284      *
285      * @param asyncRegistration a {@link AsyncRegistration}, a request the record.
286      * @param asyncFetchStatus a {@link AsyncFetchStatus}, stores Ad Tech server status.
287      */
fetchTrigger( AsyncRegistration asyncRegistration, AsyncFetchStatus asyncFetchStatus, AsyncRedirect asyncRedirect)288     public Optional<Trigger> fetchTrigger(
289             AsyncRegistration asyncRegistration,
290             AsyncFetchStatus asyncFetchStatus,
291             AsyncRedirect asyncRedirect) {
292         HttpURLConnection urlConnection = null;
293         Map<String, List<String>> headers;
294         // TODO(b/276825561): Fix code duplication between fetchSource & fetchTrigger request flow
295         try {
296             urlConnection =
297                     (HttpURLConnection)
298                             openUrl(new URL(asyncRegistration.getRegistrationUri().toString()));
299             urlConnection.setRequestMethod("POST");
300             urlConnection.setInstanceFollowRedirects(false);
301             headers = urlConnection.getHeaderFields();
302             asyncFetchStatus.setResponseSize(FetcherUtil.calculateHeadersCharactersLength(headers));
303             int responseCode = urlConnection.getResponseCode();
304             LogUtil.d("Response code = " + responseCode);
305             if (!FetcherUtil.isRedirect(responseCode) && !FetcherUtil.isSuccess(responseCode)) {
306                 asyncFetchStatus.setResponseStatus(
307                         AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
308                 return Optional.empty();
309             }
310             asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
311         } catch (MalformedURLException e) {
312             LogUtil.d(e, "Malformed registration target URL");
313             asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.INVALID_URL);
314             return Optional.empty();
315         } catch (IOException e) {
316             LogUtil.d(e, "Failed to get registration response");
317             asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
318             return Optional.empty();
319         } finally {
320             if (urlConnection != null) {
321                 urlConnection.disconnect();
322             }
323         }
324 
325         if (asyncRegistration.shouldProcessRedirects()) {
326             FetcherUtil.parseRedirects(headers).forEach(asyncRedirect::addToRedirects);
327         }
328 
329         if (!isTriggerHeaderPresent(headers)) {
330             asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.HEADER_MISSING);
331             return Optional.empty();
332         }
333 
334         Optional<String> enrollmentId =
335                 mFlags.isDisableMeasurementEnrollmentCheck()
336                         ? Optional.of(Enrollment.FAKE_ENROLLMENT)
337                         : Enrollment.getValidEnrollmentId(
338                                 asyncRegistration.getRegistrationUri(),
339                                 asyncRegistration.getRegistrant().getAuthority(),
340                                 mEnrollmentDao,
341                                 mContext,
342                                 mFlags);
343         if (enrollmentId.isEmpty()) {
344             LogUtil.d(
345                     "fetchTrigger: Valid enrollment id not found. Registration URI: %s",
346                     asyncRegistration.getRegistrationUri());
347             asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.INVALID_ENROLLMENT);
348             return Optional.empty();
349         }
350 
351         return parseTrigger(asyncRegistration, enrollmentId.get(), headers, asyncFetchStatus);
352     }
353 
isTriggerHeaderPresent(Map<String, List<String>> headers)354     private boolean isTriggerHeaderPresent(Map<String, List<String>> headers) {
355         return headers.containsKey(
356                 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER);
357     }
358 
getValidEventTriggerData(JSONArray eventTriggerDataArr)359     private static Optional<String> getValidEventTriggerData(JSONArray eventTriggerDataArr) {
360         if (eventTriggerDataArr.length() > MAX_ATTRIBUTION_EVENT_TRIGGER_DATA) {
361             LogUtil.d(
362                     "Event trigger data list has more entries than permitted. %s",
363                     eventTriggerDataArr.length());
364             return Optional.empty();
365         }
366         JSONArray validEventTriggerData = new JSONArray();
367         for (int i = 0; i < eventTriggerDataArr.length(); i++) {
368             JSONObject validEventTriggerDatum = new JSONObject();
369             try {
370                 JSONObject eventTriggerDatum = eventTriggerDataArr.getJSONObject(i);
371                 // Treat invalid trigger data, priority and deduplication key as if they were not
372                 // set.
373                 UnsignedLong triggerData = new UnsignedLong(0L);
374                 if (!eventTriggerDatum.isNull("trigger_data")) {
375                     try {
376                         triggerData = new UnsignedLong(eventTriggerDatum.getString("trigger_data"));
377                     } catch (NumberFormatException e) {
378                         LogUtil.d(e, "getValidEventTriggerData: parsing trigger_data failed.");
379                     }
380                 }
381                 validEventTriggerDatum.put("trigger_data", triggerData);
382                 if (!eventTriggerDatum.isNull("priority")) {
383                     try {
384                         validEventTriggerDatum.put(
385                                 "priority",
386                                 String.valueOf(
387                                         Long.parseLong(eventTriggerDatum.getString("priority"))));
388                     } catch (NumberFormatException e) {
389                         LogUtil.d(e, "getValidEventTriggerData: parsing priority failed.");
390                     }
391                 }
392                 if (!eventTriggerDatum.isNull("deduplication_key")) {
393                     try {
394                         validEventTriggerDatum.put(
395                                 "deduplication_key",
396                                 new UnsignedLong(eventTriggerDatum.getString("deduplication_key")));
397                     } catch (NumberFormatException e) {
398                         LogUtil.d(e, "getValidEventTriggerData: parsing deduplication_key failed.");
399                     }
400                 }
401                 if (!eventTriggerDatum.isNull("filters")) {
402                     JSONArray filters = Filter.maybeWrapFilters(eventTriggerDatum, "filters");
403                     if (!FetcherUtil.areValidAttributionFilters(filters)) {
404                         LogUtil.d("getValidEventTriggerData: filters are invalid.");
405                         return Optional.empty();
406                     }
407                     validEventTriggerDatum.put("filters", filters);
408                 }
409                 if (!eventTriggerDatum.isNull("not_filters")) {
410                     JSONArray notFilters =
411                             Filter.maybeWrapFilters(eventTriggerDatum, "not_filters");
412                     if (!FetcherUtil.areValidAttributionFilters(notFilters)) {
413                         LogUtil.d("getValidEventTriggerData: not-filters are invalid.");
414                         return Optional.empty();
415                     }
416                     validEventTriggerDatum.put("not_filters", notFilters);
417                 }
418                 validEventTriggerData.put(validEventTriggerDatum);
419             } catch (JSONException e) {
420                 LogUtil.d(e, "AsyncTriggerFetcher: " + "Parsing event trigger datum JSON failed.");
421             }
422         }
423         return Optional.of(validEventTriggerData.toString());
424     }
425 
getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr)426     private static Optional<String> getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr)
427             throws JSONException {
428         if (aggregateTriggerDataArr.length() > MAX_AGGREGATABLE_TRIGGER_DATA) {
429             LogUtil.d(
430                     "Aggregate trigger data list has more entries than permitted. %s",
431                     aggregateTriggerDataArr.length());
432             return Optional.empty();
433         }
434         JSONArray validAggregateTriggerData = new JSONArray();
435         for (int i = 0; i < aggregateTriggerDataArr.length(); i++) {
436             JSONObject aggregateTriggerData = aggregateTriggerDataArr.getJSONObject(i);
437             String keyPiece = aggregateTriggerData.optString("key_piece");
438             if (!FetcherUtil.isValidAggregateKeyPiece(keyPiece)) {
439                 LogUtil.d("Aggregate trigger data key-piece is invalid. %s", keyPiece);
440                 return Optional.empty();
441             }
442             JSONArray sourceKeys = aggregateTriggerData.optJSONArray("source_keys");
443             if (sourceKeys == null || sourceKeys.length() > MAX_AGGREGATE_KEYS_PER_REGISTRATION) {
444                 LogUtil.d(
445                         "Aggregate trigger data source-keys list failed to parse or has more"
446                                 + " entries than permitted.");
447                 return Optional.empty();
448             }
449             for (int j = 0; j < sourceKeys.length(); j++) {
450                 String key = sourceKeys.optString(j);
451                 if (!FetcherUtil.isValidAggregateKeyId(key)) {
452                     LogUtil.d("Aggregate trigger data source-key is invalid. %s", key);
453                     return Optional.empty();
454                 }
455             }
456             if (!aggregateTriggerData.isNull("filters")) {
457                 JSONArray filters = Filter.maybeWrapFilters(aggregateTriggerData, "filters");
458                 if (!FetcherUtil.areValidAttributionFilters(filters)) {
459                     LogUtil.d("Aggregate trigger data filters are invalid.");
460                     return Optional.empty();
461                 }
462                 aggregateTriggerData.put("filters", filters);
463             }
464             if (!aggregateTriggerData.isNull("not_filters")) {
465                 JSONArray notFilters = Filter.maybeWrapFilters(aggregateTriggerData, "not_filters");
466                 if (!FetcherUtil.areValidAttributionFilters(notFilters)) {
467                     LogUtil.d("Aggregate trigger data not-filters are invalid.");
468                     return Optional.empty();
469                 }
470                 aggregateTriggerData.put("not_filters", notFilters);
471             }
472             if (!aggregateTriggerData.isNull("x_network_data")) {
473                 JSONObject xNetworkDataJson = aggregateTriggerData.getJSONObject("x_network_data");
474                 // This is in order to validate the JSON parsing does not throw exception
475                 new XNetworkData.Builder(xNetworkDataJson);
476             }
477             validAggregateTriggerData.put(aggregateTriggerData);
478         }
479         return Optional.of(validAggregateTriggerData.toString());
480     }
481 
isValidAggregateValues(JSONObject aggregateValues)482     private boolean isValidAggregateValues(JSONObject aggregateValues) {
483         if (aggregateValues.length() > MAX_AGGREGATE_KEYS_PER_REGISTRATION) {
484             LogUtil.d(
485                     "Aggregate values have more keys than permitted. %s", aggregateValues.length());
486             return false;
487         }
488         Iterator<String> ids = aggregateValues.keys();
489         while (ids.hasNext()) {
490             String id = ids.next();
491             if (!FetcherUtil.isValidAggregateKeyId(id)) {
492                 LogUtil.d("Aggregate values key ID is invalid. %s", id);
493                 return false;
494             }
495         }
496         return true;
497     }
498 
getValidAggregateDuplicationKeysString( JSONArray aggregateDeduplicationKeys)499     private Optional<String> getValidAggregateDuplicationKeysString(
500             JSONArray aggregateDeduplicationKeys) throws JSONException {
501         JSONArray validAggregateDeduplicationKeys = new JSONArray();
502         if (aggregateDeduplicationKeys.length()
503                 > MAX_AGGREGATE_DEDUPLICATION_KEYS_PER_REGISTRATION) {
504             LogUtil.d(
505                     "Aggregate deduplication keys have more keys than permitted. %s",
506                     aggregateDeduplicationKeys.length());
507             return Optional.empty();
508         }
509         for (int i = 0; i < aggregateDeduplicationKeys.length(); i++) {
510             JSONObject aggregateDedupKey = new JSONObject();
511             JSONObject deduplication_key = aggregateDeduplicationKeys.getJSONObject(i);
512 
513             String deduplicationKey = deduplication_key.optString("deduplication_key");
514             if (!deduplication_key.isNull("deduplication_key")
515                     && FetcherUtil.isValidAggregateDeduplicationKey(deduplicationKey)) {
516                 aggregateDedupKey.put("deduplication_key", deduplicationKey);
517             }
518             if (!deduplication_key.isNull("filters")) {
519                 JSONArray filters = Filter.maybeWrapFilters(deduplication_key, "filters");
520                 if (!FetcherUtil.areValidAttributionFilters(filters)) {
521                     LogUtil.d("Aggregate deduplication key: " + i + " contains invalid filters.");
522                     return Optional.empty();
523                 }
524                 aggregateDedupKey.put("filters", filters);
525             }
526             if (!deduplication_key.isNull("not_filters")) {
527                 JSONArray notFilters = Filter.maybeWrapFilters(deduplication_key, "not_filters");
528                 if (!FetcherUtil.areValidAttributionFilters(notFilters)) {
529                     LogUtil.d(
530                             "Aggregate deduplication key: " + i + " contains invalid not filters.");
531                     return Optional.empty();
532                 }
533                 aggregateDedupKey.put("not_filters", notFilters);
534             }
535             validAggregateDeduplicationKeys.put(aggregateDedupKey);
536         }
537         return Optional.of(validAggregateDeduplicationKeys.toString());
538     }
539 
extractValidAttributionConfigs(JSONArray attributionConfigsArray)540     private String extractValidAttributionConfigs(JSONArray attributionConfigsArray)
541             throws JSONException {
542         JSONArray validAttributionConfigsArray = new JSONArray();
543         for (int i = 0; i < attributionConfigsArray.length(); i++) {
544             AttributionConfig attributionConfig =
545                     new AttributionConfig.Builder(attributionConfigsArray.getJSONObject(i)).build();
546             validAttributionConfigsArray.put(attributionConfig.serializeAsJson());
547         }
548         return validAttributionConfigsArray.toString();
549     }
550 
isValidXNetworkKeyMapping(JSONObject adTechBitMapping)551     private boolean isValidXNetworkKeyMapping(JSONObject adTechBitMapping) throws JSONException {
552         // TODO: Might need to add logic for keys' and values' lengths.
553         Iterator<String> keys = adTechBitMapping.keys();
554         while (keys.hasNext()) {
555             String key = keys.next();
556             String value = adTechBitMapping.optString(key);
557             if (value == null || !value.startsWith("0x")) {
558                 return false;
559             }
560         }
561         return true;
562     }
563 
getAttributionDestination(Uri destination, AsyncRegistration.RegistrationType registrationType)564     private static Uri getAttributionDestination(Uri destination,
565             AsyncRegistration.RegistrationType registrationType) {
566         return registrationType == AsyncRegistration.RegistrationType.APP_TRIGGER
567                 ? BaseUriExtractor.getBaseUri(destination)
568                 : destination;
569     }
570 
571     private interface TriggerHeaderContract {
572         String HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER =
573                 "Attribution-Reporting-Register-Trigger";
574         String ATTRIBUTION_CONFIG = "attribution_config";
575         String EVENT_TRIGGER_DATA = "event_trigger_data";
576         String FILTERS = "filters";
577         String NOT_FILTERS = "not_filters";
578         String AGGREGATABLE_TRIGGER_DATA = "aggregatable_trigger_data";
579         String AGGREGATABLE_VALUES = "aggregatable_values";
580         String AGGREGATABLE_DEDUPLICATION_KEYS = "aggregatable_deduplication_keys";
581         String DEBUG_KEY = "debug_key";
582         String DEBUG_REPORTING = "debug_reporting";
583         String X_NETWORK_KEY_MAPPING = "x_network_key_mapping";
584         String DEBUG_JOIN_KEY = "debug_join_key";
585         String DEBUG_AD_ID = "debug_ad_id";
586     }
587 }
588