blob: a3307f77d8d201060101843f2cc6dda1da20184e [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 "components/attribution_reporting/aggregatable_debug_reporting_config.h"
#include <stdint.h>
#include <algorithm>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/enum_set.h"
#include "base/functional/function_ref.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "base/values.h"
#include "components/attribution_reporting/aggregatable_utils.h"
#include "components/attribution_reporting/constants.h"
#include "components/attribution_reporting/debug_types.h"
#include "components/attribution_reporting/debug_types.mojom.h"
#include "components/attribution_reporting/parsing_utils.h"
#include "third_party/abseil-cpp/absl/numeric/int128.h"
namespace attribution_reporting {
namespace {
using ::attribution_reporting::mojom::DebugDataType;
constexpr char kAggregatableDebugReporting[] = "aggregatable_debug_reporting";
constexpr char kBudget[] = "budget";
constexpr char kDebugData[] = "debug_data";
constexpr char kTypes[] = "types";
using ParseDebugDataTypeFunc =
base::FunctionRef<base::expected<DebugDataType, ParseError>(
std::string_view)>;
bool IsValueInRange(int value, std::optional<int> max_value) {
int effective_max_value = max_value.value_or(kMaxAggregatableValue);
CHECK_LE(effective_max_value, kMaxAggregatableValue);
return value > 0 && value <= effective_max_value;
}
base::expected<int, ParseError> ParseValue(const base::Value::Dict& dict,
std::optional<int> max_value) {
const base::Value* value = dict.Find(kValue);
if (!value) {
return base::unexpected(ParseError());
}
ASSIGN_OR_RETURN(int int_value, ParseInt(*value));
if (!IsValueInRange(int_value, max_value)) {
return base::unexpected(ParseError());
}
return int_value;
}
base::expected<int, ParseError> ParseBudget(const base::Value::Dict& dict) {
const base::Value* value = dict.Find(kBudget);
if (!value) {
return base::unexpected(ParseError());
}
return ParseAggregatableValue(*value);
}
base::expected<absl::uint128, ParseError> ParseKeyPiece(
const base::Value::Dict& dict) {
const base::Value* v = dict.Find(kKeyPiece);
if (!v) {
return base::unexpected(ParseError());
}
return ParseAggregationKeyPiece(*v);
}
base::expected<void, AggregatableDebugReportingConfigError>
ParseDebugDataElement(base::Value& elem,
AggregatableDebugReportingConfig::DebugData& data,
std::optional<AggregatableDebugReportingContribution>&
unspecified_contribution,
std::set<std::string>& unknown_types,
std::optional<int> max_value,
ParseDebugDataTypeFunc parse_debug_data_type) {
base::Value::Dict* dict = elem.GetIfDict();
if (!dict) {
return base::unexpected(
AggregatableDebugReportingConfigError::kDebugDataInvalid);
}
ASSIGN_OR_RETURN(
absl::uint128 key_piece,
ParseKeyPiece(*dict).transform_error([](ParseError) {
return AggregatableDebugReportingConfigError::kDebugDataKeyPieceInvalid;
}));
ASSIGN_OR_RETURN(
int int_value,
ParseValue(*dict, max_value).transform_error([](ParseError) {
return AggregatableDebugReportingConfigError::kDebugDataValueInvalid;
}));
base::Value::List* l = dict->FindList(kTypes);
if (!l || l->empty()) {
return base::unexpected(
AggregatableDebugReportingConfigError::kDebugDataTypesInvalid);
}
std::optional<AggregatableDebugReportingContribution> contribution =
AggregatableDebugReportingContribution::Create(key_piece, int_value);
CHECK(contribution.has_value());
for (base::Value& v : *l) {
std::string* s = v.GetIfString();
if (!s) {
return base::unexpected(
AggregatableDebugReportingConfigError::kDebugDataTypesInvalid);
}
if (*s == "unspecified") {
if (unspecified_contribution.has_value()) {
return base::unexpected(
AggregatableDebugReportingConfigError::kDebugDataTypesInvalid);
}
unspecified_contribution.emplace(*contribution);
continue;
}
if (auto type = parse_debug_data_type(*s); type.has_value()) {
auto [_, inserted] = data.try_emplace(*type, *contribution);
if (!inserted) {
return base::unexpected(
AggregatableDebugReportingConfigError::kDebugDataTypesInvalid);
}
} else {
auto [_, inserted] = unknown_types.emplace(std::move(*s));
if (!inserted) {
return base::unexpected(
AggregatableDebugReportingConfigError::kDebugDataTypesInvalid);
}
}
}
return base::ok();
}
base::expected<AggregatableDebugReportingConfig::DebugData,
AggregatableDebugReportingConfigError>
ParseDebugData(base::Value::Dict& dict,
std::optional<int> max_value,
const DebugDataTypes& debug_data_types,
ParseDebugDataTypeFunc parse_debug_data_type) {
base::Value* value = dict.Find(kDebugData);
if (!value) {
return {};
}
base::Value::List* l = value->GetIfList();
if (!l) {
return base::unexpected(
AggregatableDebugReportingConfigError::kDebugDataInvalid);
}
AggregatableDebugReportingConfig::DebugData data;
std::optional<AggregatableDebugReportingContribution>
unspecified_contribution;
for (std::set<std::string> unknown_types; base::Value& v : *l) {
RETURN_IF_ERROR(ParseDebugDataElement(v, data, unspecified_contribution,
unknown_types, max_value,
parse_debug_data_type));
}
if (unspecified_contribution.has_value()) {
for (DebugDataType type : debug_data_types) {
data.try_emplace(type, *unspecified_contribution);
}
}
return data;
}
void SerializeConfig(base::Value::Dict& dict,
const AggregatableDebugReportingConfig& config) {
dict.Set(kKeyPiece, HexEncodeAggregationKey(config.key_piece));
if (config.aggregation_coordinator_origin.has_value()) {
dict.Set(kAggregationCoordinatorOrigin,
config.aggregation_coordinator_origin->Serialize());
}
if (!config.debug_data.empty()) {
auto list = base::Value::List::with_capacity(config.debug_data.size());
for (const auto& [type, contribution] : config.debug_data) {
CHECK(base::IsValueInRangeForNumericType<int>(contribution.value()));
list.Append(
base::Value::Dict()
.Set(kKeyPiece, HexEncodeAggregationKey(contribution.key_piece()))
.Set(kValue, static_cast<int>(contribution.value()))
.Set(kTypes,
base::Value::List().Append(SerializeDebugDataType(type))));
}
dict.Set(kDebugData, std::move(list));
}
}
bool IsValid(int budget,
const AggregatableDebugReportingConfig::DebugData& data) {
if (!IsAggregatableBudgetInRange(budget)) {
return false;
}
return std::ranges::all_of(data, [&](const auto& p) {
return IsValueInRange(p.second.value(), budget);
});
}
base::expected<AggregatableDebugReportingConfig,
AggregatableDebugReportingConfigError>
ParseConfig(base::Value::Dict& dict,
std::optional<int> max_value,
const DebugDataTypes& debug_data_types,
ParseDebugDataTypeFunc parse_debug_data_type) {
ASSIGN_OR_RETURN(
auto key_piece, ParseKeyPiece(dict).transform_error([](ParseError) {
return AggregatableDebugReportingConfigError::kKeyPieceInvalid;
}));
ASSIGN_OR_RETURN(
auto aggregation_coordinator_origin,
ParseAggregationCoordinator(dict).transform_error([](ParseError) {
return AggregatableDebugReportingConfigError::
kAggregationCoordinatorOriginInvalid;
}));
ASSIGN_OR_RETURN(auto data, ParseDebugData(dict, max_value, debug_data_types,
parse_debug_data_type));
return AggregatableDebugReportingConfig(
key_piece, std::move(data), std::move(aggregation_coordinator_origin));
}
base::expected<SourceAggregatableDebugReportingConfig,
AggregatableDebugReportingConfigError>
ParseSourceConfig(base::Value::Dict& dict) {
base::Value* v = dict.Find(kAggregatableDebugReporting);
if (!v) {
return SourceAggregatableDebugReportingConfig();
}
base::Value::Dict* d = v->GetIfDict();
if (!d) {
return base::unexpected(
AggregatableDebugReportingConfigError::kRootInvalid);
}
ASSIGN_OR_RETURN(int budget, ParseBudget(*d), [](ParseError) {
return AggregatableDebugReportingConfigError::kBudgetInvalid;
});
ASSIGN_OR_RETURN(auto config, ParseConfig(*d, budget, SourceDebugDataTypes(),
&ParseSourceDebugDataType));
auto source_config =
SourceAggregatableDebugReportingConfig::Create(budget, std::move(config));
CHECK(source_config.has_value());
return *std::move(source_config);
}
base::expected<AggregatableDebugReportingConfig,
AggregatableDebugReportingConfigError>
ParseTriggerConfig(base::Value::Dict& dict) {
base::Value* v = dict.Find(kAggregatableDebugReporting);
if (!v) {
return AggregatableDebugReportingConfig();
}
base::Value::Dict* d = v->GetIfDict();
if (!d) {
return base::unexpected(
AggregatableDebugReportingConfigError::kRootInvalid);
}
return ParseConfig(*d, /*max_value=*/std::nullopt, TriggerDebugDataTypes(),
&ParseTriggerDebugDataType);
}
} // namespace
// static
std::optional<AggregatableDebugReportingContribution>
AggregatableDebugReportingContribution::Create(absl::uint128 key_piece,
uint32_t value) {
if (!IsValueInRange(value, /*max_value=*/std::nullopt)) {
return std::nullopt;
}
return AggregatableDebugReportingContribution(key_piece, value);
}
AggregatableDebugReportingContribution::AggregatableDebugReportingContribution(
absl::uint128 key_piece,
uint32_t value)
: key_piece_(key_piece), value_(value) {
CHECK(IsValid());
}
bool AggregatableDebugReportingContribution::IsValid() const {
return IsValueInRange(value_, /*max_value=*/std::nullopt);
}
absl::uint128 AggregatableDebugReportingContribution::key_piece() const {
CHECK(IsValid());
return key_piece_;
}
uint32_t AggregatableDebugReportingContribution::value() const {
CHECK(IsValid());
return value_;
}
// static
base::expected<AggregatableDebugReportingConfig,
AggregatableDebugReportingConfigError>
AggregatableDebugReportingConfig::Parse(base::Value::Dict& dict) {
auto parsed = ParseTriggerConfig(dict);
if (!parsed.has_value()) {
base::UmaHistogramEnumeration(
"Conversions.AggregatableDebugReporting.TriggerRegistrationError",
parsed.error());
}
return parsed;
}
AggregatableDebugReportingConfig::AggregatableDebugReportingConfig() = default;
AggregatableDebugReportingConfig::AggregatableDebugReportingConfig(
absl::uint128 key_piece,
DebugData debug_data,
std::optional<SuitableOrigin> aggregation_coordinator_origin)
: key_piece(key_piece),
debug_data(std::move(debug_data)),
aggregation_coordinator_origin(
std::move(aggregation_coordinator_origin)) {}
AggregatableDebugReportingConfig::~AggregatableDebugReportingConfig() = default;
AggregatableDebugReportingConfig::AggregatableDebugReportingConfig(
const AggregatableDebugReportingConfig&) = default;
AggregatableDebugReportingConfig::AggregatableDebugReportingConfig(
AggregatableDebugReportingConfig&&) = default;
AggregatableDebugReportingConfig& AggregatableDebugReportingConfig::operator=(
const AggregatableDebugReportingConfig&) = default;
AggregatableDebugReportingConfig& AggregatableDebugReportingConfig::operator=(
AggregatableDebugReportingConfig&&) = default;
void AggregatableDebugReportingConfig::Serialize(
base::Value::Dict& dict) const {
base::Value::Dict body;
SerializeConfig(body, *this);
dict.Set(kAggregatableDebugReporting, std::move(body));
}
// static
base::expected<SourceAggregatableDebugReportingConfig,
AggregatableDebugReportingConfigError>
SourceAggregatableDebugReportingConfig::Parse(base::Value::Dict& dict) {
auto parsed = ParseSourceConfig(dict);
if (!parsed.has_value()) {
base::UmaHistogramEnumeration(
"Conversions.AggregatableDebugReporting.SourceRegistrationError",
parsed.error());
}
return parsed;
}
// static
std::optional<SourceAggregatableDebugReportingConfig>
SourceAggregatableDebugReportingConfig::Create(
int budget,
AggregatableDebugReportingConfig config) {
if (!IsValid(budget, config.debug_data)) {
return std::nullopt;
}
return SourceAggregatableDebugReportingConfig(budget, std::move(config));
}
SourceAggregatableDebugReportingConfig::
SourceAggregatableDebugReportingConfig() = default;
SourceAggregatableDebugReportingConfig::SourceAggregatableDebugReportingConfig(
int budget,
AggregatableDebugReportingConfig config)
: budget_(budget), config_(std::move(config)) {
CHECK(IsValid(budget_, config_.debug_data));
}
SourceAggregatableDebugReportingConfig::
~SourceAggregatableDebugReportingConfig() = default;
SourceAggregatableDebugReportingConfig::SourceAggregatableDebugReportingConfig(
const SourceAggregatableDebugReportingConfig&) = default;
SourceAggregatableDebugReportingConfig::SourceAggregatableDebugReportingConfig(
SourceAggregatableDebugReportingConfig&&) = default;
SourceAggregatableDebugReportingConfig&
SourceAggregatableDebugReportingConfig::operator=(
const SourceAggregatableDebugReportingConfig&) = default;
SourceAggregatableDebugReportingConfig&
SourceAggregatableDebugReportingConfig::operator=(
SourceAggregatableDebugReportingConfig&&) = default;
void SourceAggregatableDebugReportingConfig::Serialize(
base::Value::Dict& dict) const {
// `budget_` is 0 when aggregatable debug reporting is not opted in.
if (budget_ == 0) {
return;
}
base::Value::Dict body;
body.Set(kBudget, budget_);
SerializeConfig(body, config_);
dict.Set(kAggregatableDebugReporting, std::move(body));
}
} // namespace attribution_reporting