• 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_ATTRIBUTION_FILTERS;
19 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_BYTES_PER_ATTRIBUTION_AGGREGATE_KEY_ID;
20 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_BYTES_PER_ATTRIBUTION_FILTER_STRING;
21 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_FILTER_MAPS_PER_FILTER_SET;
22 import static com.android.adservices.service.measurement.SystemHealthParams.MAX_VALUES_PER_ATTRIBUTION_FILTER;
23 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_MEASUREMENT_REGISTRATIONS;
24 
25 import android.annotation.NonNull;
26 import android.net.Uri;
27 
28 import com.android.adservices.LogUtil;
29 import com.android.adservices.service.Flags;
30 import com.android.adservices.service.FlagsFactory;
31 import com.android.adservices.service.measurement.Source;
32 import com.android.adservices.service.measurement.util.AdIdEncryption;
33 import com.android.adservices.service.measurement.util.Web;
34 import com.android.adservices.service.stats.AdServicesLogger;
35 import com.android.adservices.service.stats.MeasurementRegistrationResponseStats;
36 
37 import org.json.JSONArray;
38 import org.json.JSONObject;
39 
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.Map;
45 
46 /**
47  * Common handling for Response Based Registration
48  *
49  * @hide
50  */
51 class FetcherUtil {
52     static final String REDIRECT_LIST_HEADER_KEY = "Attribution-Reporting-Redirect";
53     static final String REDIRECT_LOCATION_HEADER_KEY = "Location";
54 
55     /**
56      * Determine all redirects.
57      *
58      * <p>Generates a list of: (url, allows_regular_redirects) tuples. Returns true if all steps
59      * succeed. Returns false if there are any failures.
60      */
parseRedirects( @onNull Map<String, List<String>> headers)61     static Map<AsyncRegistration.RedirectType, List<Uri>> parseRedirects(
62             @NonNull Map<String, List<String>> headers) {
63         Map<AsyncRegistration.RedirectType, List<Uri>> uriMap = new HashMap<>();
64         uriMap.put(AsyncRegistration.RedirectType.LOCATION, parseLocationRedirects(headers));
65         uriMap.put(AsyncRegistration.RedirectType.LIST, parseListRedirects(headers));
66         return uriMap;
67     }
68 
69     /**
70      * Check HTTP response codes that indicate a redirect.
71      */
isRedirect(int responseCode)72     static boolean isRedirect(int responseCode) {
73         return (responseCode / 100) == 3;
74     }
75 
76     /**
77      * Check HTTP response code for success.
78      */
isSuccess(int responseCode)79     static boolean isSuccess(int responseCode) {
80         return (responseCode / 100) == 2;
81     }
82 
83     /**
84      * Validate aggregate key ID.
85      */
isValidAggregateKeyId(String id)86     static boolean isValidAggregateKeyId(String id) {
87         return id != null && id.getBytes().length <= MAX_BYTES_PER_ATTRIBUTION_AGGREGATE_KEY_ID;
88     }
89 
90     /** Validate aggregate deduplication key. */
isValidAggregateDeduplicationKey(String deduplicationKey)91     static boolean isValidAggregateDeduplicationKey(String deduplicationKey) {
92         if (deduplicationKey == null) {
93             return false;
94         }
95         try {
96             Long.parseUnsignedLong(deduplicationKey);
97         } catch (NumberFormatException exception) {
98             return false;
99         }
100         return true;
101     }
102 
103     /**
104      * Validate aggregate key-piece.
105      */
isValidAggregateKeyPiece(String keyPiece)106     static boolean isValidAggregateKeyPiece(String keyPiece) {
107         if (keyPiece == null) {
108             return false;
109         }
110         int length = keyPiece.getBytes().length;
111         // Key-piece is restricted to a maximum of 128 bits and the hex strings therefore have at
112         // most 32 digits.
113         return (keyPiece.startsWith("0x") || keyPiece.startsWith("0X"))
114                 && 2 < length && length < 35;
115     }
116 
117     /**
118      * Validate attribution filters JSONArray.
119      */
areValidAttributionFilters(@onNull JSONArray filterSet)120     static boolean areValidAttributionFilters(@NonNull JSONArray filterSet) {
121         if (filterSet.length() > MAX_FILTER_MAPS_PER_FILTER_SET) {
122             return false;
123         }
124         for (int i = 0; i < filterSet.length(); i++) {
125             if (!areValidAttributionFilters(filterSet.optJSONObject(i))) {
126                 return false;
127             }
128         }
129         return true;
130     }
131 
132     /**
133      * Validate attribution filters JSONObject.
134      */
areValidAttributionFilters(JSONObject filtersObj)135     static boolean areValidAttributionFilters(JSONObject filtersObj) {
136         if (filtersObj == null || filtersObj.length() > MAX_ATTRIBUTION_FILTERS) {
137             return false;
138         }
139         Iterator<String> keys = filtersObj.keys();
140         while (keys.hasNext()) {
141             String key = keys.next();
142             if (key.getBytes().length > MAX_BYTES_PER_ATTRIBUTION_FILTER_STRING) {
143                 return false;
144             }
145             JSONArray values = filtersObj.optJSONArray(key);
146             if (values == null || values.length() > MAX_VALUES_PER_ATTRIBUTION_FILTER) {
147                 return false;
148             }
149             for (int i = 0; i < values.length(); i++) {
150                 String value = values.optString(i);
151                 if (value == null
152                         || value.getBytes().length > MAX_BYTES_PER_ATTRIBUTION_FILTER_STRING) {
153                     return false;
154                 }
155             }
156         }
157         return true;
158     }
159 
emitHeaderMetrics( Flags flags, AdServicesLogger logger, AsyncRegistration asyncRegistration, AsyncFetchStatus asyncFetchStatus)160     static void emitHeaderMetrics(
161             Flags flags,
162             AdServicesLogger logger,
163             AsyncRegistration asyncRegistration,
164             AsyncFetchStatus asyncFetchStatus) {
165         long headerSize = 0;
166         if (asyncFetchStatus.getResponseSize().isPresent()) {
167             headerSize = asyncFetchStatus.getResponseSize().get();
168         }
169         long maxSize = flags.getMaxResponseBasedRegistrationPayloadSizeBytes();
170         String adTechDomain = null;
171 
172         if (headerSize > maxSize) {
173             adTechDomain =
174                     Web.topPrivateDomainAndScheme(asyncRegistration.getRegistrationUri())
175                             .map(Uri::toString)
176                             .orElse(null);
177         }
178 
179         logger.logMeasurementRegistrationsResponseSize(
180                 new MeasurementRegistrationResponseStats.Builder(
181                                 AD_SERVICES_MEASUREMENT_REGISTRATIONS,
182                                 getRegistrationType(asyncRegistration),
183                                 headerSize,
184                                 getSourceType(asyncRegistration),
185                                 getSurfaceType(asyncRegistration),
186                                 getStatus(asyncFetchStatus),
187                                 getFailureType(asyncFetchStatus),
188                                 asyncFetchStatus.getRegistrationDelay().get())
189                         .setAdTechDomain(adTechDomain)
190                         .build());
191     }
192 
parseListRedirects(Map<String, List<String>> headers)193     private static List<Uri> parseListRedirects(Map<String, List<String>> headers) {
194         List<Uri> redirects = new ArrayList<>();
195         List<String> field = headers.get(REDIRECT_LIST_HEADER_KEY);
196         int maxRedirects = FlagsFactory.getFlags().getMeasurementMaxRegistrationRedirects();
197         if (field != null) {
198             for (int i = 0; i < Math.min(field.size(), maxRedirects); i++) {
199                 redirects.add(Uri.parse(field.get(i)));
200             }
201         }
202         return redirects;
203     }
204 
parseLocationRedirects(Map<String, List<String>> headers)205     private static List<Uri> parseLocationRedirects(Map<String, List<String>> headers) {
206         List<Uri> redirects = new ArrayList<>();
207         List<String> field = headers.get(REDIRECT_LOCATION_HEADER_KEY);
208         if (field != null && !field.isEmpty()) {
209             redirects.add(Uri.parse(field.get(0)));
210             if (field.size() > 1) {
211                 LogUtil.d("Expected one Location redirect only, others ignored!");
212             }
213         }
214         return redirects;
215     }
216 
calculateHeadersCharactersLength(Map<String, List<String>> headers)217     public static long calculateHeadersCharactersLength(Map<String, List<String>> headers) {
218         long size = 0;
219         for (String headerKey : headers.keySet()) {
220             if (headerKey != null) {
221                 size = size + headerKey.length();
222                 List<String> headerValues = headers.get(headerKey);
223                 if (headerValues != null) {
224                     for (String headerValue : headerValues) {
225                         size = size + headerValue.length();
226                     }
227                 }
228             }
229         }
230 
231         return size;
232     }
233 
getEncryptedPlatformAdIdIfPresent( AsyncRegistration asyncRegistration, String enrollmentId)234     public static String getEncryptedPlatformAdIdIfPresent(
235             AsyncRegistration asyncRegistration, String enrollmentId) {
236         if (asyncRegistration.isAppRequest()
237                 && asyncRegistration.hasAdIdPermission()
238                 && asyncRegistration.getPlatformAdId() != null) {
239             return AdIdEncryption.encryptAdIdAndEnrollmentSha256(
240                     asyncRegistration.getPlatformAdId(), enrollmentId);
241         } else {
242             return null;
243         }
244     }
245 
getRegistrationType(AsyncRegistration asyncRegistration)246     private static int getRegistrationType(AsyncRegistration asyncRegistration) {
247         if (asyncRegistration.getType() == AsyncRegistration.RegistrationType.APP_SOURCE
248                 || asyncRegistration.getType() == AsyncRegistration.RegistrationType.WEB_SOURCE) {
249             return RegistrationEnumsValues.TYPE_SOURCE;
250         } else if (asyncRegistration.getType() == AsyncRegistration.RegistrationType.APP_TRIGGER
251                 || asyncRegistration.getType() == AsyncRegistration.RegistrationType.WEB_TRIGGER) {
252             return RegistrationEnumsValues.TYPE_TRIGGER;
253         } else {
254             return RegistrationEnumsValues.TYPE_UNKNOWN;
255         }
256     }
257 
getSourceType(AsyncRegistration asyncRegistration)258     private static int getSourceType(AsyncRegistration asyncRegistration) {
259         if (asyncRegistration.getSourceType() == Source.SourceType.EVENT) {
260             return RegistrationEnumsValues.SOURCE_TYPE_EVENT;
261         } else if (asyncRegistration.getSourceType() == Source.SourceType.NAVIGATION) {
262             return RegistrationEnumsValues.SOURCE_TYPE_NAVIGATION;
263         } else {
264             return RegistrationEnumsValues.SOURCE_TYPE_UNKNOWN;
265         }
266     }
267 
getSurfaceType(AsyncRegistration asyncRegistration)268     private static int getSurfaceType(AsyncRegistration asyncRegistration) {
269         if (asyncRegistration.getType() == AsyncRegistration.RegistrationType.APP_SOURCE
270                 || asyncRegistration.getType() == AsyncRegistration.RegistrationType.APP_TRIGGER) {
271             return RegistrationEnumsValues.SURFACE_TYPE_APP;
272         } else if (asyncRegistration.getType() == AsyncRegistration.RegistrationType.WEB_SOURCE
273                 || asyncRegistration.getType() == AsyncRegistration.RegistrationType.WEB_TRIGGER) {
274             return RegistrationEnumsValues.SURFACE_TYPE_WEB;
275         } else {
276             return RegistrationEnumsValues.SURFACE_TYPE_UNKNOWN;
277         }
278     }
279 
getStatus(AsyncFetchStatus asyncFetchStatus)280     private static int getStatus(AsyncFetchStatus asyncFetchStatus) {
281         if (asyncFetchStatus.getEntityStatus() == AsyncFetchStatus.EntityStatus.SUCCESS
282                 || (asyncFetchStatus.getResponseStatus() == AsyncFetchStatus.ResponseStatus.SUCCESS
283                         && (asyncFetchStatus.getEntityStatus()
284                                         == AsyncFetchStatus.EntityStatus.UNKNOWN
285                                 || asyncFetchStatus.getEntityStatus()
286                                         == AsyncFetchStatus.EntityStatus.HEADER_MISSING))) {
287             // successful source/trigger fetching/parsing and successful redirects (with no header)
288             return RegistrationEnumsValues.STATUS_SUCCESS;
289         } else if (asyncFetchStatus.getEntityStatus() == AsyncFetchStatus.EntityStatus.UNKNOWN
290                 && asyncFetchStatus.getResponseStatus()
291                         == AsyncFetchStatus.ResponseStatus.UNKNOWN) {
292             return RegistrationEnumsValues.STATUS_UNKNOWN;
293         } else {
294             return RegistrationEnumsValues.STATUS_FAILURE;
295         }
296     }
297 
getFailureType(AsyncFetchStatus asyncFetchStatus)298     private static int getFailureType(AsyncFetchStatus asyncFetchStatus) {
299         if (asyncFetchStatus.getResponseStatus()
300                         == AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE
301                 || asyncFetchStatus.getResponseStatus()
302                         == AsyncFetchStatus.ResponseStatus.NETWORK_ERROR
303                 || asyncFetchStatus.getResponseStatus()
304                         == AsyncFetchStatus.ResponseStatus.INVALID_URL) {
305             return RegistrationEnumsValues.FAILURE_TYPE_NETWORK;
306         } else if (asyncFetchStatus.getEntityStatus()
307                 == AsyncFetchStatus.EntityStatus.INVALID_ENROLLMENT) {
308             return RegistrationEnumsValues.FAILURE_TYPE_ENROLLMENT;
309         } else if (asyncFetchStatus.getEntityStatus()
310                         == AsyncFetchStatus.EntityStatus.VALIDATION_ERROR
311                 || asyncFetchStatus.getEntityStatus() == AsyncFetchStatus.EntityStatus.PARSING_ERROR
312                 || asyncFetchStatus.getEntityStatus()
313                         == AsyncFetchStatus.EntityStatus.HEADER_MISSING
314                 || asyncFetchStatus.getEntityStatus()
315                         == AsyncFetchStatus.EntityStatus.HEADER_ERROR) {
316             return RegistrationEnumsValues.FAILURE_TYPE_PARSING;
317         } else if (asyncFetchStatus.getEntityStatus()
318                 == AsyncFetchStatus.EntityStatus.STORAGE_ERROR) {
319             return RegistrationEnumsValues.FAILURE_TYPE_STORAGE;
320         } else if (asyncFetchStatus.isRedirectError()) {
321             return RegistrationEnumsValues.FAILURE_TYPE_REDIRECT;
322         } else {
323             return RegistrationEnumsValues.FAILURE_TYPE_UNKNOWN;
324         }
325     }
326     /** AdservicesMeasurementRegistrations atom enum values. */
327     public interface RegistrationEnumsValues {
328         int TYPE_UNKNOWN = 0;
329         int TYPE_SOURCE = 1;
330         int TYPE_TRIGGER = 2;
331         int SOURCE_TYPE_UNKNOWN = 0;
332         int SOURCE_TYPE_EVENT = 1;
333         int SOURCE_TYPE_NAVIGATION = 2;
334         int SURFACE_TYPE_UNKNOWN = 0;
335         int SURFACE_TYPE_WEB = 1;
336         int SURFACE_TYPE_APP = 2;
337         int STATUS_UNKNOWN = 0;
338         int STATUS_SUCCESS = 1;
339         int STATUS_FAILURE = 2;
340         int FAILURE_TYPE_UNKNOWN = 0;
341         int FAILURE_TYPE_PARSING = 1;
342         int FAILURE_TYPE_NETWORK = 2;
343         int FAILURE_TYPE_ENROLLMENT = 3;
344         int FAILURE_TYPE_REDIRECT = 4;
345         int FAILURE_TYPE_STORAGE = 5;
346     }
347 }
348