blob: c06e41fd5c4ae9849b91096b28c6366347e2b90c [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/attribution_reporting/attribution_resolver_impl.h"
#include <memory>
#include <optional>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/overloaded.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "base/ranges/functional.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "components/attribution_reporting/aggregatable_dedup_key.h"
#include "components/attribution_reporting/aggregatable_utils.h"
#include "components/attribution_reporting/aggregatable_values.h"
#include "components/attribution_reporting/event_report_windows.h"
#include "components/attribution_reporting/event_trigger_data.h"
#include "components/attribution_reporting/features.h"
#include "components/attribution_reporting/filters.h"
#include "components/attribution_reporting/source_registration.h"
#include "components/attribution_reporting/source_type.mojom.h"
#include "components/attribution_reporting/trigger_config.h"
#include "components/attribution_reporting/trigger_data_matching.mojom.h"
#include "content/browser/attribution_reporting/aggregatable_attribution_utils.h"
#include "content/browser/attribution_reporting/aggregatable_debug_rate_limit_table.h"
#include "content/browser/attribution_reporting/aggregatable_debug_report.h"
#include "content/browser/attribution_reporting/attribution_config.h"
#include "content/browser/attribution_reporting/attribution_info.h"
#include "content/browser/attribution_reporting/attribution_report.h"
#include "content/browser/attribution_reporting/attribution_resolver_delegate.h"
#include "content/browser/attribution_reporting/attribution_storage_sql.h"
#include "content/browser/attribution_reporting/create_report_result.h"
#include "content/browser/attribution_reporting/process_aggregatable_debug_report_result.mojom.h"
#include "content/browser/attribution_reporting/rate_limit_result.h"
#include "content/browser/attribution_reporting/storable_source.h"
#include "content/browser/attribution_reporting/store_source_result.h"
#include "content/browser/attribution_reporting/stored_source.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
namespace content {
namespace {
using ProcessAggregatableDebugReportStatus =
::attribution_reporting::mojom::ProcessAggregatableDebugReportResult;
using ::attribution_reporting::EventReportWindows;
using ::attribution_reporting::SuitableOrigin;
using ::attribution_reporting::mojom::SourceType;
using ::attribution_reporting::mojom::TriggerDataMatching;
using AggregatableResult = AttributionTrigger::AggregatableResult;
using EventLevelResult = AttributionTrigger::EventLevelResult;
using StoredSourceData = AttributionStorageSql::StoredSourceData;
constexpr int64_t kUnsetRecordId = -1;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(DestinationLimitResult)
enum class DestinationLimitResult {
// Destinations allowed without hitting the limit.
kAllowed = 0,
// Destinations allowed but hitting the limit, deactivating destinations with
// lowest priority or time.
kAllowedLimitHit = 1,
// Destinations not allowed due to lower priority while hitting the limit.
kNotAllowed = 2,
kMaxValue = kNotAllowed,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/attribution_reporting/enums.xml:AttributionSourceDestinationLimitResult)
DestinationLimitResult GetDestinationLimitResult(
const std::vector<StoredSource::Id>& sources_to_deactivate) {
const bool destination_limit_hit = !sources_to_deactivate.empty();
if (!base::FeatureList::IsEnabled(attribution_reporting::features::
kAttributionSourceDestinationLimit)) {
return destination_limit_hit ? DestinationLimitResult::kNotAllowed
: DestinationLimitResult::kAllowed;
}
DestinationLimitResult result =
destination_limit_hit
? (base::Contains(sources_to_deactivate,
StoredSource::Id(RateLimitTable::kUnsetRecordId))
? DestinationLimitResult::kNotAllowed
: DestinationLimitResult::kAllowedLimitHit)
: DestinationLimitResult::kAllowed;
base::UmaHistogramEnumeration("Conversions.SourceDestinationLimitResult",
result);
return result;
}
bool IsSuccessResult(std::optional<EventLevelResult> result) {
return result == EventLevelResult::kSuccess ||
result == EventLevelResult::kSuccessDroppedLowerPriority;
}
bool IsSuccessResult(std::optional<AggregatableResult> result) {
return result == AggregatableResult::kSuccess;
}
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(AttributionResult)
enum class AttributionResult {
kEventLevelOnly = 0,
kAggregatableOnly = 1,
kBoth = 2,
kMaxValue = kBoth,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/attribution_reporting/enums.xml:ConversionAttributionResult)
void RecordAttributionResult(AttributionResult result) {
base::UmaHistogramEnumeration("Conversions.AttributionResult", result);
}
void RecordAttributionResult(const bool has_event_level_report,
const bool has_aggregatable_report) {
if (has_event_level_report && has_aggregatable_report) {
RecordAttributionResult(AttributionResult::kBoth);
} else if (has_event_level_report) {
RecordAttributionResult(AttributionResult::kEventLevelOnly);
} else if (has_aggregatable_report) {
RecordAttributionResult(AttributionResult::kAggregatableOnly);
}
}
} // namespace
AttributionResolverImpl::AttributionResolverImpl(
const base::FilePath& user_data_directory,
std::unique_ptr<AttributionResolverDelegate> delegate)
: delegate_(std::move(delegate)),
storage_(user_data_directory, delegate_.get()) {
DCHECK(delegate_);
}
AttributionResolverImpl::~AttributionResolverImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
StoreSourceResult AttributionResolverImpl::StoreSource(StorableSource source) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!source.registration().debug_key.has_value() ||
source.common_info().debug_cookie_set());
bool is_noised = false;
std::optional<int> destination_limit;
const base::Time source_time = base::Time::Now();
const auto make_result = [&](StoreSourceResult::Result&& result) {
if (absl::holds_alternative<StoreSourceResult::InternalError>(result)) {
is_noised = false;
destination_limit.reset();
}
return StoreSourceResult(std::move(source), is_noised, source_time,
destination_limit, std::move(result));
};
// TODO(crbug.com/40287976): Support multiple specs.
if (source.registration().trigger_specs.specs().size() > 1u) {
return make_result(StoreSourceResult::InternalError());
}
const CommonSourceInfo& common_info = source.common_info();
const attribution_reporting::SourceRegistration& reg = source.registration();
ASSIGN_OR_RETURN(
const auto randomized_response_data,
delegate_->GetRandomizedResponse(
common_info.source_type(), reg.trigger_specs, reg.event_level_epsilon,
reg.attribution_scopes_data),
[&](auto error) -> StoreSourceResult {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (error) {
case attribution_reporting::RandomizedResponseError::
kExceedsChannelCapacityLimit:
return make_result(StoreSourceResult::ExceedsMaxChannelCapacity(
attribution_reporting::GetMaxChannelCapacity(
common_info.source_type())));
case attribution_reporting::RandomizedResponseError::
kExceedsScopesChannelCapacityLimit:
return make_result(
StoreSourceResult::ExceedsMaxScopesChannelCapacity(
attribution_reporting::GetMaxChannelCapacityScopes(
common_info.source_type())));
case attribution_reporting::RandomizedResponseError::
kExceedsTriggerStateCardinalityLimit:
return make_result(
StoreSourceResult::ExceedsMaxTriggerStateCardinality(
attribution_reporting::MaxTriggerStateCardinality()));
case attribution_reporting::RandomizedResponseError::
kExceedsMaxEventStatesLimit:
return make_result(StoreSourceResult::ExceedsMaxEventStatesLimit(
source.registration()
.attribution_scopes_data->max_event_states()));
}
});
DCHECK(attribution_reporting::IsValid(randomized_response_data.response(),
reg.trigger_specs));
// Force the creation of the database if it doesn't exist, as we need to
// persist the source.
if (!storage_.LazyInit(
AttributionStorageSql::DbCreationPolicy::kCreateIfAbsent)) {
return make_result(StoreSourceResult::InternalError());
}
// Only delete expired impressions periodically to avoid excessive DB
// operations.
const base::TimeDelta delete_frequency =
delegate_->GetDeleteExpiredSourcesFrequency();
DCHECK_GE(delete_frequency, base::TimeDelta());
if (source_time - last_deleted_expired_sources_ >= delete_frequency) {
if (!storage_.DeleteExpiredSources()) {
return make_result(StoreSourceResult::InternalError());
}
last_deleted_expired_sources_ = source_time;
}
if (int64_t count = storage_.CountActiveSourcesWithSourceOrigin(
common_info.source_origin(), source_time);
count < 0) {
return make_result(StoreSourceResult::InternalError());
} else if (int64_t max = delegate_->GetMaxSourcesPerOrigin(); count >= max) {
if (int64_t file_size = storage_.StorageFileSizeKB(); file_size > -1) {
base::UmaHistogramCounts10M(
"Conversions.Storage.Sql.FileSizeSourcesPerOriginLimitReached2",
file_size);
std::optional<int64_t> number_of_sources = storage_.NumberOfSources();
if (number_of_sources.has_value()) {
CHECK_GT(*number_of_sources, 0);
base::UmaHistogramCounts1M(
"Conversions.Storage.Sql.FileSizeSourcesPerOriginLimitReached2."
"PerSource",
file_size * 1024 / *number_of_sources);
}
}
return make_result(StoreSourceResult::InsufficientSourceCapacity(max));
}
switch (storage_.SourceAllowedForReportingOriginPerSiteLimit(source,
source_time)) {
case RateLimitResult::kAllowed:
break;
case RateLimitResult::kNotAllowed:
return make_result(StoreSourceResult::ReportingOriginsPerSiteLimitReached(
delegate_->GetRateLimits()
.max_reporting_origins_per_source_reporting_site));
case RateLimitResult::kError:
return make_result(StoreSourceResult::InternalError());
}
RateLimitTable::DestinationRateLimitResult destination_rate_limit_result =
storage_.SourceAllowedForDestinationRateLimit(source, source_time);
base::UmaHistogramEnumeration("Conversions.DestinationRateLimitResult",
destination_rate_limit_result);
bool hit_global_destination_limit = false;
switch (destination_rate_limit_result) {
case RateLimitTable::DestinationRateLimitResult::kAllowed:
break;
case RateLimitTable::DestinationRateLimitResult::kHitGlobalLimit:
hit_global_destination_limit = true;
break;
case RateLimitTable::DestinationRateLimitResult::kHitReportingLimit:
return make_result(StoreSourceResult::DestinationReportingLimitReached(
delegate_->GetDestinationRateLimit().max_per_reporting_site));
case RateLimitTable::DestinationRateLimitResult::kHitBothLimits:
return make_result(StoreSourceResult::DestinationBothLimitsReached(
delegate_->GetDestinationRateLimit().max_per_reporting_site));
case RateLimitTable::DestinationRateLimitResult::kError:
return make_result(StoreSourceResult::InternalError());
}
if (base::FeatureList::IsEnabled(attribution_reporting::features::
kAttributionSourceDestinationLimit)) {
switch (storage_.SourceAllowedForDestinationPerDayRateLimit(source,
source_time)) {
case RateLimitResult::kAllowed:
break;
case RateLimitResult::kNotAllowed:
return make_result(
StoreSourceResult::DestinationPerDayReportingLimitReached(
delegate_->GetDestinationRateLimit()
.max_per_reporting_site_per_day));
case RateLimitResult::kError:
return make_result(StoreSourceResult::InternalError());
}
}
base::expected<std::vector<StoredSource::Id>, RateLimitTable::Error>
source_ids_to_deactivate =
storage_.GetSourcesToDeactivateForDestinationLimit(source,
source_time);
if (!source_ids_to_deactivate.has_value()) {
return make_result(StoreSourceResult::InternalError());
}
switch (GetDestinationLimitResult(*source_ids_to_deactivate)) {
case DestinationLimitResult::kNotAllowed:
return make_result(
StoreSourceResult::InsufficientUniqueDestinationCapacity(
delegate_->GetMaxDestinationsPerSourceSiteReportingSite()));
case DestinationLimitResult::kAllowedLimitHit:
destination_limit.emplace(
delegate_->GetMaxDestinationsPerSourceSiteReportingSite());
break;
case DestinationLimitResult::kAllowed:
break;
}
is_noised = randomized_response_data.response().has_value();
std::unique_ptr<AttributionStorageSql::Transaction> transaction =
storage_.StartTransaction();
if (!transaction) {
return make_result(StoreSourceResult::InternalError());
}
if (!storage_.DeactivateSourcesForDestinationLimit(*source_ids_to_deactivate,
source_time)) {
return make_result(StoreSourceResult::InternalError());
}
if (!storage_.UpdateOrRemoveSourcesWithIncompatibleScopeFields(source,
source_time) ||
!storage_.RemoveSourcesWithOutdatedScopes(source, source_time)) {
return make_result(StoreSourceResult::InternalError());
}
const auto commit_and_return = [&](StoreSourceResult::Result&& result) {
return transaction->Commit()
? make_result(std::move(result))
: make_result(StoreSourceResult::InternalError());
};
// IMPORTANT: The following rate-limits are shared across reporting sites and
// therefore security sensitive. It's important to ensure that these
// rate-limits are checked as last steps in source registration to avoid
// side-channel leakage of the cross-origin data.
if (hit_global_destination_limit) {
return commit_and_return(
StoreSourceResult::DestinationGlobalLimitReached());
}
switch (storage_.SourceAllowedForReportingOriginLimit(source, source_time)) {
case RateLimitResult::kAllowed:
break;
case RateLimitResult::kNotAllowed:
return commit_and_return(StoreSourceResult::ExcessiveReportingOrigins());
case RateLimitResult::kError:
return make_result(StoreSourceResult::InternalError());
}
const base::Time aggregatable_report_window_time =
source_time + reg.aggregatable_report_window;
int num_attributions = 0;
auto attribution_logic = StoredSource::AttributionLogic::kTruthfully;
bool event_level_active = true;
if (const auto& response = randomized_response_data.response()) {
num_attributions = response->size();
attribution_logic = num_attributions == 0
? StoredSource::AttributionLogic::kNever
: StoredSource::AttributionLogic::kFalsely;
event_level_active = num_attributions == 0;
}
std::optional<StoredSource> stored_source =
storage_.InsertSource(source, source_time, num_attributions,
event_level_active, randomized_response_data.rate(),
attribution_logic, aggregatable_report_window_time);
if (!stored_source.has_value()) {
return make_result(StoreSourceResult::InternalError());
}
if (!storage_.AddRateLimitForSource(
*stored_source, source.registration().destination_limit_priority)) {
return make_result(StoreSourceResult::InternalError());
}
std::optional<base::Time> min_fake_report_time;
if (attribution_logic == StoredSource::AttributionLogic::kFalsely) {
for (const auto& fake_report : *randomized_response_data.response()) {
auto trigger_spec_it = stored_source->trigger_specs().find(
fake_report.trigger_data, TriggerDataMatching::kExact);
const attribution_reporting::EventReportWindows& windows =
(*trigger_spec_it).second.event_report_windows();
base::Time report_time =
windows.ReportTimeAtWindow(source_time, fake_report.window_index);
// The report start time will always fall within a report window, no
// matter the report window's end time.
base::Time trigger_time =
windows.StartTimeAtWindow(source_time, fake_report.window_index);
DCHECK_EQ(windows.ComputeReportTime(source_time, trigger_time),
report_time);
// Set the `context_origin` to be the source origin for fake reports,
// as these reports are generated only via the source site's context.
// The fake destinations are not relevant to the context that
// actually created the report.
if (!storage_.StoreAttributionReport(
stored_source->source_id(), trigger_time, report_time,
/*external_report_id=*/delegate_->NewReportID(),
/*trigger_debug_key=*/std::nullopt,
/*context_origin=*/common_info.source_origin(),
common_info.reporting_origin(), fake_report.trigger_data,
/*priority=*/0)) {
return make_result(StoreSourceResult::InternalError());
}
if (!min_fake_report_time.has_value() ||
report_time < *min_fake_report_time) {
min_fake_report_time = report_time;
}
}
}
if (attribution_logic != StoredSource::AttributionLogic::kTruthfully) {
if (!storage_.AddRateLimitForAttribution(
AttributionInfo(/*time=*/source_time,
/*debug_key=*/std::nullopt,
/*context_origin=*/common_info.source_origin()),
*stored_source, RateLimitTable::Scope::kEventLevelAttribution,
AttributionReport::Id(RateLimitTable::kUnsetRecordId))) {
return make_result(StoreSourceResult::InternalError());
}
}
if (!transaction->Commit()) {
return make_result(StoreSourceResult::InternalError());
}
static_assert(AttributionStorageSql::kCurrentVersionNumber < 86);
base::UmaHistogramCustomCounts("Conversions.DbVersionOnSourceStored",
AttributionStorageSql::kCurrentVersionNumber,
/*min=*/56,
/*exclusive_max=*/86, /*buckets=*/30);
return make_result(StoreSourceResult::Success(min_fake_report_time,
stored_source->source_id()));
}
CreateReportResult AttributionResolverImpl::MaybeCreateAndStoreReport(
AttributionTrigger trigger) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const attribution_reporting::TriggerRegistration& trigger_registration =
trigger.registration();
const base::Time trigger_time = base::Time::Now();
AttributionInfo attribution_info(
trigger_time, trigger_registration.debug_key,
/*context_origin=*/trigger.destination_origin());
// Declarations for all of the various pieces of information which may be
// collected and/or returned as a result of computing new reports in order to
// produce a `CreateReportResult`.
std::optional<EventLevelResult> event_level_status;
std::optional<AttributionReport> new_event_level_report;
std::optional<AggregatableResult> aggregatable_status;
std::optional<AttributionReport> new_aggregatable_report;
std::optional<AttributionReport> replaced_event_level_report;
std::optional<AttributionReport> dropped_event_level_report;
std::optional<StoredSourceData> source_to_attribute;
std::optional<base::Time> min_null_aggregatable_report_time;
CreateReportResult::Limits limits;
auto assemble_report_result =
[&](std::optional<EventLevelResult> new_event_level_status,
std::optional<AggregatableResult> new_aggregatable_status) {
event_level_status = event_level_status.has_value()
? event_level_status
: new_event_level_status;
DCHECK(event_level_status.has_value());
if (!IsSuccessResult(*event_level_status)) {
new_event_level_report = std::nullopt;
replaced_event_level_report = std::nullopt;
}
aggregatable_status = aggregatable_status.has_value()
? aggregatable_status
: new_aggregatable_status;
DCHECK(aggregatable_status.has_value());
if (!IsSuccessResult(*aggregatable_status)) {
new_aggregatable_report = std::nullopt;
}
if (event_level_status == EventLevelResult::kInternalError ||
aggregatable_status == AggregatableResult::kInternalError) {
min_null_aggregatable_report_time.reset();
}
return CreateReportResult(
trigger_time, std::move(trigger), *event_level_status,
*aggregatable_status, std::move(replaced_event_level_report),
std::move(new_event_level_report),
std::move(new_aggregatable_report),
source_to_attribute
? std::make_optional(std::move(source_to_attribute->source))
: std::nullopt,
limits, std::move(dropped_event_level_report),
min_null_aggregatable_report_time);
};
auto generate_null_reports_and_assemble_report_result =
[&](std::optional<EventLevelResult> new_event_level_status,
std::optional<AggregatableResult> new_aggregatable_status,
AttributionStorageSql::Transaction& transaction)
VALID_CONTEXT_REQUIRED(sequence_checker_) {
DCHECK(!new_aggregatable_report.has_value());
if (!GenerateNullAggregatableReportsAndStoreReports(
trigger, attribution_info,
source_to_attribute ? &source_to_attribute->source
: nullptr,
new_aggregatable_report,
min_null_aggregatable_report_time) ||
!transaction.Commit()) {
min_null_aggregatable_report_time.reset();
}
return assemble_report_result(new_event_level_status,
new_aggregatable_status);
};
if (trigger_registration.event_triggers.empty()) {
event_level_status = EventLevelResult::kNotRegistered;
}
if (!trigger.HasAggregatableData()) {
aggregatable_status = AggregatableResult::kNotRegistered;
}
if (event_level_status.has_value() && aggregatable_status.has_value()) {
return assemble_report_result(/*new_event_level_status=*/std::nullopt,
/*new_aggregatable_status=*/std::nullopt);
}
if (!storage_.LazyInit(
AttributionStorageSql::DbCreationPolicy::kCreateIfAbsent)) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
std::unique_ptr<AttributionStorageSql::Transaction> transaction =
storage_.StartTransaction();
if (!transaction) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
std::optional<StoredSource::Id> source_id_to_attribute;
std::vector<StoredSource::Id> source_ids_to_delete;
std::vector<StoredSource::Id> source_ids_to_deactivate;
if (!storage_.FindMatchingSourceForTrigger(
trigger, trigger_time, source_id_to_attribute, source_ids_to_delete,
source_ids_to_deactivate)) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
if (!source_id_to_attribute.has_value()) {
return generate_null_reports_and_assemble_report_result(
EventLevelResult::kNoMatchingImpressions,
AggregatableResult::kNoMatchingImpressions, *transaction);
}
source_to_attribute = storage_.ReadSourceToAttribute(*source_id_to_attribute);
// This is only possible if there is a corrupt DB.
if (!source_to_attribute.has_value()) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
base::UmaHistogramBoolean(
"Conversions.TriggerTimeLessThanSourceTime",
trigger_time < source_to_attribute->source.source_time());
const bool top_level_filters_match =
source_to_attribute->source.filter_data().Matches(
source_to_attribute->source.common_info().source_type(),
source_to_attribute->source.source_time(), trigger_time,
trigger_registration.filters);
if (!top_level_filters_match) {
return generate_null_reports_and_assemble_report_result(
EventLevelResult::kNoMatchingSourceFilterData,
AggregatableResult::kNoMatchingSourceFilterData, *transaction);
}
// Delete all unattributed sources and deactivate all attributed sources not
// used.
if (!storage_.DeleteSources(source_ids_to_delete) ||
!storage_.DeactivateSources(source_ids_to_deactivate)) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
std::optional<uint64_t> dedup_key;
if (!event_level_status.has_value()) {
if (EventLevelResult create_event_level_status =
MaybeCreateEventLevelReport(attribution_info,
source_to_attribute->source, trigger,
new_event_level_report, dedup_key);
create_event_level_status != EventLevelResult::kSuccess) {
event_level_status = create_event_level_status;
}
}
std::optional<uint64_t> aggregatable_dedup_key;
if (!aggregatable_status.has_value()) {
if (AggregatableResult create_aggregatable_status =
MaybeCreateAggregatableAttributionReport(
attribution_info, source_to_attribute->source, trigger,
new_aggregatable_report, aggregatable_dedup_key,
limits.max_aggregatable_reports_per_destination,
limits.rate_limits_max_attributions);
create_aggregatable_status != AggregatableResult::kSuccess) {
aggregatable_status = create_aggregatable_status;
}
}
if (event_level_status == EventLevelResult::kInternalError ||
aggregatable_status == AggregatableResult::kInternalError) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
if (event_level_status.has_value() && aggregatable_status.has_value()) {
return generate_null_reports_and_assemble_report_result(
/*new_event_level_status=*/std::nullopt,
/*new_aggregatable_status=*/std::nullopt, *transaction);
}
switch (storage_.AttributionAllowedForReportingOriginLimit(
attribution_info, source_to_attribute->source)) {
case RateLimitResult::kAllowed:
break;
case RateLimitResult::kNotAllowed:
limits.rate_limits_max_attribution_reporting_origins =
delegate_->GetRateLimits().max_attribution_reporting_origins;
new_aggregatable_report.reset();
return generate_null_reports_and_assemble_report_result(
EventLevelResult::kExcessiveReportingOrigins,
AggregatableResult::kExcessiveReportingOrigins, *transaction);
case RateLimitResult::kError:
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
std::optional<EventLevelResult> store_event_level_status;
if (!event_level_status.has_value()) {
DCHECK(new_event_level_report.has_value());
store_event_level_status = MaybeStoreEventLevelReport(
*new_event_level_report, source_to_attribute->source, dedup_key,
source_to_attribute->num_attributions, replaced_event_level_report,
dropped_event_level_report,
limits.max_event_level_reports_per_destination,
limits.rate_limits_max_attributions);
}
std::optional<AggregatableResult> store_aggregatable_status;
if (!aggregatable_status.has_value()) {
DCHECK(new_aggregatable_report.has_value());
store_aggregatable_status =
storage_.MaybeStoreAggregatableAttributionReportData(
*new_aggregatable_report, source_to_attribute->source.source_id(),
source_to_attribute->source
.remaining_aggregatable_attribution_budget(),
source_to_attribute->num_aggregatable_attribution_reports,
aggregatable_dedup_key, limits.max_aggregatable_reports_per_source);
}
if (store_event_level_status == EventLevelResult::kInternalError ||
store_aggregatable_status == AggregatableResult::kInternalError) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
if (!IsSuccessResult(store_event_level_status)) {
new_event_level_report.reset();
}
if (!IsSuccessResult(store_aggregatable_status)) {
new_aggregatable_report.reset();
}
// Stores null reports and the aggregatable report here to be in the same
// transaction.
if (!GenerateNullAggregatableReportsAndStoreReports(
trigger, attribution_info, &source_to_attribute->source,
new_aggregatable_report, min_null_aggregatable_report_time)) {
min_null_aggregatable_report_time.reset();
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
// Early exit if done modifying the storage. Noised reports still need to
// clean sources.
if (!IsSuccessResult(store_event_level_status) &&
!IsSuccessResult(store_aggregatable_status) &&
store_event_level_status != EventLevelResult::kNeverAttributedSource) {
if (!transaction->Commit()) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
return assemble_report_result(store_event_level_status,
store_aggregatable_status);
}
// Based on the deletion logic here and the fact that we delete sources
// with |num_attributions > 0| or |num_aggregatable_attribution_reports > 0|
// when there is a new matching source in |StoreSource()|, we should be
// guaranteed that these sources all have |num_attributions == 0| and
// |num_aggregatable_attribution_reports == 0|, and that they never
// contributed to a rate limit. Therefore, we don't need to call
// |RateLimitTable::ClearDataForSourceIds()| here.
// Reports which are dropped do not need to make any further changes.
if (store_event_level_status == EventLevelResult::kNeverAttributedSource &&
!IsSuccessResult(store_aggregatable_status)) {
if (!transaction->Commit()) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
return assemble_report_result(store_event_level_status,
store_aggregatable_status);
}
RecordAttributionResult(IsSuccessResult(store_event_level_status),
IsSuccessResult(store_aggregatable_status));
if (new_event_level_report.has_value() &&
!storage_.AddRateLimitForAttribution(
attribution_info, source_to_attribute->source,
RateLimitTable::Scope::kEventLevelAttribution,
new_event_level_report->id())) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
if (new_aggregatable_report.has_value() &&
!storage_.AddRateLimitForAttribution(
attribution_info, source_to_attribute->source,
RateLimitTable::Scope::kAggregatableAttribution,
new_aggregatable_report->id())) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
if (!transaction->Commit()) {
return assemble_report_result(EventLevelResult::kInternalError,
AggregatableResult::kInternalError);
}
return assemble_report_result(store_event_level_status,
store_aggregatable_status);
}
EventLevelResult AttributionResolverImpl::MaybeCreateEventLevelReport(
const AttributionInfo& attribution_info,
const StoredSource& source,
const AttributionTrigger& trigger,
std::optional<AttributionReport>& report,
std::optional<uint64_t>& dedup_key) {
if (source.attribution_logic() == StoredSource::AttributionLogic::kFalsely) {
DCHECK_EQ(source.active_state(),
StoredSource::ActiveState::kReachedEventLevelAttributionLimit);
return EventLevelResult::kFalselyAttributedSource;
}
const CommonSourceInfo& common_info = source.common_info();
const SourceType source_type = common_info.source_type();
auto event_trigger = base::ranges::find_if(
trigger.registration().event_triggers,
[&](const attribution_reporting::EventTriggerData& event_trigger) {
return source.filter_data().Matches(
source_type, source.source_time(),
/*trigger_time=*/attribution_info.time, event_trigger.filters);
});
if (event_trigger == trigger.registration().event_triggers.end()) {
return EventLevelResult::kNoMatchingConfigurations;
}
if (event_trigger->dedup_key.has_value() &&
base::Contains(source.dedup_keys(), *event_trigger->dedup_key)) {
return EventLevelResult::kDeduplicated;
}
auto trigger_spec_it = source.trigger_specs().find(
event_trigger->data, source.trigger_data_matching());
if (!trigger_spec_it) {
return EventLevelResult::kNoMatchingTriggerData;
}
auto [trigger_data, trigger_spec] = *trigger_spec_it;
switch (trigger_spec.event_report_windows().FallsWithin(
attribution_info.time - source.source_time())) {
case EventReportWindows::WindowResult::kFallsWithin:
break;
case EventReportWindows::WindowResult::kNotStarted:
return EventLevelResult::kReportWindowNotStarted;
case EventReportWindows::WindowResult::kPassed:
return EventLevelResult::kReportWindowPassed;
}
const base::Time report_time = delegate_->GetEventLevelReportTime(
trigger_spec.event_report_windows(), source.source_time(),
attribution_info.time);
report = AttributionReport(
attribution_info, AttributionReport::Id(kUnsetRecordId), report_time,
/*initial_report_time=*/report_time, delegate_->NewReportID(),
/*failed_send_attempts=*/0,
AttributionReport::EventLevelData(trigger_data, event_trigger->priority,
source),
common_info.reporting_origin());
dedup_key = event_trigger->dedup_key;
return EventLevelResult::kSuccess;
}
AggregatableResult
AttributionResolverImpl::MaybeCreateAggregatableAttributionReport(
const AttributionInfo& attribution_info,
const StoredSource& source,
const AttributionTrigger& trigger,
std::optional<AttributionReport>& report,
std::optional<uint64_t>& dedup_key,
std::optional<int>& max_aggregatable_reports_per_destination,
std::optional<int64_t>& rate_limits_max_attributions) {
const attribution_reporting::TriggerRegistration& trigger_registration =
trigger.registration();
const CommonSourceInfo& common_info = source.common_info();
if (attribution_info.time >= source.aggregatable_report_window_time()) {
return AggregatableResult::kReportWindowPassed;
}
const SourceType source_type = common_info.source_type();
auto matched_dedup_key = base::ranges::find_if(
trigger.registration().aggregatable_dedup_keys,
[&](const attribution_reporting::AggregatableDedupKey&
aggregatable_dedup_key) {
return source.filter_data().Matches(
source_type, source.source_time(),
/*trigger_time=*/attribution_info.time,
aggregatable_dedup_key.filters);
});
if (matched_dedup_key !=
trigger.registration().aggregatable_dedup_keys.end()) {
dedup_key = matched_dedup_key->dedup_key;
}
if (dedup_key.has_value() &&
base::Contains(source.aggregatable_dedup_keys(), *dedup_key)) {
return AggregatableResult::kDeduplicated;
}
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions = CreateAggregatableHistogram(
source.filter_data(), source_type, source.source_time(),
/*trigger_time=*/attribution_info.time, source.aggregation_keys(),
trigger_registration.aggregatable_trigger_data,
trigger_registration.aggregatable_values);
if (contributions.empty()) {
return AggregatableResult::kNoHistograms;
}
if (int64_t count = storage_.CountReportsWithDestinationSite(
net::SchemefulSite(attribution_info.context_origin),
AttributionReport::Type::kAggregatableAttribution);
count < 0) {
return AggregatableResult::kInternalError;
} else if (max_aggregatable_reports_per_destination =
delegate_->GetMaxReportsPerDestination(
AttributionReport::Type::kAggregatableAttribution);
count >= *max_aggregatable_reports_per_destination) {
return AggregatableResult::kNoCapacityForConversionDestination;
}
switch (storage_.AttributionAllowedForAttributionLimit(
attribution_info, source,
RateLimitTable::Scope::kAggregatableAttribution)) {
case RateLimitResult::kAllowed:
break;
case RateLimitResult::kNotAllowed:
rate_limits_max_attributions =
delegate_->GetRateLimits().max_attributions;
return AggregatableResult::kExcessiveAttributions;
case RateLimitResult::kError:
return AggregatableResult::kInternalError;
}
base::Time report_time =
GetAggregatableReportTime(trigger, attribution_info.time);
report = AttributionReport(
attribution_info, AttributionReport::Id(kUnsetRecordId), report_time,
/*initial_report_time=*/report_time, delegate_->NewReportID(),
/*failed_send_attempts=*/0,
AttributionReport::AggregatableAttributionData(
AttributionReport::CommonAggregatableData(
trigger_registration.aggregation_coordinator_origin,
trigger_registration.aggregatable_trigger_config),
std::move(contributions), source),
source.common_info().reporting_origin());
return AggregatableResult::kSuccess;
}
bool AttributionResolverImpl::GenerateNullAggregatableReportsAndStoreReports(
const AttributionTrigger& trigger,
const AttributionInfo& attribution_info,
const StoredSource* source,
std::optional<AttributionReport>& new_aggregatable_report,
std::optional<base::Time>& min_null_aggregatable_report_time) {
std::optional<base::Time> attributed_source_time;
if (new_aggregatable_report) {
const auto* data =
absl::get_if<AttributionReport::AggregatableAttributionData>(
&new_aggregatable_report->data());
DCHECK(data);
attributed_source_time = data->source_time;
DCHECK(source);
std::optional<AttributionReport::Id> report_id =
storage_.StoreAggregatableReport(
source->source_id(), attribution_info.time,
new_aggregatable_report->initial_report_time(),
new_aggregatable_report->external_report_id(),
attribution_info.debug_key, attribution_info.context_origin,
new_aggregatable_report->reporting_origin(),
data->common_data.aggregation_coordinator_origin,
data->common_data.aggregatable_trigger_config, data->contributions);
if (!report_id.has_value()) {
return false;
}
new_aggregatable_report->set_id(*report_id);
}
if (trigger.HasAggregatableData()) {
std::vector<attribution_reporting::NullAggregatableReport>
null_aggregatable_reports =
attribution_reporting::GetNullAggregatableReports(
trigger.registration().aggregatable_trigger_config,
attribution_info.time, attributed_source_time,
[&](int lookback_day) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return delegate_
->GenerateNullAggregatableReportForLookbackDay(
lookback_day, trigger.registration()
.aggregatable_trigger_config
.source_registration_time_config());
});
for (const auto& null_aggregatable_report : null_aggregatable_reports) {
base::Time report_time =
GetAggregatableReportTime(trigger, attribution_info.time);
min_null_aggregatable_report_time = AttributionReport::MinReportTime(
min_null_aggregatable_report_time, report_time);
if (!storage_.StoreNullReport(
/*trigger_time=*/attribution_info.time, report_time,
/*external_report_id=*/delegate_->NewReportID(),
/*trigger_debug_key=*/std::nullopt,
attribution_info.context_origin, trigger.reporting_origin(),
trigger.registration().aggregation_coordinator_origin,
trigger.registration().aggregatable_trigger_config,
null_aggregatable_report.fake_source_time)) {
return false;
}
}
}
return true;
}
base::Time AttributionResolverImpl::GetAggregatableReportTime(
const AttributionTrigger& trigger,
base::Time trigger_time) const {
if (trigger.registration()
.aggregatable_trigger_config
.ShouldCauseAReportToBeSentUnconditionally()) {
return trigger_time;
}
return delegate_->GetAggregatableReportTime(trigger_time);
}
std::vector<AttributionReport> AttributionResolverImpl::GetAttributionReports(
base::Time max_report_time,
int limit) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<AttributionReport> reports =
storage_.GetAttributionReports(max_report_time, limit);
delegate_->ShuffleReports(reports);
return reports;
}
std::optional<base::Time> AttributionResolverImpl::GetNextReportTime(
base::Time time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return storage_.GetNextReportTime(time);
}
std::optional<AttributionReport> AttributionResolverImpl::GetReport(
AttributionReport::Id id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return storage_.GetReport(id);
}
std::vector<StoredSource> AttributionResolverImpl::GetActiveSources(int limit) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return storage_.GetActiveSources(limit);
}
std::set<AttributionDataModel::DataKey>
AttributionResolverImpl::GetAllDataKeys() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return storage_.GetAllDataKeys();
}
void AttributionResolverImpl::DeleteByDataKey(
const AttributionDataModel::DataKey& datakey) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ClearData(base::Time::Min(), base::Time::Max(),
base::BindRepeating(std::equal_to<blink::StorageKey>(),
blink::StorageKey::CreateFirstParty(
datakey.reporting_origin())),
/*delete_rate_limit_data=*/true);
}
bool AttributionResolverImpl::DeleteReport(AttributionReport::Id report_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return storage_.DeleteReport(report_id);
}
bool AttributionResolverImpl::UpdateReportForSendFailure(
AttributionReport::Id report_id,
base::Time new_report_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return storage_.UpdateReportForSendFailure(report_id, new_report_time);
}
std::optional<base::Time> AttributionResolverImpl::AdjustOfflineReportTimes() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (auto delay = delegate_->GetOfflineReportDelayConfig()) {
storage_.AdjustOfflineReportTimes(delay->min, delay->max);
}
return storage_.GetNextReportTime(base::Time::Min());
}
void AttributionResolverImpl::ClearData(
base::Time delete_begin,
base::Time delete_end,
StoragePartition::StorageKeyMatcherFunction filter,
bool delete_rate_limit_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SCOPED_UMA_HISTOGRAM_TIMER("Conversions.ClearDataTime");
if (filter.is_null() && (delete_begin.is_null() || delete_begin.is_min()) &&
delete_end.is_max()) {
storage_.ClearAllDataAllTime(delete_rate_limit_data);
return;
}
// Measure the time it takes to perform a clear with a filter separately from
// the above histogram.
SCOPED_UMA_HISTOGRAM_TIMER("Conversions.Storage.ClearDataWithFilterDuration");
storage_.ClearDataWithFilter(delete_begin, delete_end, std::move(filter),
delete_rate_limit_data);
}
ProcessAggregatableDebugReportResult
AttributionResolverImpl::ProcessAggregatableDebugReport(
AggregatableDebugReport report,
std::optional<int> remaining_budget,
std::optional<StoredSource::Id> source_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto make_result = [&](ProcessAggregatableDebugReportStatus result) {
switch (result) {
case ProcessAggregatableDebugReportStatus::kSuccess:
break;
case ProcessAggregatableDebugReportStatus::kNoDebugData:
case ProcessAggregatableDebugReportStatus::kInsufficientBudget:
case ProcessAggregatableDebugReportStatus::kExcessiveReports:
case ProcessAggregatableDebugReportStatus::kGlobalRateLimitReached:
case ProcessAggregatableDebugReportStatus::kReportingSiteRateLimitReached:
case ProcessAggregatableDebugReportStatus::kBothRateLimitsReached:
case ProcessAggregatableDebugReportStatus::kInternalError:
report.ToNull();
break;
}
base::UmaHistogramEnumeration(
"Conversions.AggregatableDebugReport.ProcessResult", result);
return ProcessAggregatableDebugReportResult(std::move(report), result);
};
report.set_report_id(delegate_->NewReportID());
if (report.contributions().empty()) {
return make_result(ProcessAggregatableDebugReportStatus::kNoDebugData);
}
int num_reports = 0;
if (source_id.has_value()) {
std::optional<AttributionStorageSql::AggregatableDebugSourceData>
source_data = storage_.GetAggregatableDebugSourceData(*source_id);
if (!source_data.has_value() ||
!attribution_reporting::IsRemainingAggregatableBudgetInRange(
source_data->remaining_budget) ||
source_data->num_reports < 0) {
return make_result(ProcessAggregatableDebugReportStatus::kInternalError);
}
if (remaining_budget.has_value()) {
// Source aggregatable debug report should be the first aggregatable debug
// report created for this source.
if (source_data->remaining_budget != remaining_budget ||
source_data->num_reports != num_reports) {
return make_result(
ProcessAggregatableDebugReportStatus::kInternalError);
}
}
remaining_budget = source_data->remaining_budget;
num_reports = source_data->num_reports;
}
// `remaining_budget` is `std::nullopt` for `kTriggerNoMatchingSource` debug
// report. In this case, the total budget is required to not exceed the
// maximum budget per source.
int effective_remaining_budget =
remaining_budget.value_or(attribution_reporting::kMaxAggregatableValue);
CHECK(attribution_reporting::IsRemainingAggregatableBudgetInRange(
effective_remaining_budget));
if (report.BudgetRequired() > effective_remaining_budget) {
return make_result(
ProcessAggregatableDebugReportStatus::kInsufficientBudget);
}
int max_reports_per_source =
delegate_->GetAggregatableDebugRateLimit().max_reports_per_source;
CHECK_GT(max_reports_per_source, 0);
if (num_reports >= max_reports_per_source) {
return make_result(ProcessAggregatableDebugReportStatus::kExcessiveReports);
}
switch (storage_.AggregatableDebugReportAllowedForRateLimit(report)) {
case AggregatableDebugRateLimitTable::Result::kAllowed:
break;
case AggregatableDebugRateLimitTable::Result::kHitGlobalLimit:
return make_result(
ProcessAggregatableDebugReportStatus::kGlobalRateLimitReached);
case AggregatableDebugRateLimitTable::Result::kHitReportingLimit:
return make_result(
ProcessAggregatableDebugReportStatus::kReportingSiteRateLimitReached);
case AggregatableDebugRateLimitTable::Result::kHitBothLimits:
return make_result(
ProcessAggregatableDebugReportStatus::kBothRateLimitsReached);
case AggregatableDebugRateLimitTable::Result::kError:
return make_result(ProcessAggregatableDebugReportStatus::kInternalError);
}
if (!storage_.AdjustForAggregatableDebugReport(report, source_id)) {
return make_result(ProcessAggregatableDebugReportStatus::kInternalError);
}
return make_result(ProcessAggregatableDebugReportStatus::kSuccess);
}
void AttributionResolverImpl::SetDelegate(
std::unique_ptr<AttributionResolverDelegate> delegate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(delegate);
storage_.SetDelegate(delegate.get());
delegate_ = std::move(delegate);
}
// Checks whether a new report is allowed to be stored for the given source
// based on `GetDefaultAttributionsPerSource()`. If there's sufficient capacity,
// the new report should be stored. Otherwise, if all existing reports were from
// an earlier window, the corresponding source is deactivated and the new
// report should be dropped. Otherwise, If there's insufficient capacity, checks
// the new report's priority against all existing ones for the same source.
// If all existing ones have greater priority, the new report should be dropped;
// otherwise, the existing one with the lowest priority is deleted and the new
// one should be stored.
AttributionResolverImpl::ReplaceReportResult
AttributionResolverImpl::MaybeReplaceLowerPriorityEventLevelReport(
const AttributionReport& report,
const StoredSource& source,
int num_attributions,
std::optional<AttributionReport>& replaced_report) {
DCHECK_GE(num_attributions, 0);
const auto* data =
absl::get_if<AttributionReport::EventLevelData>(&report.data());
DCHECK(data);
// TODO(crbug.com/40287976): The logic in this method doesn't properly handle
// the case in which there are different report windows for different trigger
// data. Prior to enabling `attribution_reporting::features::kTriggerConfig`,
// this must be fixed.
DCHECK(source.trigger_specs().SingleSharedSpec());
// If there's already capacity for the new report, there's nothing to do.
if (num_attributions < source.trigger_specs().max_event_level_reports()) {
return ReplaceReportResult::kAddNewReport;
}
ASSIGN_OR_RETURN(
const std::optional<AttributionStorageSql::ReportIdAndPriority>
report_with_min_priority,
storage_.GetReportWithMinPriority(source.source_id(),
report.initial_report_time()),
[](AttributionStorageSql::Error) { return ReplaceReportResult::kError; });
// Deactivate the source at event-level as a new report will never be
// generated in the future.
if (!report_with_min_priority.has_value()) {
if (!storage_.DeactivateSourceAtEventLevel(source.source_id())) {
return ReplaceReportResult::kError;
}
return ReplaceReportResult::kDropNewReportSourceDeactivated;
}
// If the new report's priority is less than all existing ones, or if its
// priority is equal to the minimum existing one and it is more recent, drop
// it. We could explicitly check the trigger time here, but it would only
// be relevant in the case of an ill-behaved clock, in which case the rest of
// the attribution functionality would probably also break.
if (data->priority <= report_with_min_priority->priority) {
return ReplaceReportResult::kDropNewReport;
}
std::optional<AttributionReport> replaced =
storage_.GetReport(report_with_min_priority->id);
if (!replaced.has_value()) {
return ReplaceReportResult::kError;
}
// Otherwise, delete the existing report with the lowest priority and the
// corresponding attribution rate-limit record.
if (!storage_.DeleteReport(report_with_min_priority->id) ||
!storage_.DeleteAttributionRateLimit(
RateLimitTable::Scope::kEventLevelAttribution, replaced->id())) {
return ReplaceReportResult::kError;
}
replaced_report = std::move(replaced);
return ReplaceReportResult::kReplaceOldReport;
}
EventLevelResult AttributionResolverImpl::MaybeStoreEventLevelReport(
AttributionReport& report,
const StoredSource& source,
std::optional<uint64_t> dedup_key,
int num_attributions,
std::optional<AttributionReport>& replaced_report,
std::optional<AttributionReport>& dropped_report,
std::optional<int>& max_event_level_reports_per_destination,
std::optional<int64_t>& rate_limits_max_attributions) {
const auto* event_level_data =
absl::get_if<AttributionReport::EventLevelData>(&report.data());
DCHECK(event_level_data);
if (source.active_state() ==
StoredSource::ActiveState::kReachedEventLevelAttributionLimit) {
dropped_report = std::move(report);
return EventLevelResult::kExcessiveReports;
}
std::unique_ptr<AttributionStorageSql::Transaction> transaction =
storage_.StartTransaction();
if (!transaction) {
return EventLevelResult::kInternalError;
}
const auto maybe_replace_lower_priority_report_result =
MaybeReplaceLowerPriorityEventLevelReport(
report, source, num_attributions, replaced_report);
const auto commit_and_return = [&](EventLevelResult result) {
return transaction->Commit() ? result : EventLevelResult::kInternalError;
};
switch (maybe_replace_lower_priority_report_result) {
case ReplaceReportResult::kError:
return EventLevelResult::kInternalError;
case ReplaceReportResult::kDropNewReport:
case ReplaceReportResult::kDropNewReportSourceDeactivated:
dropped_report = std::move(report);
return commit_and_return(maybe_replace_lower_priority_report_result ==
ReplaceReportResult::kDropNewReport
? EventLevelResult::kPriorityTooLow
: EventLevelResult::kExcessiveReports);
case ReplaceReportResult::kAddNewReport: {
switch (storage_.AttributionAllowedForAttributionLimit(
report.attribution_info(), source,
RateLimitTable::Scope::kEventLevelAttribution)) {
case RateLimitResult::kAllowed:
break;
case RateLimitResult::kNotAllowed:
rate_limits_max_attributions =
delegate_->GetRateLimits().max_attributions;
return commit_and_return(EventLevelResult::kExcessiveAttributions);
case RateLimitResult::kError:
return EventLevelResult::kInternalError;
}
if (int64_t count = storage_.CountReportsWithDestinationSite(
net::SchemefulSite(report.attribution_info().context_origin),
AttributionReport::Type::kEventLevel);
count < 0) {
return EventLevelResult::kInternalError;
} else if (max_event_level_reports_per_destination =
delegate_->GetMaxReportsPerDestination(
AttributionReport::Type::kEventLevel);
count >= *max_event_level_reports_per_destination) {
return commit_and_return(
EventLevelResult::kNoCapacityForConversionDestination);
}
// Only increment the number of conversions associated with the source if
// we are adding a new one, rather than replacing a dropped one.
if (!storage_.IncrementNumAttributions(source.source_id())) {
return EventLevelResult::kInternalError;
}
break;
}
case ReplaceReportResult::kReplaceOldReport:
break;
}
// Reports with `AttributionLogic::kNever` should be included in all
// attribution operations and matching, but only `kTruthfully` should generate
// reports that get sent.
const bool create_report =
source.attribution_logic() == StoredSource::AttributionLogic::kTruthfully;
if (create_report) {
std::optional<AttributionReport::Id> report_id =
storage_.StoreAttributionReport(
source.source_id(), report.attribution_info().time,
report.initial_report_time(), report.external_report_id(),
report.attribution_info().debug_key,
report.attribution_info().context_origin, report.reporting_origin(),
event_level_data->trigger_data, event_level_data->priority);
if (!report_id.has_value()) {
return EventLevelResult::kInternalError;
}
report.set_id(*report_id);
}
// If a dedup key is present, store it. We do this regardless of whether
// `create_report` is true to avoid leaking whether the report was actually
// stored.
if (dedup_key.has_value() &&
!storage_.StoreDedupKey(source.source_id(), *dedup_key,
AttributionReport::Type::kEventLevel)) {
return EventLevelResult::kInternalError;
}
if (!create_report) {
return commit_and_return(EventLevelResult::kNeverAttributedSource);
}
if (maybe_replace_lower_priority_report_result ==
ReplaceReportResult::kReplaceOldReport) {
return commit_and_return(EventLevelResult::kSuccessDroppedLowerPriority);
}
return commit_and_return(EventLevelResult::kSuccess);
}
} // namespace content