1 /* 2 * Copyright (C) 2025 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.measurement.registration; 18 19 import android.annotation.NonNull; 20 import android.net.Uri; 21 import android.util.Pair; 22 23 import com.android.adservices.LoggerFactory; 24 import com.android.adservices.data.measurement.DatastoreException; 25 import com.android.adservices.data.measurement.IMeasurementDao; 26 import com.android.adservices.service.Flags; 27 import com.android.adservices.service.FlagsFactory; 28 import com.android.adservices.service.common.WebAddresses; 29 import com.android.adservices.service.measurement.EventSurfaceType; 30 import com.android.adservices.service.measurement.Source; 31 import com.android.adservices.service.measurement.reporting.DebugReportApi; 32 import com.android.adservices.service.measurement.util.BaseUriExtractor; 33 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Optional; 37 import java.util.Set; 38 39 /** Validation for inserting source to database */ 40 public class SourceEligibilityChecker { 41 private final Flags mFlags; 42 private final DebugReportApi mDebugReportApi; 43 SourceEligibilityChecker(Flags flags, DebugReportApi debugReportApi)44 public SourceEligibilityChecker(Flags flags, DebugReportApi debugReportApi) { 45 mFlags = flags; 46 mDebugReportApi = debugReportApi; 47 } 48 49 enum InsertSourcePermission { 50 NOT_ALLOWED(false), 51 ALLOWED(true), 52 ALLOWED_FIFO_SUCCESS(true); 53 54 private final boolean mIsAllowed; 55 InsertSourcePermission(boolean isAllowed)56 InsertSourcePermission(boolean isAllowed) { 57 mIsAllowed = isAllowed; 58 } 59 isAllowed()60 public boolean isAllowed() { 61 return mIsAllowed; 62 } 63 } 64 65 /** 66 * Determines the permission for inserting a source into the database. 67 * 68 * @param source incoming {@link Source} 69 * @param topOrigin a {@link Uri} 70 * @param publisherType represents an event surface type 71 * @param dao a {@link IMeasurementDao} 72 * @param asyncFetchStatus a {@link AsyncFetchStatus}, stores Ad Tech server status 73 * @param adrTypes a set of types for aggregate debug reporting 74 * @return a {@link InsertSourcePermission}, indicating whether the source can be inserted 75 */ isAllowedToInsert( Source source, Uri topOrigin, @EventSurfaceType int publisherType, IMeasurementDao dao, AsyncFetchStatus asyncFetchStatus, Set<DebugReportApi.Type> adrTypes)76 public InsertSourcePermission isAllowedToInsert( 77 Source source, 78 Uri topOrigin, 79 @EventSurfaceType int publisherType, 80 IMeasurementDao dao, 81 AsyncFetchStatus asyncFetchStatus, 82 Set<DebugReportApi.Type> adrTypes) 83 throws DatastoreException { 84 // Do not persist the navigation source if the same reporting origin has been registered 85 // for the registration. 86 if (isNavigationOriginAlreadyRegisteredForRegistration(source, dao)) { 87 LoggerFactory.getMeasurementLogger() 88 .d( 89 "storeSource (FAILURE): Duplicate reporting origin in registration" 90 + " sequence. Enrollment ID: %s, Source ID: %s, Source Event ID:" 91 + " %s", 92 source.getEnrollmentId(), source.getId(), source.getEventId()); 93 return InsertSourcePermission.NOT_ALLOWED; 94 } 95 long windowStartTime = 96 source.getEventTime() - mFlags.getMeasurementRateLimitWindowMilliseconds(); 97 Optional<Uri> publisher = getTopLevelPublisher(topOrigin, publisherType); 98 if (publisher.isEmpty()) { 99 LoggerFactory.getMeasurementLogger() 100 .d( 101 "storeSource (FAILURE): getTopLevelPublisher failed. topOrigin: %s," 102 + " Enrollment ID: %s, Source ID: %s, Source Event ID: %s", 103 topOrigin, 104 source.getEnrollmentId(), 105 source.getId(), 106 source.getEventId()); 107 return InsertSourcePermission.NOT_ALLOWED; 108 } 109 long numOfSourcesPerPublisher = 110 dao.getNumSourcesPerPublisher( 111 BaseUriExtractor.getBaseUri(topOrigin), publisherType); 112 if (numOfSourcesPerPublisher >= mFlags.getMeasurementMaxSourcesPerPublisher()) { 113 LoggerFactory.getMeasurementLogger() 114 .d( 115 "storeSource (FAILURE): Reached limit of %s sources for publisher - %s." 116 + " Enrollment ID: %s, Source ID: %s, Source Event ID: %s", 117 mFlags.getMeasurementMaxSourcesPerPublisher(), 118 publisher, 119 source.getEnrollmentId(), 120 source.getId(), 121 source.getEventId()); 122 mDebugReportApi.scheduleSourceReport( 123 source, 124 DebugReportApi.Type.SOURCE_STORAGE_LIMIT, 125 Map.of(DebugReportApi.Body.LIMIT, String.valueOf(numOfSourcesPerPublisher)), 126 dao); 127 adrTypes.add(DebugReportApi.Type.SOURCE_STORAGE_LIMIT); 128 return InsertSourcePermission.NOT_ALLOWED; 129 } 130 131 // Blocks ad-techs to register multiple sources with various destinations in a short window 132 // (per minute) 133 int destinationsPerMinuteRateLimit = 134 mFlags.getMeasurementMaxDestPerPublisherXEnrollmentPerRateLimitWindow(); 135 if (mFlags.getMeasurementEnableDestinationRateLimit() 136 && sourceExceedsTimeBasedDestinationLimits( 137 source, 138 publisher.get(), 139 publisherType, 140 mFlags.getMeasurementDestinationRateLimitWindow(), 141 destinationsPerMinuteRateLimit, 142 dao)) { 143 LoggerFactory.getMeasurementLogger() 144 .d( 145 "storeSource (FAILURE): Exceeded limit of %s destinations per minute." 146 + " Enrollment ID: %s, Source ID: %s, Source Event ID: %s", 147 destinationsPerMinuteRateLimit, 148 source.getEnrollmentId(), 149 source.getId(), 150 source.getEventId()); 151 mDebugReportApi.scheduleSourceDestinationPerMinuteRateLimitDebugReport( 152 source, String.valueOf(destinationsPerMinuteRateLimit), dao); 153 adrTypes.add(DebugReportApi.Type.SOURCE_DESTINATION_RATE_LIMIT); 154 return InsertSourcePermission.NOT_ALLOWED; 155 } 156 157 // Global (cross reporting-origin) destinations rate limit. This needs to be recorded before 158 // FIFO based deletion as it's a LIFO based rate limit. Although reject the source if it 159 // fails only if every other (enrollment based) rate limit passes to not reveal cross site 160 // data. 161 boolean destinationExceedsGlobalRateLimit = 162 destinationExceedsGlobalRateLimit(source, publisher.get(), dao); 163 164 // Blocks ad-techs to reconstruct browser history by registering multiple sources with 165 // various destinations in a medium window (per day). The larger window is 30 days. 166 int destinationsPerDayRateLimit = mFlags.getMeasurementDestinationPerDayRateLimit(); 167 if (mFlags.getMeasurementEnableDestinationPerDayRateLimitWindow() 168 && sourceExceedsTimeBasedDestinationLimits( 169 source, 170 publisher.get(), 171 publisherType, 172 mFlags.getMeasurementDestinationPerDayRateLimitWindowInMs(), 173 destinationsPerDayRateLimit, 174 dao)) { 175 LoggerFactory.getMeasurementLogger() 176 .d( 177 "storeSource (FAILURE): Exceeded limit of %s destinations per" 178 + " day. Enrollment ID: %s, Source ID: %s, Source Event ID: %s", 179 destinationsPerDayRateLimit, 180 source.getEnrollmentId(), 181 source.getId(), 182 source.getEventId()); 183 mDebugReportApi.scheduleSourceDestinationPerDayRateLimitDebugReport( 184 source, String.valueOf(destinationsPerDayRateLimit), dao); 185 adrTypes.add(DebugReportApi.Type.SOURCE_DESTINATION_PER_DAY_RATE_LIMIT); 186 return InsertSourcePermission.NOT_ALLOWED; 187 } 188 189 if (source.getAppDestinations() != null 190 && isDestinationOutOfBounds( 191 source, 192 publisher.get(), 193 publisherType, 194 source.getEnrollmentId(), 195 source.getAppDestinations(), 196 EventSurfaceType.APP, 197 windowStartTime, 198 source.getEventTime(), 199 dao, 200 adrTypes)) { 201 return InsertSourcePermission.NOT_ALLOWED; 202 } 203 204 if (source.getWebDestinations() != null 205 && isDestinationOutOfBounds( 206 source, 207 publisher.get(), 208 publisherType, 209 source.getEnrollmentId(), 210 source.getWebDestinations(), 211 EventSurfaceType.WEB, 212 windowStartTime, 213 source.getEventTime(), 214 dao, 215 adrTypes)) { 216 return InsertSourcePermission.NOT_ALLOWED; 217 } 218 219 Map<String, Object> additionalDebugReportParams = null; 220 InsertSourcePermission result = InsertSourcePermission.ALLOWED; 221 // Should be deprecated once destination priority is fully launched 222 if (extractSourceDestinationLimitingAlgo(mFlags, source) 223 == Source.DestinationLimitAlgorithm.FIFO) { 224 InsertSourcePermission appDestSourceAllowedToInsert = 225 deleteLowPriorityDestinationSourcesToAccommodateNewSource( 226 source, 227 publisherType, 228 dao, 229 publisher.get(), 230 EventSurfaceType.APP, 231 source.getAppDestinations(), 232 asyncFetchStatus); 233 if (appDestSourceAllowedToInsert == InsertSourcePermission.NOT_ALLOWED) { 234 // Return early without checking web destinations 235 LoggerFactory.getMeasurementLogger() 236 .d( 237 "storeSource (FAILURE): Cannot make space for app destination in" 238 + " source. Enrollment ID: %s, Source ID: %s, Source" 239 + " Event ID: %s", 240 source.getEnrollmentId(), source.getId(), source.getEventId()); 241 mDebugReportApi.scheduleSourceDestinationLimitDebugReport( 242 source, 243 String.valueOf( 244 mFlags.getMeasurementMaxDistinctDestinationsInActiveSource()), 245 dao); 246 adrTypes.add(DebugReportApi.Type.SOURCE_DESTINATION_LIMIT); 247 return InsertSourcePermission.NOT_ALLOWED; 248 } 249 InsertSourcePermission webDestSourceAllowedToInsert = 250 deleteLowPriorityDestinationSourcesToAccommodateNewSource( 251 source, 252 publisherType, 253 dao, 254 publisher.get(), 255 EventSurfaceType.WEB, 256 source.getWebDestinations(), 257 asyncFetchStatus); 258 if (webDestSourceAllowedToInsert == InsertSourcePermission.NOT_ALLOWED) { 259 LoggerFactory.getMeasurementLogger() 260 .d( 261 "storeSource (FAILURE): Cannot make space for web destinations in" 262 + " source. Enrollment ID: %s, Source ID: %s, Source" 263 + " Event ID: %s", 264 source.getEnrollmentId(), source.getId(), source.getEventId()); 265 mDebugReportApi.scheduleSourceDestinationLimitDebugReport( 266 source, 267 String.valueOf( 268 mFlags.getMeasurementMaxDistinctDestinationsInActiveSource()), 269 dao); 270 adrTypes.add(DebugReportApi.Type.SOURCE_DESTINATION_LIMIT); 271 return InsertSourcePermission.NOT_ALLOWED; 272 } 273 274 if (appDestSourceAllowedToInsert == InsertSourcePermission.ALLOWED_FIFO_SUCCESS 275 || webDestSourceAllowedToInsert 276 == InsertSourcePermission.ALLOWED_FIFO_SUCCESS) { 277 int limit = mFlags.getMeasurementMaxDistinctDestinationsInActiveSource(); 278 additionalDebugReportParams = 279 Map.of(DebugReportApi.Body.SOURCE_DESTINATION_LIMIT, String.valueOf(limit)); 280 result = InsertSourcePermission.ALLOWED_FIFO_SUCCESS; 281 adrTypes.add(DebugReportApi.Type.SOURCE_DESTINATION_LIMIT_REPLACED); 282 } 283 } 284 285 // Global (cross ad-tech) destinations rate limit 286 if (destinationExceedsGlobalRateLimit) { 287 // Source won't be inserted, yet we produce a success to debug report to avoid side 288 // channel leakage of cross site data 289 mDebugReportApi.scheduleSourceReport( 290 source, 291 source.getAttributionMode() != Source.AttributionMode.TRUTHFULLY 292 ? DebugReportApi.Type.SOURCE_NOISED 293 : DebugReportApi.Type.SOURCE_SUCCESS, 294 additionalDebugReportParams, 295 dao); 296 adrTypes.add(DebugReportApi.Type.SOURCE_DESTINATION_GLOBAL_RATE_LIMIT); 297 return InsertSourcePermission.NOT_ALLOWED; 298 } 299 300 int numOfDistinctOriginExcludingRegistrationOrigin = 301 dao.countDistinctRegOriginPerPublisherXEnrollmentExclRegOrigin( 302 source.getRegistrationOrigin(), 303 publisher.get(), 304 publisherType, 305 source.getEnrollmentId(), 306 source.getEventTime(), 307 mFlags.getMeasurementMinReportingOriginUpdateWindow()); 308 if (numOfDistinctOriginExcludingRegistrationOrigin 309 >= mFlags.getMeasurementMaxReportingOriginsPerSourceReportingSitePerWindow()) { 310 LoggerFactory.getMeasurementLogger() 311 .d( 312 "storeSource (FAILURE): Reached limit of %s reporting origin for" 313 + " publisher - %s and enrollment - %s per window." 314 + " Source ID: %s, Source Event ID: %s", 315 mFlags 316 .getMeasurementMaxReportingOriginsPerSourceReportingSitePerWindow(), 317 publisher, 318 source.getEnrollmentId(), 319 source.getId(), 320 source.getEventId()); 321 mDebugReportApi.scheduleSourceReport( 322 source, 323 source.getAttributionMode() != Source.AttributionMode.TRUTHFULLY 324 ? DebugReportApi.Type.SOURCE_NOISED 325 : DebugReportApi.Type.SOURCE_SUCCESS, 326 additionalDebugReportParams, 327 dao); 328 adrTypes.add(DebugReportApi.Type.SOURCE_REPORTING_ORIGIN_PER_SITE_LIMIT); 329 return InsertSourcePermission.NOT_ALLOWED; 330 } 331 332 LoggerFactory.getMeasurementLogger() 333 .d( 334 "storeSource: Source allowed to be inserted. Enrollment ID: %s, " 335 + "Source ID: %s, Source Event ID: %s", 336 source.getEnrollmentId(), source.getId(), source.getEventId()); 337 return result; 338 } 339 isNavigationOriginAlreadyRegisteredForRegistration( @onNull Source source, IMeasurementDao dao)340 private boolean isNavigationOriginAlreadyRegisteredForRegistration( 341 @NonNull Source source, IMeasurementDao dao) throws DatastoreException { 342 if (!mFlags.getMeasurementEnableNavigationReportingOriginCheck() 343 || source.getSourceType() != Source.SourceType.NAVIGATION) { 344 return false; 345 } 346 return dao.countNavigationSourcesPerReportingOrigin( 347 source.getRegistrationOrigin(), source.getRegistrationId()) 348 > 0; 349 } 350 getTopLevelPublisher( Uri topOrigin, @EventSurfaceType int publisherType)351 private static Optional<Uri> getTopLevelPublisher( 352 Uri topOrigin, @EventSurfaceType int publisherType) { 353 return publisherType == EventSurfaceType.APP 354 ? Optional.of(topOrigin) 355 : WebAddresses.topPrivateDomainAndScheme(topOrigin); 356 } 357 sourceExceedsTimeBasedDestinationLimits( Source source, Uri publisher, @EventSurfaceType int publisherType, long window, int limit, IMeasurementDao dao)358 private static boolean sourceExceedsTimeBasedDestinationLimits( 359 Source source, 360 Uri publisher, 361 @EventSurfaceType int publisherType, 362 long window, 363 int limit, 364 IMeasurementDao dao) 365 throws DatastoreException { 366 List<Uri> appDestinations = source.getAppDestinations(); 367 if (appDestinations != null) { 368 int appDestinationReportingCount = 369 dao.countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( 370 publisher, 371 publisherType, 372 source.getEnrollmentId(), 373 appDestinations, 374 EventSurfaceType.APP, 375 /* window start time */ source.getEventTime() - window, 376 /*window end time*/ source.getEventTime()); 377 // Same reporting-site destination limit 378 if (appDestinationReportingCount + appDestinations.size() > limit) { 379 LoggerFactory.getMeasurementLogger() 380 .d( 381 "AsyncRegistrationQueueRunner: App time based destination limit" 382 + " exceeded"); 383 return true; 384 } 385 } 386 387 List<Uri> webDestinations = source.getWebDestinations(); 388 if (webDestinations != null) { 389 int webDestinationReportingCount = 390 dao.countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( 391 publisher, 392 publisherType, 393 source.getEnrollmentId(), 394 webDestinations, 395 EventSurfaceType.WEB, 396 /* window start time */ source.getEventTime() - window, 397 /*window end time*/ source.getEventTime()); 398 399 // Same reporting-site destination limit 400 if (webDestinationReportingCount + webDestinations.size() > limit) { 401 LoggerFactory.getMeasurementLogger() 402 .d( 403 "AsyncRegistrationQueueRunner: Web time based destination limit" 404 + " exceeded"); 405 return true; 406 } 407 } 408 409 return false; 410 } 411 destinationExceedsGlobalRateLimit( Source source, Uri publisher, IMeasurementDao dao)412 private boolean destinationExceedsGlobalRateLimit( 413 Source source, Uri publisher, IMeasurementDao dao) throws DatastoreException { 414 long window = mFlags.getMeasurementDestinationRateLimitWindow(); 415 long limit = mFlags.getMeasurementMaxDestinationsPerPublisherPerRateLimitWindow(); 416 long windowStartTime = source.getEventTime() - window; 417 List<Uri> appDestinations = source.getAppDestinations(); 418 if (appDestinations != null) { 419 int destinationCount = 420 dao.countDistinctDestinationsPerPublisherPerRateLimitWindow( 421 publisher, 422 source.getPublisherType(), 423 /* excluded destinations */ appDestinations, 424 EventSurfaceType.APP, 425 windowStartTime, 426 /* windowEndTime */ source.getEventTime()); 427 428 if (destinationCount + appDestinations.size() > limit) { 429 LoggerFactory.getMeasurementLogger() 430 .d( 431 "AsyncRegistrationQueueRunner: App destination global rate limit " 432 + "exceeded"); 433 return true; 434 } 435 } 436 437 List<Uri> webDestinations = source.getWebDestinations(); 438 if (webDestinations != null) { 439 int destinationCount = 440 dao.countDistinctDestinationsPerPublisherPerRateLimitWindow( 441 publisher, 442 source.getPublisherType(), 443 /* excluded destinations */ webDestinations, 444 EventSurfaceType.WEB, 445 windowStartTime, 446 /* windowEndTime */ source.getEventTime()); 447 448 if (destinationCount + webDestinations.size() > limit) { 449 LoggerFactory.getMeasurementLogger() 450 .d( 451 "AsyncRegistrationQueueRunner: Web destination global rate limit " 452 + "exceeded"); 453 return true; 454 } 455 } 456 457 return false; 458 } 459 isDestinationOutOfBounds( Source source, Uri publisher, @EventSurfaceType int publisherType, String enrollmentId, List<Uri> destinations, @EventSurfaceType int destinationType, long windowStartTime, long requestTime, IMeasurementDao dao, Set<DebugReportApi.Type> adrTypes)460 private boolean isDestinationOutOfBounds( 461 Source source, 462 Uri publisher, 463 @EventSurfaceType int publisherType, 464 String enrollmentId, 465 List<Uri> destinations, 466 @EventSurfaceType int destinationType, 467 long windowStartTime, 468 long requestTime, 469 IMeasurementDao dao, 470 Set<DebugReportApi.Type> adrTypes) 471 throws DatastoreException { 472 Flags flags = FlagsFactory.getFlags(); 473 474 // If the source has destination algorithm overridden as LIFO, the source is rejected if the 475 // destination rate limit is exceeded. 476 if (extractSourceDestinationLimitingAlgo(flags, source) 477 == Source.DestinationLimitAlgorithm.LIFO) { 478 int destinationCount; 479 if (flags.getMeasurementEnableDestinationRateLimit()) { 480 destinationCount = 481 dao.countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( 482 publisher, 483 publisherType, 484 enrollmentId, 485 destinations, 486 destinationType, 487 requestTime); 488 } else { 489 destinationCount = 490 dao.countDistinctDestPerPubXEnrollmentInUnexpiredSourceInWindow( 491 publisher, 492 publisherType, 493 enrollmentId, 494 destinations, 495 destinationType, 496 windowStartTime, 497 requestTime); 498 } 499 int maxDistinctDestinations = 500 flags.getMeasurementMaxDistinctDestinationsInActiveSource(); 501 if (destinationCount + destinations.size() > maxDistinctDestinations) { 502 LoggerFactory.getMeasurementLogger() 503 .d( 504 "AsyncRegistrationQueueRunner: " 505 + (destinationType == EventSurfaceType.APP ? "App" : "Web") 506 + " destination count >=" 507 + " MaxDistinctDestinationsPerPublisherXEnrollmentInActive" 508 + "Source. Enrollment ID: " 509 + source.getEnrollmentId() 510 + ", Source ID: " 511 + source.getId() 512 + ", Source Event ID: " 513 + source.getEventId()); 514 mDebugReportApi.scheduleSourceDestinationLimitDebugReport( 515 source, String.valueOf(maxDistinctDestinations), dao); 516 adrTypes.add(DebugReportApi.Type.SOURCE_DESTINATION_LIMIT); 517 return true; 518 } 519 } 520 521 int distinctReportingOriginCount = 522 dao.countDistinctReportingOriginsPerPublisherXDestinationInSource( 523 publisher, 524 publisherType, 525 destinations, 526 source.getRegistrationOrigin(), 527 windowStartTime, 528 requestTime); 529 if (distinctReportingOriginCount 530 >= flags.getMeasurementMaxDistinctRepOrigPerPublXDestInSource()) { 531 LoggerFactory.getMeasurementLogger() 532 .d( 533 "AsyncRegistrationQueueRunner: " 534 + (destinationType == EventSurfaceType.APP ? "App" : "Web") 535 + " distinct reporting origin count >= " 536 + "MaxDistinctRepOrigPerPublisherXDestInSource exceeded." 537 + " Enrollment ID: " 538 + source.getEnrollmentId() 539 + ", Source ID: " 540 + source.getId() 541 + ", Source Event ID: " 542 + source.getEventId()); 543 mDebugReportApi.scheduleSourceReport( 544 source, 545 source.getAttributionMode() != Source.AttributionMode.TRUTHFULLY 546 ? DebugReportApi.Type.SOURCE_NOISED 547 : DebugReportApi.Type.SOURCE_SUCCESS, 548 null, 549 dao); 550 adrTypes.add(DebugReportApi.Type.SOURCE_REPORTING_ORIGIN_LIMIT); 551 return true; 552 } 553 return false; 554 } 555 556 /** 557 * Returns the effective source destination limiting algorithm. Return if the source has 558 * overridden the algorithm, otherwise fallback to the configured default destination algorithm. 559 * 560 * @param flags flags 561 * @param source incoming source 562 * @return the effective source destination limiting algorithm 563 */ extractSourceDestinationLimitingAlgo( Flags flags, Source source)564 private static Source.DestinationLimitAlgorithm extractSourceDestinationLimitingAlgo( 565 Flags flags, Source source) { 566 return Optional.ofNullable(source.getDestinationLimitAlgorithm()) 567 .orElse( 568 Source.DestinationLimitAlgorithm.values()[ 569 flags.getMeasurementDefaultSourceDestinationLimitAlgorithm()]); 570 } 571 deleteLowPriorityDestinationSourcesToAccommodateNewSource( Source source, @EventSurfaceType int publisherType, IMeasurementDao dao, Uri publisher, @EventSurfaceType int destinationType, List<Uri> destinations, AsyncFetchStatus asyncFetchStatus)572 private InsertSourcePermission deleteLowPriorityDestinationSourcesToAccommodateNewSource( 573 Source source, 574 @EventSurfaceType int publisherType, 575 IMeasurementDao dao, 576 Uri publisher, 577 @EventSurfaceType int destinationType, 578 List<Uri> destinations, 579 AsyncFetchStatus asyncFetchStatus) 580 throws DatastoreException { 581 if (destinations == null || destinations.isEmpty()) { 582 return InsertSourcePermission.ALLOWED; 583 } 584 int fifoLimit = mFlags.getMeasurementMaxDistinctDestinationsInActiveSource(); 585 if (destinations.size() > fifoLimit) { 586 return InsertSourcePermission.NOT_ALLOWED; 587 } 588 int distinctDestinations = 589 dao.countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( 590 publisher, 591 publisherType, 592 source.getEnrollmentId(), 593 destinations, 594 destinationType, 595 source.getEventTime()); 596 if (distinctDestinations + destinations.size() <= fifoLimit) { 597 // Source is allowed to be inserted without any deletion 598 return InsertSourcePermission.ALLOWED; 599 } 600 601 // Delete sources associated to the oldest destination per enrollment per publisher. 602 // The new source may have multiple app and web destination, because of which we might 603 // need to delete multiple oldest destinations - in FIFO manner, i.e. in a loop. 604 // Although it should not be more than 4 iterations because the new source can have 605 // at max 1 app destination and 3 web destinations (configurable). 606 while (distinctDestinations + destinations.size() > fifoLimit) { 607 // Delete sources for the lowest priority / oldest destination 608 Pair<Long, List<String>> destinationPriorityWithSourcesToDelete = 609 dao.fetchSourceIdsForLowestPriorityDestinationXEnrollmentXPublisher( 610 publisher, 611 publisherType, 612 source.getEnrollmentId(), 613 destinations, 614 destinationType, 615 source.getEventTime()); 616 if (source.getDestinationLimitPriority() 617 < destinationPriorityWithSourcesToDelete.first) { 618 // If the incoming source has a lower priority than the least prioritized 619 // destination, reject the incoming source. 620 return InsertSourcePermission.NOT_ALLOWED; 621 } 622 623 List<String> sourceIdsToDelete = destinationPriorityWithSourcesToDelete.second; 624 if (sourceIdsToDelete.isEmpty()) { 625 // If destination limit exceeds, the oldest destination deletion should be 626 // successful. This is an unexpected state. 627 throw new IllegalStateException( 628 "No sources were deleted; incoming destinations: " 629 + destinations.size() 630 + "; FIFO limit:" 631 + fifoLimit); 632 } 633 dao.updateSourceStatus(sourceIdsToDelete, Source.Status.MARKED_TO_DELETE); 634 LoggerFactory.getMeasurementLogger() 635 .d( 636 "Deleted " 637 + sourceIdsToDelete.size() 638 + " sources to insert the new source."); 639 if (mFlags.getMeasurementEnableFifoDestinationsDeleteAggregateReports()) { 640 dao.deletePendingAggregateReportsAndAttributionsForSources(sourceIdsToDelete); 641 LoggerFactory.getMeasurementLogger() 642 .d( 643 "Deleted pending aggregate reports of" 644 + sourceIdsToDelete.size() 645 + " sources to insert the new source."); 646 } 647 dao.deleteFutureFakeEventReportsForSources(sourceIdsToDelete, source.getEventTime()); 648 distinctDestinations = 649 dao.countDistinctDestinationsPerPubXEnrollmentInUnexpiredSource( 650 publisher, 651 publisherType, 652 source.getEnrollmentId(), 653 destinations, 654 destinationType, 655 source.getEventTime()); 656 asyncFetchStatus.incrementNumDeletedEntities(sourceIdsToDelete.size()); 657 } 658 return InsertSourcePermission.ALLOWED_FIFO_SUCCESS; 659 } 660 } 661