blob: 0ea4338718e39f39846a82708f174fbf7b5a23c9 [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/aggregatable_debug_report.h"
#include <stddef.h>
#include <stdint.h>
#include <optional>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/enum_set.h"
#include "base/feature_list.h"
#include "base/functional/function_ref.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/attribution_reporting/aggregatable_debug_reporting_config.h"
#include "components/attribution_reporting/aggregatable_filtering_id_max_bytes.h"
#include "components/attribution_reporting/aggregatable_utils.h"
#include "components/attribution_reporting/debug_types.h"
#include "components/attribution_reporting/debug_types.mojom.h"
#include "components/attribution_reporting/features.h"
#include "components/attribution_reporting/source_registration.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "components/attribution_reporting/trigger_registration.h"
#include "content/browser/aggregation_service/aggregatable_report.h"
#include "content/browser/aggregation_service/aggregation_service_features.h"
#include "content/browser/attribution_reporting/aggregatable_attribution_utils.h"
#include "content/browser/attribution_reporting/aggregatable_result.mojom.h"
#include "content/browser/attribution_reporting/attribution_trigger.h"
#include "content/browser/attribution_reporting/create_report_result.h"
#include "content/browser/attribution_reporting/event_level_result.mojom.h"
#include "content/browser/attribution_reporting/storable_source.h"
#include "content/browser/attribution_reporting/store_source_result.h"
#include "content/browser/attribution_reporting/store_source_result.mojom.h"
#include "content/browser/attribution_reporting/stored_source.h"
#include "net/base/schemeful_site.h"
#include "third_party/abseil-cpp/absl/numeric/int128.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
using ::attribution_reporting::DebugDataTypes;
using ::attribution_reporting::mojom::AggregatableResult;
using ::attribution_reporting::mojom::DebugDataType;
using ::attribution_reporting::mojom::EventLevelResult;
using ::blink::mojom::AggregatableReportHistogramContribution;
using StoreSourceStatus = ::attribution_reporting::mojom::StoreSourceResult;
constexpr size_t kMaxContributions = 2;
constexpr char kApiIdentifier[] = "attribution-reporting-debug";
constexpr char kVersion[] = "0.1";
constexpr char kVersionWithFlexibleContributionFiltering[] = "1.0";
std::optional<DebugDataType> GetDebugType(const StoreSourceResult& result) {
switch (result.status()) {
case StoreSourceStatus::kSuccess:
return DebugDataType::kSourceSuccess;
case StoreSourceStatus::kInternalError:
return DebugDataType::kSourceUnknownError;
case StoreSourceStatus::kInsufficientSourceCapacity:
return DebugDataType::kSourceStorageLimit;
case StoreSourceStatus::kInsufficientUniqueDestinationCapacity:
return DebugDataType::kSourceDestinationLimit;
case StoreSourceStatus::kExcessiveReportingOrigins:
return DebugDataType::kSourceReportingOriginLimit;
case StoreSourceStatus::kProhibitedByBrowserPolicy:
return std::nullopt;
case StoreSourceStatus::kSuccessNoised:
return DebugDataType::kSourceNoised;
case StoreSourceStatus::kDestinationReportingLimitReached:
case StoreSourceStatus::kDestinationBothLimitsReached:
return DebugDataType::kSourceDestinationRateLimit;
case StoreSourceStatus::kDestinationGlobalLimitReached:
return DebugDataType::kSourceDestinationGlobalRateLimit;
case StoreSourceStatus::kReportingOriginsPerSiteLimitReached:
return DebugDataType::kSourceReportingOriginPerSiteLimit;
case StoreSourceStatus::kExceedsMaxChannelCapacity:
return DebugDataType::kSourceChannelCapacityLimit;
case StoreSourceStatus::kExceedsMaxTriggerStateCardinality:
return DebugDataType::kSourceTriggerStateCardinalityLimit;
case StoreSourceStatus::kDestinationPerDayReportingLimitReached:
return DebugDataType::kSourceDestinationPerDayRateLimit;
case StoreSourceStatus::kExceedsMaxScopesChannelCapacity:
return DebugDataType::kSourceScopesChannelCapacityLimit;
case StoreSourceStatus::kExceedsMaxEventStatesLimit:
return DebugDataType::kSourceMaxEventStatesLimit;
}
}
std::optional<DebugDataType> GetDebugType(EventLevelResult result) {
switch (result) {
case EventLevelResult::kSuccess:
case EventLevelResult::kProhibitedByBrowserPolicy:
case EventLevelResult::kSuccessDroppedLowerPriority:
case EventLevelResult::kNotRegistered:
return std::nullopt;
case EventLevelResult::kInternalError:
return DebugDataType::kTriggerUnknownError;
case EventLevelResult::kNoCapacityForConversionDestination:
return DebugDataType::kTriggerEventStorageLimit;
case EventLevelResult::kExcessiveReportingOrigins:
return DebugDataType::kTriggerReportingOriginLimit;
case EventLevelResult::kNoMatchingImpressions:
return DebugDataType::kTriggerNoMatchingSource;
case EventLevelResult::kExcessiveAttributions:
return DebugDataType::kTriggerEventAttributionsPerSourceDestinationLimit;
case EventLevelResult::kNoMatchingSourceFilterData:
return DebugDataType::kTriggerNoMatchingFilterData;
case EventLevelResult::kDeduplicated:
return DebugDataType::kTriggerEventDeduplicated;
case EventLevelResult::kNoMatchingConfigurations:
return DebugDataType::kTriggerEventNoMatchingConfigurations;
case EventLevelResult::kNeverAttributedSource:
case EventLevelResult::kFalselyAttributedSource:
return DebugDataType::kTriggerEventNoise;
case EventLevelResult::kPriorityTooLow:
return DebugDataType::kTriggerEventLowPriority;
case EventLevelResult::kExcessiveReports:
return DebugDataType::kTriggerEventExcessiveReports;
case EventLevelResult::kReportWindowNotStarted:
return DebugDataType::kTriggerEventReportWindowNotStarted;
case EventLevelResult::kReportWindowPassed:
return DebugDataType::kTriggerEventReportWindowPassed;
case EventLevelResult::kNoMatchingTriggerData:
return DebugDataType::kTriggerEventNoMatchingTriggerData;
}
}
std::optional<DebugDataType> GetDebugType(AggregatableResult result) {
switch (result) {
case AggregatableResult::kSuccess:
case AggregatableResult::kNotRegistered:
case AggregatableResult::kProhibitedByBrowserPolicy:
return std::nullopt;
case AggregatableResult::kInternalError:
return DebugDataType::kTriggerUnknownError;
case AggregatableResult::kNoCapacityForConversionDestination:
return DebugDataType::kTriggerAggregateStorageLimit;
case AggregatableResult::kExcessiveReportingOrigins:
return DebugDataType::kTriggerReportingOriginLimit;
case AggregatableResult::kNoMatchingImpressions:
return DebugDataType::kTriggerNoMatchingSource;
case AggregatableResult::kExcessiveAttributions:
return DebugDataType::
kTriggerAggregateAttributionsPerSourceDestinationLimit;
case AggregatableResult::kNoMatchingSourceFilterData:
return DebugDataType::kTriggerNoMatchingFilterData;
case AggregatableResult::kDeduplicated:
return DebugDataType::kTriggerAggregateDeduplicated;
case AggregatableResult::kNoHistograms:
return DebugDataType::kTriggerAggregateNoContributions;
case AggregatableResult::kInsufficientBudget:
return DebugDataType::kTriggerAggregateInsufficientBudget;
case AggregatableResult::kReportWindowPassed:
return DebugDataType::kTriggerAggregateReportWindowPassed;
case AggregatableResult::kExcessiveReports:
return DebugDataType::kTriggerAggregateExcessiveReports;
}
}
std::vector<AggregatableReportHistogramContribution>
GetAggregatableContributions(
absl::uint128 context_key_piece,
const attribution_reporting::AggregatableDebugReportingConfig::DebugData&
debug_data,
const DebugDataTypes& debug_types) {
std::vector<AggregatableReportHistogramContribution> contributions;
for (DebugDataType type : debug_types) {
if (auto iter = debug_data.find(type); iter != debug_data.end()) {
contributions.emplace_back(
iter->second.key_piece() | context_key_piece,
base::checked_cast<int32_t>(iter->second.value()),
/*filtering_id=*/std::nullopt);
}
}
return contributions;
}
bool IsAggregatableFilteringIdsEnabled() {
return base::FeatureList::IsEnabled(
attribution_reporting::features::
kAttributionReportingAggregatableFilteringIds) &&
base::FeatureList::IsEnabled(
kPrivacySandboxAggregationServiceFilteringIds);
}
} // namespace
// static
std::optional<AggregatableDebugReport> AggregatableDebugReport::Create(
base::FunctionRef<bool()> is_operation_allowed,
const StoreSourceResult& result) {
if (!base::FeatureList::IsEnabled(
attribution_reporting::features::
kAttributionAggregatableDebugReporting)) {
return std::nullopt;
}
const StorableSource& source = result.source();
const attribution_reporting::SourceAggregatableDebugReportingConfig& config =
source.registration().aggregatable_debug_reporting_config;
if (config.config().debug_data.empty() || source.is_within_fenced_frame() ||
!is_operation_allowed()) {
return std::nullopt;
}
CHECK(!source.registration().destination_set.destinations().empty());
DebugDataTypes types;
if (std::optional<DebugDataType> type = GetDebugType(result)) {
types.Put(*type);
}
if (result.destination_limit().has_value()) {
types.Put(DebugDataType::kSourceDestinationLimitReplaced);
}
return AggregatableDebugReport(
GetAggregatableContributions(config.config().key_piece,
config.config().debug_data, types),
source.common_info().source_site(),
source.common_info().reporting_origin(),
*source.registration().destination_set.destinations().begin(),
config.config().aggregation_coordinator_origin, result.source_time());
}
// static
std::optional<AggregatableDebugReport> AggregatableDebugReport::Create(
base::FunctionRef<bool()> is_operation_allowed,
const CreateReportResult& result) {
if (!base::FeatureList::IsEnabled(
attribution_reporting::features::
kAttributionAggregatableDebugReporting)) {
return std::nullopt;
}
if (absl::holds_alternative<CreateReportResult::NotRegistered>(
result.event_level_result()) &&
absl::holds_alternative<CreateReportResult::NotRegistered>(
result.aggregatable_result())) {
return std::nullopt;
}
const AttributionTrigger& trigger = result.trigger();
const attribution_reporting::AggregatableDebugReportingConfig& config =
trigger.registration().aggregatable_debug_reporting_config;
if (config.debug_data.empty() || trigger.is_within_fenced_frame() ||
!is_operation_allowed()) {
return std::nullopt;
}
DebugDataTypes types;
if (std::optional<DebugDataType> event_level_type =
GetDebugType(result.event_level_status())) {
types.Put(*event_level_type);
}
if (std::optional<DebugDataType> aggregatable_type =
GetDebugType(result.aggregatable_status())) {
types.Put(*aggregatable_type);
}
absl::uint128 context_key_piece = config.key_piece;
if (result.source()) {
context_key_piece |= result.source()->aggregatable_debug_key_piece();
}
return AggregatableDebugReport(
GetAggregatableContributions(context_key_piece, config.debug_data, types),
net::SchemefulSite(trigger.destination_origin()),
trigger.reporting_origin(),
net::SchemefulSite(trigger.destination_origin()),
config.aggregation_coordinator_origin, result.trigger_time());
}
// static
AggregatableDebugReport AggregatableDebugReport::CreateForTesting(
std::vector<AggregatableReportHistogramContribution> contributions,
net::SchemefulSite context_site,
attribution_reporting::SuitableOrigin reporting_origin,
net::SchemefulSite effective_destination,
std::optional<attribution_reporting::SuitableOrigin>
aggregation_coordinator_origin,
base::Time scheduled_report_time) {
return AggregatableDebugReport(
std::move(contributions), std::move(context_site),
std::move(reporting_origin), std::move(effective_destination),
std::move(aggregation_coordinator_origin), scheduled_report_time);
}
AggregatableDebugReport::AggregatableDebugReport(
std::vector<AggregatableReportHistogramContribution> contributions,
net::SchemefulSite context_site,
attribution_reporting::SuitableOrigin reporting_origin,
net::SchemefulSite effective_destination,
std::optional<attribution_reporting::SuitableOrigin>
aggregation_coordinator_origin,
base::Time scheduled_report_time)
: contributions_(std::move(contributions)),
context_site_(std::move(context_site)),
reporting_origin_(std::move(reporting_origin)),
effective_destination_(std::move(effective_destination)),
aggregation_coordinator_origin_(
std::move(aggregation_coordinator_origin)),
scheduled_report_time_(scheduled_report_time) {
CHECK_LE(contributions_.size(), kMaxContributions);
CHECK(base::ranges::all_of(contributions_, [](const auto& contribution) {
return attribution_reporting::IsAggregatableValueInRange(
contribution.value);
}));
}
AggregatableDebugReport::AggregatableDebugReport(AggregatableDebugReport&&) =
default;
AggregatableDebugReport& AggregatableDebugReport::operator=(
AggregatableDebugReport&&) = default;
AggregatableDebugReport::~AggregatableDebugReport() = default;
int AggregatableDebugReport::BudgetRequired() const {
base::CheckedNumeric<int64_t> budget_required =
GetTotalAggregatableValues(contributions_);
CHECK(budget_required.IsValid());
int64_t budget_required_value = budget_required.ValueOrDie();
CHECK(base::IsValueInRangeForNumericType<int>(budget_required_value));
return budget_required_value;
}
net::SchemefulSite AggregatableDebugReport::ReportingSite() const {
return net::SchemefulSite(reporting_origin_);
}
void AggregatableDebugReport::ToNull() {
// Null contributions will be padded in
// `ConstructUnencryptedTeeBasedPayload()`.
contributions_.clear();
}
GURL AggregatableDebugReport::ReportUrl() const {
static constexpr char kPath[] =
"/.well-known/attribution-reporting/debug/report-aggregate-debug";
GURL::Replacements replacements;
replacements.SetPathStr(kPath);
return reporting_origin_->GetURL().ReplaceComponents(replacements);
}
std::optional<AggregatableReportRequest>
AggregatableDebugReport::CreateAggregatableReportRequest() const {
CHECK(report_id_.is_valid());
std::optional<size_t> filtering_id_max_bytes;
if (IsAggregatableFilteringIdsEnabled()) {
filtering_id_max_bytes =
attribution_reporting::AggregatableFilteringIdsMaxBytes().value();
}
base::Value::Dict additional_fields;
SetAttributionDestination(additional_fields, effective_destination_);
return AggregatableReportRequest::Create(
AggregationServicePayloadContents(
AggregationServicePayloadContents::Operation::kHistogram,
contributions_, blink::mojom::AggregationServiceMode::kDefault,
aggregation_coordinator_origin_
? std::make_optional(**aggregation_coordinator_origin_)
: std::nullopt,
kMaxContributions, filtering_id_max_bytes),
AggregatableReportSharedInfo(
scheduled_report_time_, report_id_, reporting_origin_,
AggregatableReportSharedInfo::DebugMode::kDisabled,
std::move(additional_fields),
filtering_id_max_bytes.has_value()
? kVersionWithFlexibleContributionFiltering
: kVersion,
kApiIdentifier),
// The returned request cannot be serialized due to the null `delay_type`.
/*delay_type=*/std::nullopt);
}
} // namespace content