• 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 
17 package com.android.adservices.service.common;
18 
19 import android.annotation.NonNull;
20 import android.os.Binder;
21 import android.util.Pair;
22 
23 import com.android.adservices.service.Flags;
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import com.google.common.util.concurrent.RateLimiter;
27 
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.Objects;
31 import java.util.concurrent.ConcurrentHashMap;
32 
33 /** Class to throttle PPAPI requests. */
34 public class Throttler {
35     // Enum for each PP API or entry point that will be throttled.
36     public enum ApiKey {
37         UNKNOWN,
38 
39         // Key to throttle AdId API, based on app package name.
40         ADID_API_APP_PACKAGE_NAME,
41 
42         // Key to throttle AppSetId API, based on app package name.
43         APPSETID_API_APP_PACKAGE_NAME,
44 
45         // Key to throttle Join Custom Audience API
46         FLEDGE_API_JOIN_CUSTOM_AUDIENCE,
47 
48         // Key to throttle Leave Custom Audience API
49         FLEDGE_API_LEAVE_CUSTOM_AUDIENCE,
50 
51         // Key to throttle Report impressions API
52         FLEDGE_API_REPORT_IMPRESSIONS,
53 
54         // Key to throttle Report impressions API
55         FLEDGE_API_REPORT_INTERACTION,
56 
57         // Key to throttle Select Ads API
58         FLEDGE_API_SELECT_ADS,
59 
60         // Key to throttle Set App Install Advertisers API
61         FLEDGE_API_SET_APP_INSTALL_ADVERTISERS,
62 
63         // Key to throttle FLEDGE updateAdCounterHistogram API
64         FLEDGE_API_UPDATE_AD_COUNTER_HISTOGRAM,
65 
66         // Key to throttle Measurement Deletion Registration API
67         MEASUREMENT_API_DELETION_REGISTRATION,
68 
69         // Key to throttle Measurement Register Source API
70         MEASUREMENT_API_REGISTER_SOURCE,
71 
72         // Key to throttle Measurement Register Trigger API
73         MEASUREMENT_API_REGISTER_TRIGGER,
74 
75         // Key to throttle Measurement Register Web Source API
76         MEASUREMENT_API_REGISTER_WEB_SOURCE,
77 
78         // Key to throttle Measurement Register Web Trigger API
79         MEASUREMENT_API_REGISTER_WEB_TRIGGER,
80 
81         // Key to throttle Topics API based on the App Package Name.
82         TOPICS_API_APP_PACKAGE_NAME,
83 
84         // Key to throttle Topics API based on the Sdk Name.
85         TOPICS_API_SDK_NAME,
86     }
87 
88     private static volatile Throttler sSingleton;
89     private static final double DEFAULT_RATE_LIMIT = 1d;
90 
91     // A Map from a Pair<ApiKey, Requester> to its RateLimiter.
92     // The Requester could be a SdkName or an AppPackageName depending on the rate limiting needs.
93     // Example Pair<TOPICS_API, "SomeSdkName">, Pair<TOPICS_API, "SomePackageName">.
94     private final ConcurrentHashMap<Pair<ApiKey, String>, RateLimiter> mSdkRateLimitMap =
95             new ConcurrentHashMap<>();
96 
97     // Used as a configuration to determine the rate limit per API
98     // Example:
99     // - TOPICS_API_SDK_NAME, 1 request per second
100     // - MEASUREMENT_API_REGISTER_SOURCE, 5 requests per second
101     private final Map<ApiKey, Double> mRateLimitPerApiMap = new HashMap<>();
102 
103     /** Returns the singleton instance of the Throttler. */
104     @NonNull
getInstance(@onNull Flags flags)105     public static Throttler getInstance(@NonNull Flags flags) {
106         Objects.requireNonNull(flags);
107         synchronized (Throttler.class) {
108             if (null == sSingleton) {
109                 // Clearing calling identity to check device config permission read by flags on the
110                 // local process and not on the process called. Once the device configs are read,
111                 // restore calling identity.
112                 final long token = Binder.clearCallingIdentity();
113                 sSingleton = new Throttler(flags);
114                 Binder.restoreCallingIdentity(token);
115             }
116             return sSingleton;
117         }
118     }
119 
120     @VisibleForTesting
Throttler(Flags flags)121     Throttler(Flags flags) {
122         setRateLimitPerApiMap(flags);
123     }
124 
125     /**
126      * The throttler is a Singleton and does not allow changing the rate limits once initialised,
127      * therefore it is not feasible to test different throttling policies across tests without
128      * destroying the previous instance. Intended to be used in test cases only.
129      */
130     @VisibleForTesting
destroyExistingThrottler()131     public static void destroyExistingThrottler() {
132         sSingleton = null;
133     }
134 
135     /**
136      * Acquires a permit for an API and a Requester if it can be acquired immediately without delay.
137      * Example: {@code tryAcquire(TOPICS_API, "SomeSdkName") }
138      *
139      * @return {@code true} if the permit was acquired, {@code false} otherwise
140      */
tryAcquire(ApiKey apiKey, String requester)141     public boolean tryAcquire(ApiKey apiKey, String requester) {
142         // Negative Permits Per Second turns off rate limiting.
143         final double permitsPerSecond =
144                 mRateLimitPerApiMap.getOrDefault(apiKey, DEFAULT_RATE_LIMIT);
145         if (permitsPerSecond <= 0) {
146             return true;
147         }
148 
149         final RateLimiter rateLimiter =
150                 mSdkRateLimitMap.computeIfAbsent(
151                         Pair.create(apiKey, requester), ignored -> create(permitsPerSecond));
152 
153         return rateLimiter.tryAcquire();
154     }
155 
156     /** Configures permits per second per {@link ApiKey} */
setRateLimitPerApiMap(Flags flags)157     private void setRateLimitPerApiMap(Flags flags) {
158         final double defaultPermitsPerSecond = flags.getSdkRequestPermitsPerSecond();
159         final double adIdPermitsPerSecond = flags.getAdIdRequestPermitsPerSecond();
160         final double appSetIdPermitsPerSecond = flags.getAppSetIdRequestPermitsPerSecond();
161         final double registerSource = flags.getMeasurementRegisterSourceRequestPermitsPerSecond();
162         final double registerWebSource =
163                 flags.getMeasurementRegisterWebSourceRequestPermitsPerSecond();
164         final double topicsApiAppRequestPermitsPerSecond =
165                 flags.getTopicsApiAppRequestPermitsPerSecond();
166         final double topicsApiSdkRequestPermitsPerSecond =
167                 flags.getTopicsApiSdkRequestPermitsPerSecond();
168 
169         mRateLimitPerApiMap.put(ApiKey.UNKNOWN, defaultPermitsPerSecond);
170 
171         mRateLimitPerApiMap.put(ApiKey.ADID_API_APP_PACKAGE_NAME, adIdPermitsPerSecond);
172         mRateLimitPerApiMap.put(ApiKey.APPSETID_API_APP_PACKAGE_NAME, appSetIdPermitsPerSecond);
173 
174         mRateLimitPerApiMap.put(ApiKey.FLEDGE_API_JOIN_CUSTOM_AUDIENCE, defaultPermitsPerSecond);
175         mRateLimitPerApiMap.put(ApiKey.FLEDGE_API_LEAVE_CUSTOM_AUDIENCE, defaultPermitsPerSecond);
176         mRateLimitPerApiMap.put(ApiKey.FLEDGE_API_REPORT_IMPRESSIONS, defaultPermitsPerSecond);
177         mRateLimitPerApiMap.put(ApiKey.FLEDGE_API_REPORT_INTERACTION, defaultPermitsPerSecond);
178         mRateLimitPerApiMap.put(ApiKey.FLEDGE_API_SELECT_ADS, defaultPermitsPerSecond);
179         mRateLimitPerApiMap.put(
180                 ApiKey.FLEDGE_API_UPDATE_AD_COUNTER_HISTOGRAM, defaultPermitsPerSecond);
181 
182         mRateLimitPerApiMap.put(
183                 ApiKey.MEASUREMENT_API_DELETION_REGISTRATION, defaultPermitsPerSecond);
184         mRateLimitPerApiMap.put(ApiKey.MEASUREMENT_API_REGISTER_SOURCE, registerSource);
185         mRateLimitPerApiMap.put(ApiKey.MEASUREMENT_API_REGISTER_TRIGGER, defaultPermitsPerSecond);
186         mRateLimitPerApiMap.put(ApiKey.MEASUREMENT_API_REGISTER_WEB_SOURCE, registerWebSource);
187         mRateLimitPerApiMap.put(
188                 ApiKey.MEASUREMENT_API_REGISTER_WEB_TRIGGER, defaultPermitsPerSecond);
189 
190         mRateLimitPerApiMap.put(
191                 ApiKey.TOPICS_API_APP_PACKAGE_NAME, topicsApiAppRequestPermitsPerSecond);
192         mRateLimitPerApiMap.put(ApiKey.TOPICS_API_SDK_NAME, topicsApiSdkRequestPermitsPerSecond);
193     }
194 
195     /**
196      * Creates a Burst RateLimiter. This is a workaround since Guava does not support RateLimiter
197      * with initial Burst.
198      *
199      * <p>The RateLimiter is created with {@link Double#POSITIVE_INFINITY} to open all permit slots
200      * immediately. It is immediately overridden to the expected rate based on the permitsPerSecond
201      * parameter. Then {@link RateLimiter#tryAcquire()} is called to use the first acquisition so
202      * the expected bursting rate could kick in on the following calls. This flow enables initial
203      * bursting, multiple simultaneous permits would be acquired as soon as RateLimiter is created.
204      * Otherwise, if only {@link RateLimiter#create(double)} is called, after the 1st call
205      * subsequent request would have to be spread out evenly over 1 second.
206      */
create(double permitsPerSecond)207     private RateLimiter create(double permitsPerSecond) {
208         RateLimiter rateLimiter = RateLimiter.create(Double.POSITIVE_INFINITY);
209         rateLimiter.setRate(permitsPerSecond);
210         boolean unused = rateLimiter.tryAcquire();
211         return rateLimiter;
212     }
213 }
214