910e62b5创建于 1月15日历史提交
// 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 <algorithm>
#include <optional>
#include <utility>
#include <variant>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/enum_set.h"
#include "base/functional/function_ref.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.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/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/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/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[] = "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::kInsufficientNamedBudget:
      return DebugDataType::kTriggerAggregateInsufficientNamedBudget;
    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;
}

}  // namespace

// static
std::optional<AggregatableDebugReport> AggregatableDebugReport::Create(
    base::FunctionRef<bool()> is_operation_allowed,
    const StoreSourceResult& result) {
  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 (std::holds_alternative<CreateReportResult::NotRegistered>(
          result.event_level_result()) &&
      std::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(std::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());

  base::Value::Dict additional_fields;
  SetAttributionDestination(additional_fields, effective_destination_);
  return AggregatableReportRequest::Create(
      AggregationServicePayloadContents(
          AggregationServicePayloadContents::Operation::kHistogram,
          contributions_,
          aggregation_coordinator_origin_
              ? std::make_optional(**aggregation_coordinator_origin_)
              : std::nullopt,
          kMaxContributions,
          attribution_reporting::AggregatableFilteringIdsMaxBytes().value()),
      AggregatableReportSharedInfo(
          scheduled_report_time_, report_id_, reporting_origin_,
          AggregatableReportSharedInfo::DebugMode::kDisabled,
          std::move(additional_fields), kVersion, kApiIdentifier),
      // The returned request cannot be serialized due to the null `delay_type`.
      /*delay_type=*/std::nullopt);
}

}  // namespace content