• 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.registration.AsyncFetchStatus.EntityStatus;
19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ENROLLMENT_INVALID;
20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REGISTRATION_ODP_GET_MANAGER_ERROR;
21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT;
22 
23 import android.adservices.ondevicepersonalization.OnDevicePersonalizationSystemEventManager;
24 import android.annotation.NonNull;
25 import android.content.Context;
26 import android.net.Uri;
27 
28 import com.android.adservices.LoggerFactory;
29 import com.android.adservices.data.enrollment.EnrollmentDao;
30 import com.android.adservices.data.measurement.DatastoreManager;
31 import com.android.adservices.data.measurement.DatastoreManagerFactory;
32 import com.android.adservices.errorlogging.ErrorLogUtil;
33 import com.android.adservices.service.Flags;
34 import com.android.adservices.service.FlagsFactory;
35 import com.android.adservices.service.common.AllowLists;
36 import com.android.adservices.service.common.WebAddresses;
37 import com.android.adservices.service.measurement.AttributionConfig;
38 import com.android.adservices.service.measurement.EventSurfaceType;
39 import com.android.adservices.service.measurement.MeasurementHttpClient;
40 import com.android.adservices.service.measurement.Trigger;
41 import com.android.adservices.service.measurement.TriggerSpecs;
42 import com.android.adservices.service.measurement.XNetworkData;
43 import com.android.adservices.service.measurement.aggregation.AggregatableKeyValue.AggregatableKeyValueContract;
44 import com.android.adservices.service.measurement.aggregation.AggregatableNamedBudget.NamedBudgetContract;
45 import com.android.adservices.service.measurement.aggregation.AggregatableValuesConfig.AggregatableValuesConfigContract;
46 import com.android.adservices.service.measurement.ondevicepersonalization.IOdpDelegationWrapper;
47 import com.android.adservices.service.measurement.ondevicepersonalization.NoOdpDelegationWrapper;
48 import com.android.adservices.service.measurement.ondevicepersonalization.OdpDelegationWrapperImpl;
49 import com.android.adservices.service.measurement.reporting.DebugReportApi;
50 import com.android.adservices.service.measurement.util.BaseUriExtractor;
51 import com.android.adservices.service.measurement.util.Enrollment;
52 import com.android.adservices.service.measurement.util.Filter;
53 import com.android.adservices.service.measurement.util.Filter.FilterContract;
54 import com.android.adservices.service.measurement.util.UnsignedLong;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.modules.utils.build.SdkLevel;
57 
58 import org.json.JSONArray;
59 import org.json.JSONException;
60 import org.json.JSONObject;
61 
62 import java.io.IOException;
63 import java.math.BigDecimal;
64 import java.math.BigInteger;
65 import java.net.HttpURLConnection;
66 import java.net.MalformedURLException;
67 import java.net.URL;
68 import java.net.URLConnection;
69 import java.util.HashSet;
70 import java.util.Iterator;
71 import java.util.List;
72 import java.util.Locale;
73 import java.util.Map;
74 import java.util.Optional;
75 import java.util.Set;
76 import java.util.UUID;
77 
78 /**
79  * Download and decode Trigger registration.
80  *
81  * @hide
82  */
83 public class AsyncTriggerFetcher {
84 
85     private final MeasurementHttpClient mNetworkConnection;
86     private final EnrollmentDao mEnrollmentDao;
87     private final Flags mFlags;
88     private final Context mContext;
89     private final IOdpDelegationWrapper mOdpWrapper;
90     private final DatastoreManager mDatastoreManager;
91     private final DebugReportApi mDebugReportApi;
92     private static final long ONE_BYTE = (long) Math.pow(2, 8);
93 
AsyncTriggerFetcher(Context context)94     public AsyncTriggerFetcher(Context context) {
95         this(
96                 context,
97                 EnrollmentDao.getInstance(),
98                 FlagsFactory.getFlags(),
99                 getOdpDelegationManager(context, FlagsFactory.getFlags()),
100                 DatastoreManagerFactory.getDatastoreManager(),
101                 new DebugReportApi(context, FlagsFactory.getFlags()));
102     }
103 
104     @VisibleForTesting
AsyncTriggerFetcher( Context context, EnrollmentDao enrollmentDao, Flags flags, IOdpDelegationWrapper odpWrapper, DatastoreManager datastoreManager, DebugReportApi debugReportApi)105     public AsyncTriggerFetcher(
106             Context context,
107             EnrollmentDao enrollmentDao,
108             Flags flags,
109             IOdpDelegationWrapper odpWrapper,
110             DatastoreManager datastoreManager,
111             DebugReportApi debugReportApi) {
112         mContext = context;
113         mEnrollmentDao = enrollmentDao;
114         mFlags = flags;
115         mNetworkConnection = new MeasurementHttpClient(context);
116         mDatastoreManager = datastoreManager;
117         mDebugReportApi = debugReportApi;
118         mOdpWrapper = odpWrapper;
119     }
120 
121     /**
122      * Parse a {@code Trigger}, given response headers, adding the {@code Trigger} to a given list.
123      */
124     @VisibleForTesting
parseTrigger( AsyncRegistration asyncRegistration, String enrollmentId, Map<String, List<String>> headers, AsyncFetchStatus asyncFetchStatus)125     public Optional<Trigger> parseTrigger(
126             AsyncRegistration asyncRegistration,
127             String enrollmentId,
128             Map<String, List<String>> headers,
129             AsyncFetchStatus asyncFetchStatus) {
130         boolean arDebugPermission = asyncRegistration.getDebugKeyAllowed();
131         LoggerFactory.getMeasurementLogger()
132                 .d("Trigger ArDebug permission enabled %b", arDebugPermission);
133         String triggerId = UUID.randomUUID().toString();
134         Trigger.Builder builder = new Trigger.Builder();
135         builder.setId(triggerId);
136         builder.setEnrollmentId(enrollmentId);
137         builder.setAttributionDestination(
138                 getAttributionDestination(
139                         asyncRegistration.getTopOrigin(), asyncRegistration.getType()));
140         builder.setRegistrant(asyncRegistration.getRegistrant());
141         builder.setAdIdPermission(asyncRegistration.hasAdIdPermission());
142         builder.setArDebugPermission(arDebugPermission);
143         builder.setDestinationType(
144                 asyncRegistration.isWebRequest() ? EventSurfaceType.WEB : EventSurfaceType.APP);
145         builder.setTriggerTime(asyncRegistration.getRequestTime());
146         Optional<Uri> registrationUriOrigin =
147                 WebAddresses.originAndScheme(asyncRegistration.getRegistrationUri());
148         LoggerFactory.getMeasurementLogger()
149                 .d(
150                         "AsyncTriggerFetcher: Parsing Trigger. Enrollment ID: %s, Trigger ID: %s",
151                         enrollmentId, triggerId);
152         if (!registrationUriOrigin.isPresent()) {
153             LoggerFactory.getMeasurementLogger()
154                     .d(
155                             "AsyncTriggerFetcher: Invalid registration uri. Host: %s, Enrollment"
156                                     + " ID: %s, Trigger ID: %s",
157                             asyncRegistration.getRegistrationUri().getHost(),
158                             enrollmentId,
159                             triggerId);
160             return Optional.empty();
161         }
162         builder.setRegistrationOrigin(registrationUriOrigin.get());
163         builder.setPlatformAdId(asyncRegistration.getPlatformAdId());
164 
165         boolean isHeaderErrorDebugReportEnabled =
166                 FetcherUtil.isHeaderErrorDebugReportEnabled(
167                         headers.get(TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_INFO),
168                         mFlags);
169         String registrationHeaderStr = null;
170         try {
171             List<String> field =
172                     headers.get(
173                             TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER);
174 
175             // Check the trigger registration header size. Only one header is accepted.
176             if (field == null || field.size() != 1) {
177                 registrationHeaderStr = field == null ? null : field.toString();
178                 asyncFetchStatus.setEntityStatus(EntityStatus.HEADER_ERROR);
179                 LoggerFactory.getMeasurementLogger()
180                         .d(
181                                 "AsyncTriggerFetcher: null, empty, or multiple %s headers. "
182                                         + "Enrollment ID: %s, Trigger ID: %s",
183                                 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER,
184                                 enrollmentId,
185                                 triggerId);
186                 FetcherUtil.sendHeaderErrorDebugReport(
187                         isHeaderErrorDebugReportEnabled,
188                         mDebugReportApi,
189                         mDatastoreManager,
190                         asyncRegistration.getTopOrigin(),
191                         registrationUriOrigin.get(),
192                         asyncRegistration.getRegistrant(),
193                         TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER,
194                         enrollmentId,
195                         registrationHeaderStr);
196                 return Optional.empty();
197             }
198 
199             // Validate trigger header parameters.
200             registrationHeaderStr = field.get(0);
201             boolean isValid =
202                     parseValidateTrigger(
203                             registrationHeaderStr,
204                             asyncRegistration,
205                             builder,
206                             enrollmentId,
207                             triggerId,
208                             asyncFetchStatus);
209             if (!isValid) {
210                 asyncFetchStatus.setEntityStatus(EntityStatus.VALIDATION_ERROR);
211                 LoggerFactory.getMeasurementLogger()
212                         .d(
213                                 "AsyncTriggerFetcher: Invalid trigger params in %s header. "
214                                         + "Enrollment ID: %s, Trigger ID: %s",
215                                 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER,
216                                 enrollmentId,
217                                 triggerId);
218                 FetcherUtil.sendHeaderErrorDebugReport(
219                         isHeaderErrorDebugReportEnabled,
220                         mDebugReportApi,
221                         mDatastoreManager,
222                         asyncRegistration.getTopOrigin(),
223                         registrationUriOrigin.get(),
224                         asyncRegistration.getRegistrant(),
225                         TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER,
226                         enrollmentId,
227                         registrationHeaderStr);
228                 return Optional.empty();
229             }
230 
231             // Set success status and return parsed trigger if no error.
232             asyncFetchStatus.setEntityStatus(EntityStatus.SUCCESS);
233             return Optional.of(builder.build());
234         } catch (JSONException e) {
235             asyncFetchStatus.setEntityStatus(EntityStatus.PARSING_ERROR);
236             LoggerFactory.getMeasurementLogger()
237                     .d(
238                             e,
239                             "AsyncTriggerFetcher: %s header JSON Parsing Exception. "
240                                     + "Enrollment ID: %s, Trigger ID: %s",
241                             TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER,
242                             enrollmentId,
243                             triggerId);
244             FetcherUtil.sendHeaderErrorDebugReport(
245                     isHeaderErrorDebugReportEnabled,
246                     mDebugReportApi,
247                     mDatastoreManager,
248                     asyncRegistration.getTopOrigin(),
249                     registrationUriOrigin.get(),
250                     asyncRegistration.getRegistrant(),
251                     TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER,
252                     enrollmentId,
253                     registrationHeaderStr);
254             return Optional.empty();
255         } catch (IllegalArgumentException e) {
256             asyncFetchStatus.setEntityStatus(EntityStatus.VALIDATION_ERROR);
257             LoggerFactory.getMeasurementLogger()
258                     .d(
259                             e,
260                             "AsyncTriggerFetcher: IllegalArgumentException. Enrollment ID: %s,"
261                                     + " Trigger ID: %s",
262                             enrollmentId,
263                             triggerId);
264             FetcherUtil.sendHeaderErrorDebugReport(
265                     isHeaderErrorDebugReportEnabled,
266                     mDebugReportApi,
267                     mDatastoreManager,
268                     asyncRegistration.getTopOrigin(),
269                     registrationUriOrigin.get(),
270                     asyncRegistration.getRegistrant(),
271                     TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER,
272                     enrollmentId,
273                     registrationHeaderStr);
274             return Optional.empty();
275         }
276     }
277 
getValidTriggerContextId(JSONObject json)278     private Optional<String> getValidTriggerContextId(JSONObject json) throws JSONException {
279         Object triggerContextIdObj = json.get(TriggerHeaderContract.TRIGGER_CONTEXT_ID);
280         if (!(triggerContextIdObj instanceof String)) {
281             LoggerFactory.getMeasurementLogger()
282                     .d(
283                             "%s: %s, is not a String",
284                             TriggerHeaderContract.TRIGGER_CONTEXT_ID,
285                             json.get(TriggerHeaderContract.TRIGGER_CONTEXT_ID).toString());
286             return Optional.empty();
287         }
288 
289         String contextId = triggerContextIdObj.toString();
290         if (contextId.length() > mFlags.getMeasurementMaxLengthOfTriggerContextId()) {
291             LoggerFactory.getMeasurementLogger()
292                     .d(
293                             "Length of %s: \"%s\", exceeds max length of %d",
294                             TriggerHeaderContract.TRIGGER_CONTEXT_ID,
295                             contextId,
296                             mFlags.getMeasurementMaxLengthOfTriggerContextId());
297             return Optional.empty();
298         }
299 
300         return Optional.of(contextId);
301     }
302 
getSourceRegistrationTimeConfig(JSONObject json)303     private Trigger.SourceRegistrationTimeConfig getSourceRegistrationTimeConfig(JSONObject json) {
304         Trigger.SourceRegistrationTimeConfig sourceRegistrationTimeConfig =
305                 Trigger.SourceRegistrationTimeConfig.EXCLUDE;
306         if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME)) {
307             String sourceRegistrationTimeConfigString =
308                     json.optString(TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME)
309                             .toUpperCase(Locale.ENGLISH);
310             sourceRegistrationTimeConfig =
311                     Trigger.SourceRegistrationTimeConfig.valueOf(
312                             sourceRegistrationTimeConfigString);
313         }
314 
315 
316         return sourceRegistrationTimeConfig;
317     }
318 
isAllowlisted(String allowlist, String origin)319     private boolean isAllowlisted(String allowlist, String origin) {
320         if (AllowLists.doesAllowListAllowAll(allowlist)) {
321             return true;
322         }
323         Set<String> elements = new HashSet<>(AllowLists.splitAllowList(allowlist));
324         return elements.contains(origin);
325     }
326 
327     /** Provided a testing hook. */
328     @NonNull
openUrl(@onNull URL url)329     public URLConnection openUrl(@NonNull URL url) throws IOException {
330         return mNetworkConnection.setup(url);
331     }
332 
isXnaAllowedForTriggerRegistrant( Uri registrant, AsyncRegistration.RegistrationType registrationType)333     private boolean isXnaAllowedForTriggerRegistrant(
334             Uri registrant, AsyncRegistration.RegistrationType registrationType) {
335         // If the trigger is registered from web context, only allow-listed apps should be able to
336         // parse attribution config.
337         return !AsyncRegistration.RegistrationType.WEB_TRIGGER.equals(registrationType)
338                 || AllowLists.isPackageAllowListed(
339                         mFlags.getWebContextClientAppAllowList(), registrant.getAuthority());
340     }
341 
342     /**
343      * Fetch a trigger type registration.
344      *
345      * @param asyncRegistration a {@link AsyncRegistration}, a request the record.
346      * @param asyncFetchStatus a {@link AsyncFetchStatus}, stores Ad Tech server status.
347      * @param asyncRedirects a {@link AsyncRedirects}, stores redirections.
348      */
fetchTrigger( AsyncRegistration asyncRegistration, AsyncFetchStatus asyncFetchStatus, AsyncRedirects asyncRedirects)349     public Optional<Trigger> fetchTrigger(
350             AsyncRegistration asyncRegistration,
351             AsyncFetchStatus asyncFetchStatus,
352             AsyncRedirects asyncRedirects) {
353         HttpURLConnection urlConnection = null;
354         Map<String, List<String>> headers;
355         if (!asyncRegistration.getRegistrationUri().getScheme().equalsIgnoreCase("https")) {
356             LoggerFactory.getMeasurementLogger()
357                     .d(
358                             "AsyncTriggerFetcher: Invalid scheme for registrationUri - %s",
359                             asyncRegistration.getRegistrationUri().getScheme());
360             asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.INVALID_URL);
361             return Optional.empty();
362         }
363         // TODO(b/276825561): Fix code duplication between fetchSource & fetchTrigger request flow
364         Optional<String> enrollmentId = Optional.empty();
365         try {
366             urlConnection =
367                     (HttpURLConnection)
368                             openUrl(new URL(asyncRegistration.getRegistrationUri().toString()));
369             urlConnection.setRequestMethod("POST");
370             urlConnection.setInstanceFollowRedirects(false);
371             headers = urlConnection.getHeaderFields();
372             enrollmentId = getEnrollmentId(asyncRegistration);
373 
374             // get ODP header from headers map and forward ODP header
375             long odpHeaderSize = 0;
376             Optional<Map<String, List<String>>> odpHeader = getOdpTriggerHeader(headers);
377             if (odpHeader.isPresent()) {
378                 mOdpWrapper.registerOdpTrigger(
379                         asyncRegistration, odpHeader.get(), enrollmentId.isPresent());
380                 odpHeaderSize = FetcherUtil.calculateHeadersCharactersLength(odpHeader.get());
381             }
382 
383             long headerSize = FetcherUtil.calculateHeadersCharactersLength(headers) - odpHeaderSize;
384             if (mFlags.getMeasurementEnableUpdateTriggerHeaderLimit()
385                     && headerSize > mFlags.getMaxTriggerRegistrationHeaderSizeBytes()) {
386                 LoggerFactory.getMeasurementLogger()
387                         .d(
388                                 "AsyncTriggerFetcher: Trigger registration header size exceeds"
389                                         + " limit bytes %s. Host: %s",
390                                 mFlags.getMaxTriggerRegistrationHeaderSizeBytes(),
391                                 urlConnection.getURL().getHost());
392                 asyncFetchStatus.setResponseStatus(
393                         AsyncFetchStatus.ResponseStatus.HEADER_SIZE_LIMIT_EXCEEDED);
394                 return Optional.empty();
395             }
396             asyncFetchStatus.setResponseSize(headerSize);
397             int responseCode = urlConnection.getResponseCode();
398             LoggerFactory.getMeasurementLogger()
399                     .d(
400                             "AsyncTriggerFetcher - Response code: %s, Method: %s, Host: %s",
401                             responseCode,
402                             urlConnection.getRequestMethod(),
403                             urlConnection.getURL().getHost());
404             if (!FetcherUtil.isRedirect(responseCode) && !FetcherUtil.isSuccess(responseCode)) {
405                 asyncFetchStatus.setResponseStatus(
406                         AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE);
407                 return Optional.empty();
408             }
409             asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.SUCCESS);
410         } catch (MalformedURLException e) {
411             LoggerFactory.getMeasurementLogger()
412                     .e(e, "AsyncTriggerFetcher: Malformed registration target URL");
413             asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.INVALID_URL);
414             return Optional.empty();
415         } catch (IOException e) {
416             LoggerFactory.getMeasurementLogger()
417                     .e(e, "AsyncTriggerFetcher: Failed to get registration response");
418             asyncFetchStatus.setResponseStatus(AsyncFetchStatus.ResponseStatus.NETWORK_ERROR);
419             return Optional.empty();
420         } finally {
421             if (urlConnection != null) {
422                 urlConnection.disconnect();
423             }
424         }
425 
426         asyncRedirects.configure(headers, asyncRegistration);
427 
428         if (!isTriggerHeaderPresent(headers)) {
429             asyncFetchStatus.setEntityStatus(EntityStatus.HEADER_MISSING);
430             asyncFetchStatus.setRedirectOnlyStatus(true);
431             LoggerFactory.getMeasurementLogger()
432                     .d(
433                             "AsyncTriggerFetcher: %s header not found. Host: %s",
434                             TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER,
435                             urlConnection.getURL().getHost());
436             return Optional.empty();
437         }
438 
439         if (enrollmentId.isEmpty()) {
440             LoggerFactory.getMeasurementLogger()
441                     .d(
442                             "AsyncTriggerFetcher: Enrollment ID could not be verified. Host: %s",
443                             urlConnection.getURL().getHost());
444             asyncFetchStatus.setEntityStatus(EntityStatus.INVALID_ENROLLMENT);
445             ErrorLogUtil.e(
446                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__ENROLLMENT_INVALID,
447                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
448             return Optional.empty();
449         }
450         return parseTrigger(asyncRegistration, enrollmentId.get(), headers, asyncFetchStatus);
451     }
452 
453     /** Return instance of IOdpDelegationWrapper. */
getOdpWrapper()454     public IOdpDelegationWrapper getOdpWrapper() {
455         return mOdpWrapper;
456     }
457 
parseValidateTrigger( String registrationHeaderStr, AsyncRegistration asyncRegistration, Trigger.Builder builder, String enrollmentId, String triggerId, AsyncFetchStatus asyncFetchStatus)458     private boolean parseValidateTrigger(
459             String registrationHeaderStr,
460             AsyncRegistration asyncRegistration,
461             Trigger.Builder builder,
462             String enrollmentId,
463             String triggerId,
464             AsyncFetchStatus asyncFetchStatus)
465             throws JSONException {
466         String eventTriggerData = new JSONArray().toString();
467         JSONObject json = new JSONObject(registrationHeaderStr);
468         if (!json.isNull(TriggerHeaderContract.EVENT_TRIGGER_DATA)) {
469             Optional<String> validEventTriggerData =
470                     getValidEventTriggerData(
471                             json.getJSONArray(TriggerHeaderContract.EVENT_TRIGGER_DATA));
472             if (!validEventTriggerData.isPresent()) {
473                 logInvalidTriggerField(
474                         TriggerHeaderContract.EVENT_TRIGGER_DATA, enrollmentId, triggerId);
475                 return false;
476             }
477             eventTriggerData = validEventTriggerData.get();
478         }
479         builder.setEventTriggers(eventTriggerData);
480         if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA)) {
481             Optional<String> validAggregateTriggerData =
482                     getValidAggregateTriggerData(
483                             json.getJSONArray(TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA));
484             if (!validAggregateTriggerData.isPresent()) {
485                 logInvalidTriggerField(
486                         TriggerHeaderContract.AGGREGATABLE_TRIGGER_DATA, enrollmentId, triggerId);
487                 return false;
488             }
489             builder.setAggregateTriggerData(validAggregateTriggerData.get());
490         }
491         Integer filteringIdMaxBytes = null;
492         if (mFlags.getMeasurementEnableFlexibleContributionFiltering()) {
493             if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_FILTERING_ID_MAX_BYTES)) {
494                 Optional<Integer> maybeAggregatableFilteringIdMaxBytes =
495                         getValidAggregatableFilteringIdMaxBytes(json);
496                 if (!maybeAggregatableFilteringIdMaxBytes.isPresent()) {
497                     logInvalidTriggerField(
498                             TriggerHeaderContract.AGGREGATABLE_FILTERING_ID_MAX_BYTES,
499                             enrollmentId,
500                             triggerId);
501                     return false;
502                 }
503                 filteringIdMaxBytes = maybeAggregatableFilteringIdMaxBytes.get();
504                 builder.setAggregatableFilteringIdMaxBytes(filteringIdMaxBytes);
505             } else {
506                 filteringIdMaxBytes = mFlags.getMeasurementDefaultFilteringIdMaxBytes();
507             }
508         }
509         if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_VALUES)) {
510             Object maybeValidAggregatableValues =
511                     json.get(TriggerHeaderContract.AGGREGATABLE_VALUES);
512             boolean invalidAggregatableValuesType =
513                     !(maybeValidAggregatableValues instanceof JSONObject)
514                             && !(maybeValidAggregatableValues instanceof JSONArray);
515             boolean rejectJsonArray =
516                     (maybeValidAggregatableValues instanceof JSONArray)
517                             && !(mFlags.getMeasurementEnableAggregateValueFilters());
518             if (invalidAggregatableValuesType || rejectJsonArray) {
519                 LoggerFactory.getMeasurementLogger()
520                         .d(
521                                 "AsyncTriggerFetcher: Invalid aggregate value type in"
522                                         + " %s. Enrollment ID: %s, Trigger ID: %s",
523                                 TriggerHeaderContract.AGGREGATABLE_VALUES, enrollmentId, triggerId);
524                 return false;
525             }
526             if (maybeValidAggregatableValues instanceof JSONObject) {
527                 if (!isValidAggregateValues(
528                         (JSONObject) maybeValidAggregatableValues,
529                         filteringIdMaxBytes,
530                         asyncFetchStatus)) {
531                     LoggerFactory.getMeasurementLogger()
532                             .d(
533                                     "AsyncTriggerFetcher: Invalid values found in"
534                                             + " %s. Enrollment ID: %s, Trigger ID: %s",
535                                     TriggerHeaderContract.AGGREGATABLE_VALUES,
536                                     enrollmentId,
537                                     triggerId);
538                     return false;
539                 }
540             } else {
541                 if (!isValidAggregatableValuesJsonArray(
542                         (JSONArray) maybeValidAggregatableValues,
543                         filteringIdMaxBytes,
544                         asyncFetchStatus)) {
545                     LoggerFactory.getMeasurementLogger()
546                             .d(
547                                     "AsyncTriggerFetcher: Invalid %s array. Enrollment ID: %s,"
548                                             + " Trigger ID: %s",
549                                     TriggerHeaderContract.AGGREGATABLE_VALUES,
550                                     enrollmentId,
551                                     triggerId);
552                     return false;
553                 }
554                 if (mFlags.getMeasurementEnableAggregateValueFilters()) {
555                     asyncFetchStatus.setIsTriggerAggregatableValueFiltersConfigured(true);
556                 }
557             }
558             builder.setAggregateValuesString(
559                     json.getString(TriggerHeaderContract.AGGREGATABLE_VALUES));
560         }
561         if (!json.isNull(TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS)) {
562             Optional<String> validAggregateDeduplicationKeysString =
563                     getValidAggregateDuplicationKeysString(
564                             json.getJSONArray(
565                                     TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS));
566             if (!validAggregateDeduplicationKeysString.isPresent()) {
567                 logInvalidTriggerField(
568                         TriggerHeaderContract.AGGREGATABLE_DEDUPLICATION_KEYS,
569                         enrollmentId,
570                         triggerId);
571                 return false;
572             }
573             builder.setAggregateDeduplicationKeys(validAggregateDeduplicationKeysString.get());
574         }
575         boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit();
576         if (!json.isNull(FilterContract.FILTERS)) {
577             JSONArray filters = Filter.maybeWrapFilters(json, FilterContract.FILTERS);
578             if (!FetcherUtil.areValidAttributionFilters(
579                     filters, mFlags, true, shouldCheckFilterSize)) {
580                 logInvalidTriggerField(FilterContract.FILTERS, enrollmentId, triggerId);
581                 return false;
582             }
583             builder.setFilters(filters.toString());
584         }
585         if (!json.isNull(FilterContract.NOT_FILTERS)) {
586             JSONArray notFilters = Filter.maybeWrapFilters(json, FilterContract.NOT_FILTERS);
587             if (!FetcherUtil.areValidAttributionFilters(
588                     notFilters, mFlags, true, shouldCheckFilterSize)) {
589                 logInvalidTriggerField(FilterContract.NOT_FILTERS, enrollmentId, triggerId);
590                 return false;
591             }
592             builder.setNotFilters(notFilters.toString());
593         }
594         if (!json.isNull(TriggerHeaderContract.DEBUG_REPORTING)) {
595             builder.setIsDebugReporting(json.optBoolean(TriggerHeaderContract.DEBUG_REPORTING));
596         }
597         if (!json.isNull(TriggerHeaderContract.DEBUG_KEY)) {
598             Optional<UnsignedLong> maybeDebugKey =
599                     FetcherUtil.extractUnsignedLong(json, TriggerHeaderContract.DEBUG_KEY);
600             if (maybeDebugKey.isPresent()) {
601                 builder.setDebugKey(maybeDebugKey.get());
602             }
603         }
604         if (mFlags.getMeasurementEnableXNA()
605                 && !json.isNull(TriggerHeaderContract.X_NETWORK_KEY_MAPPING)) {
606             if (!isValidXNetworkKeyMapping(
607                     json.getJSONObject(TriggerHeaderContract.X_NETWORK_KEY_MAPPING))) {
608                 LoggerFactory.getMeasurementLogger()
609                         .d(
610                                 "AsyncTriggerFetcher: Invalid %s, continuing to process trigger."
611                                         + " Enrollment ID: %s, Trigger ID: %s",
612                                 TriggerHeaderContract.X_NETWORK_KEY_MAPPING,
613                                 enrollmentId,
614                                 triggerId);
615             } else {
616                 builder.setAdtechBitMapping(
617                         json.getString(TriggerHeaderContract.X_NETWORK_KEY_MAPPING));
618             }
619         }
620         if (mFlags.getMeasurementEnableXNA()
621                 && isXnaAllowedForTriggerRegistrant(
622                         asyncRegistration.getRegistrant(), asyncRegistration.getType())
623                 && !json.isNull(TriggerHeaderContract.ATTRIBUTION_CONFIG)) {
624             String attributionConfigsString =
625                     extractValidAttributionConfigs(
626                             json.getJSONArray(TriggerHeaderContract.ATTRIBUTION_CONFIG));
627             builder.setAttributionConfig(attributionConfigsString);
628         }
629 
630         if (mFlags.getMeasurementAggregationCoordinatorOriginEnabled()
631                 && !json.isNull(TriggerHeaderContract.AGGREGATION_COORDINATOR_ORIGIN)) {
632             String origin = json.getString(TriggerHeaderContract.AGGREGATION_COORDINATOR_ORIGIN);
633             String allowlist = mFlags.getMeasurementAggregationCoordinatorOriginList();
634             if (origin.isEmpty() || !isAllowlisted(allowlist, origin)) {
635                 logInvalidTriggerField(
636                         TriggerHeaderContract.AGGREGATION_COORDINATOR_ORIGIN,
637                         enrollmentId,
638                         triggerId);
639                 return false;
640             }
641             builder.setAggregationCoordinatorOrigin(Uri.parse(origin));
642         }
643 
644         String enrollmentBlockList =
645                 mFlags.getMeasurementPlatformDebugAdIdMatchingEnrollmentBlocklist();
646         Set<String> blockedEnrollmentsString =
647                 new HashSet<>(AllowLists.splitAllowList(enrollmentBlockList));
648         if (!AllowLists.doesAllowListAllowAll(enrollmentBlockList)
649                 && !blockedEnrollmentsString.contains(enrollmentId)
650                 && !json.isNull(TriggerHeaderContract.DEBUG_AD_ID)) {
651             builder.setDebugAdId(json.optString(TriggerHeaderContract.DEBUG_AD_ID));
652         }
653 
654         Set<String> allowedEnrollmentsString =
655                 new HashSet<>(
656                         AllowLists.splitAllowList(
657                                 mFlags.getMeasurementDebugJoinKeyEnrollmentAllowlist()));
658         if (allowedEnrollmentsString.contains(enrollmentId)
659                 && !json.isNull(TriggerHeaderContract.DEBUG_JOIN_KEY)) {
660             builder.setDebugJoinKey(json.optString(TriggerHeaderContract.DEBUG_JOIN_KEY));
661         }
662 
663         Trigger.SourceRegistrationTimeConfig sourceRegistrationTimeConfig =
664                 getSourceRegistrationTimeConfig(json);
665 
666         if (filteringIdMaxBytes != null
667                 && filteringIdMaxBytes != mFlags.getMeasurementDefaultFilteringIdMaxBytes()
668                 && Trigger.SourceRegistrationTimeConfig.INCLUDE.equals(
669                         sourceRegistrationTimeConfig)) {
670             LoggerFactory.getMeasurementLogger()
671                     .d(
672                             "AsyncTriggerFetcher: Non-default %s cannot be provided when %s"
673                                     + " is %s. Enrollment ID: %s, Trigger ID: %s",
674                             TriggerHeaderContract.AGGREGATABLE_FILTERING_ID_MAX_BYTES,
675                             TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME,
676                             Trigger.SourceRegistrationTimeConfig.INCLUDE.name(),
677                             enrollmentId,
678                             triggerId);
679             return false;
680         }
681 
682         builder.setAggregatableSourceRegistrationTimeConfig(sourceRegistrationTimeConfig);
683 
684         if (!json.isNull(TriggerHeaderContract.TRIGGER_CONTEXT_ID)) {
685             if (Trigger.SourceRegistrationTimeConfig.INCLUDE.equals(sourceRegistrationTimeConfig)) {
686                 LoggerFactory.getMeasurementLogger()
687                         .d(
688                                 "AsyncTriggerFetcher: %s cannot be provided when %s"
689                                         + " is %s. Enrollment ID: %s, Trigger ID: %s",
690                                 TriggerHeaderContract.TRIGGER_CONTEXT_ID,
691                                 TriggerHeaderContract.AGGREGATABLE_SOURCE_REGISTRATION_TIME,
692                                 Trigger.SourceRegistrationTimeConfig.INCLUDE.name(),
693                                 enrollmentId,
694                                 triggerId);
695                 return false;
696             }
697 
698             Optional<String> contextIdOpt = getValidTriggerContextId(json);
699             if (contextIdOpt.isEmpty()) {
700                 logInvalidTriggerField(
701                         TriggerHeaderContract.TRIGGER_CONTEXT_ID, enrollmentId, triggerId);
702                 return false;
703             }
704 
705             asyncFetchStatus.setIsTriggerContextIdConfigured(true);
706             builder.setTriggerContextId(contextIdOpt.get());
707         }
708 
709         if (mFlags.getMeasurementEnableAttributionScope()
710                 && !json.isNull(TriggerHeaderContract.ATTRIBUTION_SCOPES)) {
711             Optional<List<String>> attributionScopes =
712                     FetcherUtil.extractStringArray(
713                             json,
714                             TriggerHeaderContract.ATTRIBUTION_SCOPES,
715                             Integer.MAX_VALUE,
716                             Integer.MAX_VALUE);
717             if (attributionScopes.isEmpty()) {
718                 logInvalidTriggerField(
719                         TriggerHeaderContract.ATTRIBUTION_SCOPES, enrollmentId, triggerId);
720                 return false;
721             }
722             builder.setAttributionScopesString(
723                     json.getJSONArray(TriggerHeaderContract.ATTRIBUTION_SCOPES).toString());
724         }
725 
726         if (mFlags.getMeasurementEnableAggregateDebugReporting()
727                 && !json.isNull(TriggerHeaderContract.AGGREGATABLE_DEBUG_REPORTING)) {
728             Optional<String> validAggregateDebugReporting =
729                     FetcherUtil.getValidAggregateDebugReportingWithoutBudget(
730                             json.getJSONObject(TriggerHeaderContract.AGGREGATABLE_DEBUG_REPORTING),
731                             mFlags);
732             if (validAggregateDebugReporting.isPresent()) {
733                 builder.setAggregateDebugReportingString(validAggregateDebugReporting.get());
734             } else {
735                 LoggerFactory.getMeasurementLogger()
736                         .d(
737                                 "AsyncTriggerFetcher: Invalid %s, continuing to process trigger. "
738                                         + "Enrollment ID: %s, Trigger ID: %s",
739                                 TriggerHeaderContract.AGGREGATABLE_DEBUG_REPORTING,
740                                 enrollmentId,
741                                 triggerId);
742             }
743         }
744 
745         if (mFlags.getMeasurementEnableAggregatableNamedBudgets()
746                 && !json.isNull(NamedBudgetContract.NAMED_BUDGETS)) {
747             Object namedBudgetsObj = json.get(NamedBudgetContract.NAMED_BUDGETS);
748             if (!(namedBudgetsObj instanceof JSONArray)) {
749                 LoggerFactory.getMeasurementLogger()
750                         .d(
751                                 "AsyncTriggerFetcher: %s is not an array."
752                                         + " Enrollment ID: %s, Trigger ID: %s",
753                                 NamedBudgetContract.NAMED_BUDGETS, enrollmentId, triggerId);
754                 return false;
755             }
756             JSONArray namedBudgetsArray = (JSONArray) namedBudgetsObj;
757             Optional<String> maybeNamedBudgets = parseNamedBudgets(namedBudgetsArray);
758             if (maybeNamedBudgets.isEmpty()) {
759                 logInvalidTriggerField(NamedBudgetContract.NAMED_BUDGETS, enrollmentId, triggerId);
760                 return false;
761             }
762             builder.setNamedBudgetsString(maybeNamedBudgets.get());
763         }
764 
765         return true;
766     }
767 
isTriggerHeaderPresent(Map<String, List<String>> headers)768     private boolean isTriggerHeaderPresent(Map<String, List<String>> headers) {
769         return headers.containsKey(
770                 TriggerHeaderContract.HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER);
771     }
772 
getOdpTriggerHeader( Map<String, List<String>> headers)773     private Optional<Map<String, List<String>>> getOdpTriggerHeader(
774             Map<String, List<String>> headers) {
775         return headers.containsKey(OdpTriggerHeaderContract.HEADER_ODP_REGISTER_TRIGGER)
776                 ? Optional.of(Map.of(
777                         OdpTriggerHeaderContract.HEADER_ODP_REGISTER_TRIGGER,
778                         headers.get(
779                                 OdpTriggerHeaderContract.HEADER_ODP_REGISTER_TRIGGER)))
780                 : Optional.empty();
781     }
782 
getEnrollmentId(AsyncRegistration asyncRegistration)783     private Optional<String> getEnrollmentId(AsyncRegistration asyncRegistration) {
784         return mFlags.isDisableMeasurementEnrollmentCheck()
785                 ? WebAddresses.topPrivateDomainAndScheme(asyncRegistration.getRegistrationUri())
786                         .map(Uri::toString)
787                 : Enrollment.getValidEnrollmentId(
788                         asyncRegistration.getRegistrationUri(),
789                         asyncRegistration.getRegistrant().getAuthority(),
790                         mEnrollmentDao,
791                         mContext,
792                         mFlags);
793     }
794 
parseNamedBudgets(JSONArray namedBudgetsArray)795     private Optional<String> parseNamedBudgets(JSONArray namedBudgetsArray) throws JSONException {
796         boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit();
797         JSONArray validNamedBudgets = new JSONArray();
798 
799         for (int i = 0; i < namedBudgetsArray.length(); i++) {
800             JSONObject namedBudget = new JSONObject();
801             Object maybeBudgetObj = namedBudgetsArray.get(i);
802 
803             if (!(maybeBudgetObj instanceof JSONObject)) {
804                 LoggerFactory.getMeasurementLogger()
805                         .d("parseNamedBudgets: named budget " + i + " is not a JSON object.");
806                 return Optional.empty();
807             }
808             JSONObject budgetObj = (JSONObject) maybeBudgetObj;
809 
810             if (!budgetObj.isNull(NamedBudgetContract.NAME)) {
811                 if (!(budgetObj.get(NamedBudgetContract.NAME) instanceof String)) {
812                     LoggerFactory.getMeasurementLogger()
813                             .d(
814                                     "parseNamedBudgets: Value for named budget "
815                                             + i
816                                             + " \"name\" is not a string.");
817                     return Optional.empty();
818                 }
819                 namedBudget.put(
820                         NamedBudgetContract.NAME, budgetObj.getString(NamedBudgetContract.NAME));
821             }
822             if (!budgetObj.isNull(FilterContract.FILTERS)) {
823                 JSONArray filters = Filter.maybeWrapFilters(budgetObj, FilterContract.FILTERS);
824                 if (!FetcherUtil.areValidAttributionFilters(
825                         filters,
826                         mFlags,
827                         /* canIncludeLookbackWindow= */ true,
828                         shouldCheckFilterSize)) {
829                     LoggerFactory.getMeasurementLogger()
830                             .d(
831                                     "parseNamedBudgets: Named budget "
832                                             + i
833                                             + " contains invalid filters.");
834                     return Optional.empty();
835                 }
836                 namedBudget.put(FilterContract.FILTERS, filters);
837             }
838             if (!budgetObj.isNull(FilterContract.NOT_FILTERS)) {
839                 JSONArray notFilters =
840                         Filter.maybeWrapFilters(budgetObj, FilterContract.NOT_FILTERS);
841                 if (!FetcherUtil.areValidAttributionFilters(
842                         notFilters,
843                         mFlags,
844                         /* canIncludeLookbackWindow= */ true,
845                         shouldCheckFilterSize)) {
846                     LoggerFactory.getMeasurementLogger()
847                             .d(
848                                     "parseNamedBudgets: Named budget "
849                                             + i
850                                             + " contains invalid not_filters.");
851                     return Optional.empty();
852                 }
853                 namedBudget.put(FilterContract.NOT_FILTERS, notFilters);
854             }
855             validNamedBudgets.put(namedBudget);
856         }
857         return Optional.of(validNamedBudgets.toString());
858     }
859 
getValidEventTriggerData(JSONArray eventTriggerDataArr)860     private Optional<String> getValidEventTriggerData(JSONArray eventTriggerDataArr) {
861         JSONArray validEventTriggerData = new JSONArray();
862         for (int i = 0; i < eventTriggerDataArr.length(); i++) {
863             JSONObject validEventTriggerDatum = new JSONObject();
864             try {
865                 JSONObject eventTriggerDatum = eventTriggerDataArr.getJSONObject(i);
866                 UnsignedLong triggerData = new UnsignedLong(0L);
867                 if (!eventTriggerDatum.isNull("trigger_data")) {
868                     Optional<UnsignedLong> maybeTriggerData =
869                             FetcherUtil.extractUnsignedLong(eventTriggerDatum, "trigger_data");
870                     if (!maybeTriggerData.isPresent()) {
871                         return Optional.empty();
872                     }
873                     triggerData = maybeTriggerData.get();
874                 }
875                 validEventTriggerDatum.put("trigger_data", triggerData);
876                 if (!eventTriggerDatum.isNull("priority")) {
877                     Optional<Long> maybePriority =
878                             FetcherUtil.extractLongString(eventTriggerDatum, "priority");
879                     if (!maybePriority.isPresent()) {
880                         return Optional.empty();
881                     }
882                     validEventTriggerDatum.put("priority", String.valueOf(maybePriority.get()));
883                 }
884                 if (!eventTriggerDatum.isNull("value")) {
885                     Optional<Long> maybeValue =
886                             FetcherUtil.extractLong(eventTriggerDatum, "value");
887                     if (!maybeValue.isPresent()) {
888                         return Optional.empty();
889                     }
890                     long value = maybeValue.get();
891                     if (value < 1L || value > TriggerSpecs.MAX_BUCKET_THRESHOLD) {
892                         return Optional.empty();
893                     }
894                     validEventTriggerDatum.put("value", value);
895                 }
896                 if (!eventTriggerDatum.isNull("deduplication_key")) {
897                     Optional<UnsignedLong> maybeDedupKey = FetcherUtil.extractUnsignedLong(
898                             eventTriggerDatum, "deduplication_key");
899                     if (!maybeDedupKey.isPresent()) {
900                         return Optional.empty();
901                     }
902                     validEventTriggerDatum.put("deduplication_key", maybeDedupKey.get());
903                 }
904                 boolean shouldCheckFilterSize =
905                         !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit();
906                 if (!eventTriggerDatum.isNull(FilterContract.FILTERS)) {
907                     JSONArray filters =
908                             Filter.maybeWrapFilters(eventTriggerDatum, FilterContract.FILTERS);
909                     if (!FetcherUtil.areValidAttributionFilters(
910                             filters,
911                             mFlags,
912                             /* canIncludeLookbackWindow= */ true,
913                             shouldCheckFilterSize)) {
914                         LoggerFactory.getMeasurementLogger()
915                                 .d("getValidEventTriggerData: filters are invalid.");
916                         return Optional.empty();
917                     }
918                     validEventTriggerDatum.put(FilterContract.FILTERS, filters);
919                 }
920                 if (!eventTriggerDatum.isNull(FilterContract.NOT_FILTERS)) {
921                     JSONArray notFilters =
922                             Filter.maybeWrapFilters(eventTriggerDatum, FilterContract.NOT_FILTERS);
923                     if (!FetcherUtil.areValidAttributionFilters(
924                             notFilters,
925                             mFlags,
926                             /* canIncludeLookbackWindow= */ true,
927                             shouldCheckFilterSize)) {
928                         LoggerFactory.getMeasurementLogger()
929                                 .d("getValidEventTriggerData: not-filters are invalid.");
930                         return Optional.empty();
931                     }
932                     validEventTriggerDatum.put(FilterContract.NOT_FILTERS, notFilters);
933                 }
934                 validEventTriggerData.put(validEventTriggerDatum);
935             } catch (JSONException e) {
936                 LoggerFactory.getMeasurementLogger()
937                         .d(e, "AsyncTriggerFetcher: JSONException parsing event trigger datum.");
938                 return Optional.empty();
939             } catch (NumberFormatException e) {
940                 LoggerFactory.getMeasurementLogger()
941                         .d(
942                                 e,
943                                 "AsyncTriggerFetcher: NumberFormatException parsing event trigger "
944                                         + "datum.");
945                 return Optional.empty();
946             }
947         }
948         return Optional.of(validEventTriggerData.toString());
949     }
950 
getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr)951     private Optional<String> getValidAggregateTriggerData(JSONArray aggregateTriggerDataArr)
952             throws JSONException {
953         JSONArray validAggregateTriggerData = new JSONArray();
954         boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit();
955         for (int i = 0; i < aggregateTriggerDataArr.length(); i++) {
956             JSONObject aggregateTriggerData = aggregateTriggerDataArr.getJSONObject(i);
957             String keyPiece = aggregateTriggerData.optString("key_piece");
958             if (!FetcherUtil.isValidAggregateKeyPiece(keyPiece, mFlags)) {
959                 LoggerFactory.getMeasurementLogger()
960                         .d("Aggregate trigger data key-piece is invalid. %s", keyPiece);
961                 return Optional.empty();
962             }
963             JSONArray sourceKeys;
964             if (aggregateTriggerData.isNull("source_keys")) {
965                 sourceKeys = new JSONArray();
966                 aggregateTriggerData.put("source_keys", sourceKeys);
967             } else {
968                 // Registration will be rejected if source-keys is not a list
969                 sourceKeys = aggregateTriggerData.getJSONArray("source_keys");
970             }
971             if (shouldCheckFilterSize
972                     && sourceKeys.length()
973                             > mFlags.getMeasurementMaxAggregateKeysPerTriggerRegistration()) {
974                 LoggerFactory.getMeasurementLogger()
975                         .d(
976                                 "Aggregate trigger data source-keys list has more entries "
977                                         + "than permitted.");
978                 return Optional.empty();
979             }
980             for (int j = 0; j < sourceKeys.length(); j++) {
981                 Object sourceKey = sourceKeys.get(j);
982                 if (!(sourceKey instanceof String)
983                         || !FetcherUtil.isValidAggregateKeyId((String) sourceKey)) {
984                     LoggerFactory.getMeasurementLogger()
985                             .d("Aggregate trigger data source-key is invalid. %s", sourceKey);
986                     return Optional.empty();
987                 }
988             }
989             if (!aggregateTriggerData.isNull(FilterContract.FILTERS)) {
990                 JSONArray filters =
991                         Filter.maybeWrapFilters(aggregateTriggerData, FilterContract.FILTERS);
992                 if (!FetcherUtil.areValidAttributionFilters(
993                         filters,
994                         mFlags,
995                         /* canIncludeLookbackWindow= */ true,
996                         shouldCheckFilterSize)) {
997                     LoggerFactory.getMeasurementLogger()
998                             .d("Aggregate trigger data filters are invalid.");
999                     return Optional.empty();
1000                 }
1001                 aggregateTriggerData.put(FilterContract.FILTERS, filters);
1002             }
1003             if (!aggregateTriggerData.isNull(FilterContract.NOT_FILTERS)) {
1004                 JSONArray notFilters =
1005                         Filter.maybeWrapFilters(aggregateTriggerData, FilterContract.NOT_FILTERS);
1006                 if (!FetcherUtil.areValidAttributionFilters(
1007                         notFilters,
1008                         mFlags,
1009                         /* canIncludeLookbackWindow= */ true,
1010                         shouldCheckFilterSize)) {
1011                     LoggerFactory.getMeasurementLogger()
1012                             .d("Aggregate trigger data not-filters are invalid.");
1013                     return Optional.empty();
1014                 }
1015                 aggregateTriggerData.put(FilterContract.NOT_FILTERS, notFilters);
1016             }
1017             if (!aggregateTriggerData.isNull("x_network_data")) {
1018                 JSONObject xNetworkDataJson = aggregateTriggerData.getJSONObject("x_network_data");
1019                 // This is in order to validate the JSON parsing does not throw exception
1020                 new XNetworkData.Builder(xNetworkDataJson);
1021             }
1022             validAggregateTriggerData.put(aggregateTriggerData);
1023         }
1024         return Optional.of(validAggregateTriggerData.toString());
1025     }
1026 
1027     /**
1028      * Returns true if all values in aggregatable_values are valid. Default Case:
1029      * {"campaignCounts":1664} Flexible Contribution Filtering: {"campaignCounts": {"value": 1664,
1030      * "filtering_id: 123}}
1031      */
isValidAggregateValues( JSONObject aggregateValues, Integer filteringIdMaxBytes, AsyncFetchStatus asyncFetchStatus)1032     private boolean isValidAggregateValues(
1033             JSONObject aggregateValues,
1034             Integer filteringIdMaxBytes,
1035             AsyncFetchStatus asyncFetchStatus)
1036             throws JSONException {
1037         if (!mFlags.getMeasurementEnableUpdateTriggerHeaderLimit()
1038                 && aggregateValues.length()
1039                         > mFlags.getMeasurementMaxAggregateKeysPerTriggerRegistration()) {
1040             LoggerFactory.getMeasurementLogger()
1041                     .d(
1042                             "Aggregate values have more keys than permitted. %s",
1043                             aggregateValues.length());
1044             return false;
1045         }
1046         Iterator<String> ids = aggregateValues.keys();
1047         while (ids.hasNext()) {
1048             String id = ids.next();
1049             if (!FetcherUtil.isValidAggregateKeyId(id)) {
1050                 LoggerFactory.getMeasurementLogger()
1051                         .d("Aggregate values key ID is invalid. %s", id);
1052                 return false;
1053             }
1054             Object value = aggregateValues.get(id);
1055             if (value instanceof JSONObject) {
1056                 if (!mFlags.getMeasurementEnableFlexibleContributionFiltering()
1057                         || !isValidAggregateValueObj(
1058                                 (JSONObject) value, filteringIdMaxBytes, asyncFetchStatus)) {
1059                     return false;
1060                 }
1061             } else if (!isValidAggregatableValuesValue(value)) {
1062                 return false;
1063             }
1064         }
1065         return true;
1066     }
1067 
1068     /* Returns true if the aggregatable_values#value field is valid. */
isValidAggregatableValuesValue(Object maybeValue)1069     private boolean isValidAggregatableValuesValue(Object maybeValue) {
1070         Optional<BigDecimal> maybeValidValue = FetcherUtil.extractIntegralValue(maybeValue);
1071         if (maybeValidValue.isEmpty()) {
1072             return false;
1073         }
1074         BigDecimal validValue = maybeValidValue.get();
1075         if (validValue.compareTo(BigDecimal.valueOf(1)) < 0
1076                 || validValue.compareTo(
1077                                 BigDecimal.valueOf(
1078                                         mFlags.getMeasurementMaxSumOfAggregateValuesPerSource()))
1079                         > 0) {
1080             return false;
1081         }
1082         return true;
1083     }
1084 
1085     /**
1086      * Returns true if JSONArray aggregatable_values is valid. Aggregate Values Filtering: [{
1087      * "values": {"campaignCounts": 32768}, "filters": {"category": ["filter_1"]},
1088      * "not_filters":{"category": ["filter_2"]} }] Flexible Contribution Filtering: [{
1089      * "values":{"campaignCounts": {"value": 32768, "filtering_id": 123}}, "filters": {"category":
1090      * ["filter_1"]}, "not_filters": {"category": ["filter_2"]} }]
1091      */
isValidAggregatableValuesJsonArray( JSONArray aggregatableValuesArr, Integer filteringIdMaxBytes, AsyncFetchStatus asyncFetchStatus)1092     private boolean isValidAggregatableValuesJsonArray(
1093             JSONArray aggregatableValuesArr,
1094             Integer filteringIdMaxBytes,
1095             AsyncFetchStatus asyncFetchStatus)
1096             throws JSONException {
1097         boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit();
1098         for (int i = 0; i < aggregatableValuesArr.length(); i++) {
1099             JSONObject aggregatableValuesObj = aggregatableValuesArr.getJSONObject(i);
1100             // validate values
1101             if (aggregatableValuesObj.isNull(AggregatableValuesConfigContract.VALUES)
1102                     || !isValidAggregateValues(
1103                             aggregatableValuesObj.getJSONObject(
1104                                     AggregatableValuesConfigContract.VALUES),
1105                             filteringIdMaxBytes,
1106                             asyncFetchStatus)) {
1107                 LoggerFactory.getMeasurementLogger()
1108                         .d(
1109                                 "AGGREGATABLE_VALUES: %s is null or invalid.",
1110                                 AggregatableValuesConfigContract.VALUES);
1111                 return false;
1112             }
1113             if (!aggregatableValuesObj.isNull(FilterContract.FILTERS)) {
1114                 JSONArray filters =
1115                         Filter.maybeWrapFilters(aggregatableValuesObj, FilterContract.FILTERS);
1116                 if (!FetcherUtil.areValidAttributionFilters(
1117                         filters,
1118                         mFlags,
1119                         /* canIncludeLookbackWindow= */ true,
1120                         shouldCheckFilterSize)) {
1121                     LoggerFactory.getMeasurementLogger()
1122                             .d("AGGREGATABLE_VALUES: filters are invalid.");
1123                     return false;
1124                 }
1125             }
1126             // validate not_filters
1127             if (!aggregatableValuesObj.isNull(FilterContract.NOT_FILTERS)) {
1128                 JSONArray not_filters =
1129                         Filter.maybeWrapFilters(aggregatableValuesObj, FilterContract.NOT_FILTERS);
1130                 if (!FetcherUtil.areValidAttributionFilters(
1131                         not_filters,
1132                         mFlags,
1133                         /* canIncludeLookbackWindow= */ true,
1134                         shouldCheckFilterSize)) {
1135                     LoggerFactory.getMeasurementLogger()
1136                             .d("AGGREGATABLE_VALUES: not_filters are invalid.");
1137                     return false;
1138                 }
1139             }
1140         }
1141         return true;
1142     }
1143 
1144     /** Returns true if filtering_id in inclusive range of 0-255^maxBytes. */
isValidFilteringId( JSONObject value, Integer maxBytes, AsyncFetchStatus asyncFetchStatus)1145     private boolean isValidFilteringId(
1146             JSONObject value, Integer maxBytes, AsyncFetchStatus asyncFetchStatus) {
1147         Optional<UnsignedLong> maybeFilteringId =
1148                 FetcherUtil.extractUnsignedLong(value, AggregatableKeyValueContract.FILTERING_ID);
1149         if (maybeFilteringId.isEmpty()) {
1150             LoggerFactory.getMeasurementLogger()
1151                     .e(
1152                             String.format(
1153                                     "AGGREGATABLE_VALUES: filtering_id is not an unsigned long"
1154                                             + " string"));
1155             return false;
1156         }
1157         BigInteger filteringId = new BigInteger(maybeFilteringId.get().toString());
1158         if (filteringId.compareTo(BigInteger.valueOf(ONE_BYTE).pow(maxBytes)) >= 0) {
1159             LoggerFactory.getMeasurementLogger()
1160                     .e(String.format("AGGREGATABLE_VALUES: filtering_id is out of bounds"));
1161             return false;
1162         }
1163         asyncFetchStatus.setIsTriggerFilteringIdConfigured(true);
1164         return true;
1165     }
1166 
1167     /*
1168      * Returns true if JSONObject has valid value and filtering_id
1169      * Input looks like: {“value”: 32768, “filtering_id”: 123}
1170      */
isValidAggregateValueObj( JSONObject obj, Integer filteringIdMaxBytes, AsyncFetchStatus asyncFetchStatus)1171     private boolean isValidAggregateValueObj(
1172             JSONObject obj, Integer filteringIdMaxBytes, AsyncFetchStatus asyncFetchStatus)
1173             throws JSONException {
1174         // Validate value
1175         if (obj.isNull(AggregatableKeyValueContract.VALUE)
1176                 || !isValidAggregatableValuesValue(obj.get(AggregatableKeyValueContract.VALUE))) {
1177             LoggerFactory.getMeasurementLogger()
1178                     .d(
1179                             String.format(
1180                                     "Aggregatable Values: Null or invalid %s",
1181                                     AggregatableKeyValueContract.VALUE));
1182             return false;
1183         }
1184         // Validate filtering_id
1185         if (!obj.isNull(AggregatableKeyValueContract.FILTERING_ID)
1186                 && (!isValidFilteringId(obj, filteringIdMaxBytes, asyncFetchStatus))) {
1187             return false;
1188         }
1189         return true;
1190     }
1191 
1192     /** Returns filtering_id_max_bytes if in inclusive range 1-8. */
getValidAggregatableFilteringIdMaxBytes(JSONObject headers)1193     private Optional<Integer> getValidAggregatableFilteringIdMaxBytes(JSONObject headers) {
1194         Optional<BigDecimal> maybeValidAggregatableFilteringIdMaxBytes =
1195                 FetcherUtil.extractIntegralValue(
1196                         headers, TriggerHeaderContract.AGGREGATABLE_FILTERING_ID_MAX_BYTES);
1197         if (maybeValidAggregatableFilteringIdMaxBytes.isPresent()) {
1198             BigDecimal lowerBound = new BigDecimal("1");
1199             BigDecimal upperBound = new BigDecimal(mFlags.getMeasurementMaxFilteringIdMaxBytes());
1200             if (maybeValidAggregatableFilteringIdMaxBytes.get().compareTo(lowerBound) < 0
1201                     || maybeValidAggregatableFilteringIdMaxBytes.get().compareTo(upperBound) > 0) {
1202                 LoggerFactory.getMeasurementLogger()
1203                         .e("AGGREGATABLE_FILTERING_ID_MAX_BYTES is out of bounds.");
1204                 return Optional.empty();
1205             }
1206             return Optional.of(maybeValidAggregatableFilteringIdMaxBytes.get().intValue());
1207         }
1208         return Optional.empty();
1209     }
1210 
getValidAggregateDuplicationKeysString( JSONArray aggregateDeduplicationKeys)1211     private Optional<String> getValidAggregateDuplicationKeysString(
1212             JSONArray aggregateDeduplicationKeys) throws JSONException {
1213         JSONArray validAggregateDeduplicationKeys = new JSONArray();
1214         boolean shouldCheckFilterSize = !mFlags.getMeasurementEnableUpdateTriggerHeaderLimit();
1215         if (shouldCheckFilterSize
1216                 && aggregateDeduplicationKeys.length()
1217                         > mFlags.getMeasurementMaxAggregateDeduplicationKeysPerRegistration()) {
1218             LoggerFactory.getMeasurementLogger()
1219                     .d(
1220                             "Aggregate deduplication keys have more keys than permitted. %s",
1221                             aggregateDeduplicationKeys.length());
1222             return Optional.empty();
1223         }
1224         for (int i = 0; i < aggregateDeduplicationKeys.length(); i++) {
1225             JSONObject aggregateDedupKey = new JSONObject();
1226             JSONObject deduplicationKeyObj = aggregateDeduplicationKeys.getJSONObject(i);
1227 
1228             if (!deduplicationKeyObj.isNull("deduplication_key")) {
1229                 Optional<UnsignedLong> maybeDedupKey = FetcherUtil.extractUnsignedLong(
1230                         deduplicationKeyObj, "deduplication_key");
1231                 if (!maybeDedupKey.isPresent()) {
1232                     return Optional.empty();
1233                 }
1234                 aggregateDedupKey.put("deduplication_key", maybeDedupKey.get().toString());
1235             }
1236             if (!deduplicationKeyObj.isNull(FilterContract.FILTERS)) {
1237                 JSONArray filters =
1238                         Filter.maybeWrapFilters(deduplicationKeyObj, FilterContract.FILTERS);
1239                 if (!FetcherUtil.areValidAttributionFilters(
1240                         filters,
1241                         mFlags,
1242                         /* canIncludeLookbackWindow= */ true,
1243                         shouldCheckFilterSize)) {
1244                     LoggerFactory.getMeasurementLogger()
1245                             .d("Aggregate deduplication key: " + i + " contains invalid filters.");
1246                     return Optional.empty();
1247                 }
1248                 aggregateDedupKey.put(FilterContract.FILTERS, filters);
1249             }
1250             if (!deduplicationKeyObj.isNull(FilterContract.NOT_FILTERS)) {
1251                 JSONArray notFilters =
1252                         Filter.maybeWrapFilters(deduplicationKeyObj, FilterContract.NOT_FILTERS);
1253                 if (!FetcherUtil.areValidAttributionFilters(
1254                         notFilters,
1255                         mFlags,
1256                         /* canIncludeLookbackWindow= */ true,
1257                         shouldCheckFilterSize)) {
1258                     LoggerFactory.getMeasurementLogger()
1259                             .d(
1260                                     "Aggregate deduplication key: "
1261                                             + i
1262                                             + " contains invalid not filters.");
1263                     return Optional.empty();
1264                 }
1265                 aggregateDedupKey.put(FilterContract.NOT_FILTERS, notFilters);
1266             }
1267             validAggregateDeduplicationKeys.put(aggregateDedupKey);
1268         }
1269         return Optional.of(validAggregateDeduplicationKeys.toString());
1270     }
1271 
extractValidAttributionConfigs(JSONArray attributionConfigsArray)1272     private String extractValidAttributionConfigs(JSONArray attributionConfigsArray)
1273             throws JSONException {
1274         JSONArray validAttributionConfigsArray = new JSONArray();
1275         for (int i = 0; i < attributionConfigsArray.length(); i++) {
1276             AttributionConfig attributionConfig =
1277                     new AttributionConfig.Builder(attributionConfigsArray.getJSONObject(i), mFlags)
1278                             .build();
1279             validAttributionConfigsArray.put(attributionConfig.serializeAsJson(mFlags));
1280         }
1281         return validAttributionConfigsArray.toString();
1282     }
1283 
isValidXNetworkKeyMapping(JSONObject adTechBitMapping)1284     private boolean isValidXNetworkKeyMapping(JSONObject adTechBitMapping) throws JSONException {
1285         // TODO: Might need to add logic for keys' and values' lengths.
1286         Iterator<String> keys = adTechBitMapping.keys();
1287         while (keys.hasNext()) {
1288             String key = keys.next();
1289             String value = adTechBitMapping.optString(key);
1290             if (value == null || !value.startsWith("0x")) {
1291                 return false;
1292             }
1293         }
1294         return true;
1295     }
1296 
getAttributionDestination( Uri destination, AsyncRegistration.RegistrationType registrationType)1297     private static Uri getAttributionDestination(
1298             Uri destination, AsyncRegistration.RegistrationType registrationType) {
1299         return registrationType == AsyncRegistration.RegistrationType.APP_TRIGGER
1300                 ? BaseUriExtractor.getBaseUri(destination)
1301                 : destination;
1302     }
1303 
1304     // TODO(b/311183933): Remove passed in Context from static method.
1305     @SuppressWarnings("AvoidStaticContext")
getOdpDelegationManager(Context context, Flags flags)1306     private static IOdpDelegationWrapper getOdpDelegationManager(Context context, Flags flags) {
1307         if (!SdkLevel.isAtLeastT() || !flags.getMeasurementEnableOdpWebTriggerRegistration()) {
1308             return new NoOdpDelegationWrapper();
1309         }
1310 
1311         OnDevicePersonalizationSystemEventManager odpSystemEventManager = null;
1312         try {
1313             odpSystemEventManager =
1314                     context.getSystemService(OnDevicePersonalizationSystemEventManager.class);
1315         } catch (Exception e) {
1316             LoggerFactory.getMeasurementLogger().d(e, "getOdpDelegationManager: Unknown Exception");
1317             ErrorLogUtil.e(
1318                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_REGISTRATION_ODP_GET_MANAGER_ERROR,
1319                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT);
1320         }
1321         return (odpSystemEventManager != null)
1322                 ? new OdpDelegationWrapperImpl(odpSystemEventManager)
1323                 : new NoOdpDelegationWrapper();
1324     }
1325 
logInvalidTriggerField(String field, String enrollmentId, String triggerId)1326     private void logInvalidTriggerField(String field, String enrollmentId, String triggerId) {
1327         LoggerFactory.getMeasurementLogger()
1328                 .d(
1329                         "AsyncTriggerFetcher: Invalid %s. Enrollment ID: %s, Trigger ID: %s",
1330                         field, enrollmentId, triggerId);
1331     }
1332 
1333     private interface TriggerHeaderContract {
1334         String HEADER_ATTRIBUTION_REPORTING_REGISTER_TRIGGER =
1335                 "Attribution-Reporting-Register-Trigger";
1336         // Header for verbose debug reports.
1337         String HEADER_ATTRIBUTION_REPORTING_INFO = "Attribution-Reporting-Info";
1338         String ATTRIBUTION_CONFIG = "attribution_config";
1339         String EVENT_TRIGGER_DATA = "event_trigger_data";
1340         String AGGREGATABLE_TRIGGER_DATA = "aggregatable_trigger_data";
1341         String AGGREGATABLE_VALUES = "aggregatable_values";
1342         String AGGREGATABLE_DEDUPLICATION_KEYS = "aggregatable_deduplication_keys";
1343         String DEBUG_KEY = "debug_key";
1344         String DEBUG_REPORTING = "debug_reporting";
1345         String X_NETWORK_KEY_MAPPING = "x_network_key_mapping";
1346         String DEBUG_JOIN_KEY = "debug_join_key";
1347         String DEBUG_AD_ID = "debug_ad_id";
1348         String AGGREGATION_COORDINATOR_ORIGIN = "aggregation_coordinator_origin";
1349         String AGGREGATABLE_SOURCE_REGISTRATION_TIME = "aggregatable_source_registration_time";
1350         String TRIGGER_CONTEXT_ID = "trigger_context_id";
1351         String ATTRIBUTION_SCOPES = "attribution_scopes";
1352         String AGGREGATABLE_DEBUG_REPORTING = "aggregatable_debug_reporting";
1353         String AGGREGATABLE_FILTERING_ID_MAX_BYTES = "aggregatable_filtering_id_max_bytes";
1354     }
1355 
1356     private interface OdpTriggerHeaderContract {
1357         String HEADER_ODP_REGISTER_TRIGGER = "Odp-Register-Trigger";
1358     }
1359 }
1360