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