• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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