1 /* 2 * Copyright (C) 2023 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.adselection.encryption; 18 19 import static com.android.adservices.service.stats.AdsRelevanceStatusUtils.BACKGROUND_KEY_FETCH_STATUS_NO_OP; 20 import static com.android.adservices.service.stats.AdsRelevanceStatusUtils.BACKGROUND_KEY_FETCH_STATUS_REFRESH_KEYS_INITIATED; 21 import static com.android.adservices.service.stats.AdsRelevanceStatusUtils.SERVER_AUCTION_COORDINATOR_SOURCE_API; 22 import static com.android.adservices.service.stats.AdsRelevanceStatusUtils.SERVER_AUCTION_COORDINATOR_SOURCE_DEFAULT; 23 import static com.android.adservices.service.stats.AdsRelevanceStatusUtils.SERVER_AUCTION_KEY_FETCH_SOURCE_BACKGROUND_FETCH; 24 25 import android.annotation.NonNull; 26 import android.content.Context; 27 import android.net.Uri; 28 29 import com.android.adservices.LoggerFactory; 30 import com.android.adservices.concurrency.AdServicesExecutors; 31 import com.android.adservices.data.adselection.DBEncryptionKey; 32 import com.android.adservices.service.Flags; 33 import com.android.adservices.service.FlagsFactory; 34 import com.android.adservices.service.adselection.MultiCloudSupportStrategyFactory; 35 import com.android.adservices.service.common.AllowLists; 36 import com.android.adservices.service.common.SingletonRunner; 37 import com.android.adservices.service.common.httpclient.AdServicesHttpsClient; 38 import com.android.adservices.service.devapi.DevContext; 39 import com.android.adservices.service.stats.AdServicesLogger; 40 import com.android.adservices.service.stats.AdServicesLoggerImpl; 41 import com.android.adservices.service.stats.AdsRelevanceStatusUtils; 42 import com.android.adservices.service.stats.FetchProcessLogger; 43 import com.android.adservices.service.stats.ServerAuctionBackgroundKeyFetchScheduledStats; 44 import com.android.adservices.service.stats.ServerAuctionKeyFetchExecutionLoggerFactory; 45 import com.android.internal.annotations.VisibleForTesting; 46 47 import com.google.common.base.Strings; 48 import com.google.common.util.concurrent.ExecutionSequencer; 49 import com.google.common.util.concurrent.FluentFuture; 50 import com.google.common.util.concurrent.Futures; 51 import com.google.common.util.concurrent.ListenableFuture; 52 53 import java.time.Clock; 54 import java.time.Instant; 55 import java.util.ArrayList; 56 import java.util.List; 57 import java.util.Objects; 58 import java.util.Set; 59 import java.util.concurrent.TimeUnit; 60 import java.util.function.Supplier; 61 import java.util.stream.Collectors; 62 import java.util.stream.Stream; 63 64 /** Worker instance for fetching encryption keys and persisting to DB. */ 65 public class BackgroundKeyFetchWorker { 66 private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); 67 public static final String JOB_DESCRIPTION = "Ad selection data encryption key fetch job"; 68 private static final Object SINGLETON_LOCK = new Object(); 69 private static volatile BackgroundKeyFetchWorker sBackgroundKeyFetchWorker; 70 private final ProtectedServersEncryptionConfigManagerBase mKeyConfigManager; 71 private final DevContext mDevContext; 72 private final Flags mFlags; 73 private final Clock mClock; 74 private final AdServicesLogger mAdServicesLogger; 75 private final SingletonRunner<Void> mSingletonRunner = 76 new SingletonRunner<>(JOB_DESCRIPTION, this::doRun); 77 78 @VisibleForTesting BackgroundKeyFetchWorker( @onNull ProtectedServersEncryptionConfigManagerBase keyConfigManager, @NonNull DevContext devContext, @NonNull Flags flags, @NonNull Clock clock, @NonNull AdServicesLogger adServicesLogger)79 protected BackgroundKeyFetchWorker( 80 @NonNull ProtectedServersEncryptionConfigManagerBase keyConfigManager, 81 @NonNull DevContext devContext, 82 @NonNull Flags flags, 83 @NonNull Clock clock, 84 @NonNull AdServicesLogger adServicesLogger) { 85 Objects.requireNonNull(keyConfigManager); 86 Objects.requireNonNull(devContext); 87 Objects.requireNonNull(flags); 88 Objects.requireNonNull(clock); 89 Objects.requireNonNull(adServicesLogger); 90 mKeyConfigManager = keyConfigManager; 91 mDevContext = devContext; 92 mClock = clock; 93 mFlags = flags; 94 mAdServicesLogger = adServicesLogger; 95 } 96 97 /** 98 * Gets an instance of a {@link BackgroundKeyFetchWorker}. If an instance hasn't been 99 * initialized, a new singleton will be created and returned. 100 */ 101 @NonNull getInstance(@onNull Context context)102 public static BackgroundKeyFetchWorker getInstance(@NonNull Context context) { 103 Objects.requireNonNull(context); 104 105 if (sBackgroundKeyFetchWorker == null) { 106 synchronized (SINGLETON_LOCK) { 107 if (sBackgroundKeyFetchWorker == null) { 108 Flags flags = FlagsFactory.getFlags(); 109 AdServicesHttpsClient adServicesHttpsClient = 110 new AdServicesHttpsClient( 111 AdServicesExecutors.getBlockingExecutor(), 112 flags 113 .getFledgeAuctionServerBackgroundKeyFetchNetworkConnectTimeoutMs(), 114 flags 115 .getFledgeAuctionServerBackgroundKeyFetchNetworkReadTimeoutMs(), 116 flags.getFledgeAuctionServerBackgroundKeyFetchMaxResponseSizeB()); 117 ProtectedServersEncryptionConfigManagerBase configManager = 118 MultiCloudSupportStrategyFactory.getStrategy( 119 flags.getFledgeAuctionServerMultiCloudEnabled(), 120 flags.getFledgeAuctionServerCoordinatorUrlAllowlist()) 121 .getEncryptionConfigManager( 122 context, flags, adServicesHttpsClient); 123 // TODO (b/344636522): Derive DevContext from calling environment. 124 sBackgroundKeyFetchWorker = 125 new BackgroundKeyFetchWorker( 126 configManager, 127 DevContext.createForDevOptionsDisabled(), 128 flags, 129 Clock.systemUTC(), 130 AdServicesLoggerImpl.getInstance()); 131 } 132 } 133 } 134 return sBackgroundKeyFetchWorker; 135 } 136 getFlags()137 public Flags getFlags() { 138 return mFlags; 139 } 140 concatAbsentAndExpiredKeyTypes(Instant keyExpiryInstant)141 private Set<Integer> concatAbsentAndExpiredKeyTypes(Instant keyExpiryInstant) { 142 return Stream.concat( 143 mKeyConfigManager 144 .getExpiredAdSelectionEncryptionKeyTypes(keyExpiryInstant) 145 .stream(), 146 mKeyConfigManager.getAbsentAdSelectionEncryptionKeyTypes().stream()) 147 .collect(Collectors.toSet()); 148 } 149 getAbsentAndExpiredKeyTypes(Instant keyExpiryInstant)150 private FluentFuture<Set<Integer>> getAbsentAndExpiredKeyTypes(Instant keyExpiryInstant) { 151 return FluentFuture.from( 152 AdServicesExecutors.getBackgroundExecutor() 153 .submit(() -> concatAbsentAndExpiredKeyTypes(keyExpiryInstant))); 154 } 155 getExpiredKeyTypes(Instant keyExpiryInstant)156 private FluentFuture<Set<Integer>> getExpiredKeyTypes(Instant keyExpiryInstant) { 157 return FluentFuture.from( 158 AdServicesExecutors.getBackgroundExecutor() 159 .submit( 160 () -> 161 mKeyConfigManager.getExpiredAdSelectionEncryptionKeyTypes( 162 keyExpiryInstant))); 163 } 164 fetchNewKeys( Set<Integer> expiredKeyTypes, Instant keyExpiryInstant, Supplier<Boolean> shouldStop)165 private FluentFuture<Void> fetchNewKeys( 166 Set<Integer> expiredKeyTypes, Instant keyExpiryInstant, Supplier<Boolean> shouldStop) { 167 if (expiredKeyTypes.isEmpty()) { 168 if (mFlags.getFledgeAuctionServerKeyFetchMetricsEnabled()) { 169 mAdServicesLogger.logServerAuctionBackgroundKeyFetchScheduledStats( 170 ServerAuctionBackgroundKeyFetchScheduledStats.builder() 171 .setStatus(BACKGROUND_KEY_FETCH_STATUS_NO_OP) 172 .setCountAuctionUrls(0) 173 .setCountJoinUrls(0) 174 .build()); 175 } 176 177 return FluentFuture.from(Futures.immediateVoidFuture()) 178 .transform(ignored -> null, AdServicesExecutors.getLightWeightExecutor()); 179 } 180 181 List<ListenableFuture<List<DBEncryptionKey>>> keyFetchFutures = new ArrayList<>(); 182 int countAuctionUrls = 0; 183 int countJoinUrls = 0; 184 185 // Keys are fetched and persisted in sequence to prevent making multiple network 186 // calls in parallel. 187 ExecutionSequencer sequencer = ExecutionSequencer.create(); 188 ServerAuctionKeyFetchExecutionLoggerFactory serverAuctionKeyFetchExecutionLoggerFactory = 189 new ServerAuctionKeyFetchExecutionLoggerFactory( 190 com.android.adservices.shared.util.Clock.getInstance(), 191 mAdServicesLogger, 192 mFlags); 193 FetchProcessLogger keyFetchLogger = 194 serverAuctionKeyFetchExecutionLoggerFactory.getAdsRelevanceExecutionLogger(); 195 keyFetchLogger.setSource(SERVER_AUCTION_KEY_FETCH_SOURCE_BACKGROUND_FETCH); 196 197 if (mFlags.getFledgeAuctionServerBackgroundAuctionKeyFetchEnabled() 198 && expiredKeyTypes.contains( 199 AdSelectionEncryptionKey.AdSelectionEncryptionKeyType.AUCTION) 200 && !shouldStop.get()) { 201 202 boolean multicloudEnabled = mFlags.getFledgeAuctionServerMultiCloudEnabled(); 203 String allowlist = mFlags.getFledgeAuctionServerCoordinatorUrlAllowlist(); 204 205 if (multicloudEnabled && !Strings.isNullOrEmpty(allowlist)) { 206 List<String> allowedUrls = AllowLists.splitAllowList(allowlist); 207 countAuctionUrls = allowedUrls.size(); 208 keyFetchLogger.setCoordinatorSource(SERVER_AUCTION_COORDINATOR_SOURCE_API); 209 for (String coordinator : allowedUrls) { 210 keyFetchFutures.add( 211 fetchAndPersistAuctionKeys( 212 keyExpiryInstant, 213 sequencer, 214 Uri.parse(coordinator), 215 keyFetchLogger)); 216 } 217 } else { 218 String defaultUrl = mFlags.getFledgeAuctionServerAuctionKeyFetchUri(); 219 if (defaultUrl != null) { 220 countAuctionUrls = 1; 221 keyFetchLogger.setCoordinatorSource(SERVER_AUCTION_COORDINATOR_SOURCE_DEFAULT); 222 keyFetchFutures.add( 223 fetchAndPersistAuctionKeys( 224 keyExpiryInstant, 225 sequencer, 226 Uri.parse(defaultUrl), 227 keyFetchLogger)); 228 } 229 } 230 } 231 232 if (mFlags.getFledgeAuctionServerBackgroundJoinKeyFetchEnabled() 233 && expiredKeyTypes.contains( 234 AdSelectionEncryptionKey.AdSelectionEncryptionKeyType.JOIN) 235 && !shouldStop.get()) { 236 countJoinUrls = 1; 237 keyFetchLogger.setCoordinatorSource(SERVER_AUCTION_COORDINATOR_SOURCE_DEFAULT); 238 keyFetchFutures.add( 239 fetchAndPersistJoinKey(keyExpiryInstant, sequencer, keyFetchLogger)); 240 } 241 242 if (mFlags.getFledgeAuctionServerKeyFetchMetricsEnabled()) { 243 @AdsRelevanceStatusUtils.BackgroundKeyFetchStatus 244 int status = 245 countAuctionUrls + countJoinUrls > 0 246 ? BACKGROUND_KEY_FETCH_STATUS_REFRESH_KEYS_INITIATED 247 : BACKGROUND_KEY_FETCH_STATUS_NO_OP; 248 249 mAdServicesLogger.logServerAuctionBackgroundKeyFetchScheduledStats( 250 ServerAuctionBackgroundKeyFetchScheduledStats.builder() 251 .setStatus(status) 252 .setCountAuctionUrls(countAuctionUrls) 253 .setCountJoinUrls(countJoinUrls) 254 .build()); 255 } 256 257 return FluentFuture.from(Futures.allAsList(keyFetchFutures)) 258 .withTimeout( 259 mFlags.getFledgeAuctionServerBackgroundKeyFetchJobMaxRuntimeMs(), 260 TimeUnit.MILLISECONDS, 261 AdServicesExecutors.getScheduler()) 262 .transform(ignored -> null, AdServicesExecutors.getLightWeightExecutor()); 263 } 264 doRun(@onNull Supplier<Boolean> shouldStop)265 private FluentFuture<Void> doRun(@NonNull Supplier<Boolean> shouldStop) { 266 if (shouldStop.get()) { 267 sLogger.d("Stopping " + JOB_DESCRIPTION); 268 return FluentFuture.from(Futures.immediateVoidFuture()) 269 .transform(ignored -> null, AdServicesExecutors.getLightWeightExecutor()); 270 } 271 272 Instant currentInstant = mClock.instant(); 273 if (mFlags.getFledgeAuctionServerBackgroundKeyFetchOnEmptyDbAndInAdvanceEnabled()) { 274 long inAdvanceIntervalMs = 275 mFlags.getFledgeAuctionServerBackgroundKeyFetchInAdvanceIntervalMs(); 276 return getAbsentAndExpiredKeyTypes(currentInstant.plusMillis(inAdvanceIntervalMs)) 277 .transformAsync( 278 keyTypesToFetch -> 279 fetchNewKeys(keyTypesToFetch, currentInstant, shouldStop), 280 AdServicesExecutors.getBackgroundExecutor()); 281 } 282 283 return getExpiredKeyTypes(currentInstant) 284 .transformAsync( 285 expiredKeyTypes -> 286 fetchNewKeys(expiredKeyTypes, currentInstant, shouldStop), 287 AdServicesExecutors.getBackgroundExecutor()); 288 } 289 290 /** 291 * Runs the background key fetch job for Ad Selection Data, including persisting fetched key and 292 * removing expired keys. 293 * 294 * @return A future to be used to check when the task has completed. 295 */ runBackgroundKeyFetch()296 public FluentFuture<Void> runBackgroundKeyFetch() { 297 sLogger.d("Starting %s", JOB_DESCRIPTION); 298 return mSingletonRunner.runSingleInstance(); 299 } 300 301 /** Requests that any ongoing work be stopped gracefully and waits for work to be stopped. */ stopWork()302 public void stopWork() { 303 mSingletonRunner.stopWork(); 304 } 305 fetchAndPersistAuctionKeys( Instant keyExpiryInstant, ExecutionSequencer sequencer, Uri coordinatorUri, FetchProcessLogger keyFetchLogger)306 private ListenableFuture<List<DBEncryptionKey>> fetchAndPersistAuctionKeys( 307 Instant keyExpiryInstant, 308 ExecutionSequencer sequencer, 309 Uri coordinatorUri, 310 FetchProcessLogger keyFetchLogger) { 311 312 return sequencer.submitAsync( 313 () -> 314 mKeyConfigManager.fetchAndPersistActiveKeysOfType( 315 AdSelectionEncryptionKey.AdSelectionEncryptionKeyType.AUCTION, 316 keyExpiryInstant, 317 mFlags.getFledgeAuctionServerBackgroundKeyFetchJobMaxRuntimeMs(), 318 coordinatorUri, 319 mDevContext, 320 keyFetchLogger), 321 AdServicesExecutors.getBackgroundExecutor()); 322 } 323 fetchAndPersistJoinKey( Instant keyExpiryInstant, ExecutionSequencer sequencer, FetchProcessLogger keyFetchLogger)324 private ListenableFuture<List<DBEncryptionKey>> fetchAndPersistJoinKey( 325 Instant keyExpiryInstant, 326 ExecutionSequencer sequencer, 327 FetchProcessLogger keyFetchLogger) { 328 return sequencer.submitAsync( 329 () -> 330 mKeyConfigManager.fetchAndPersistActiveKeysOfType( 331 AdSelectionEncryptionKey.AdSelectionEncryptionKeyType.JOIN, 332 keyExpiryInstant, 333 mFlags.getFledgeAuctionServerBackgroundKeyFetchJobMaxRuntimeMs(), 334 null, 335 mDevContext, 336 keyFetchLogger), 337 AdServicesExecutors.getBackgroundExecutor()); 338 } 339 } 340