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