blob: e505088fba1888fad2bac57061cabd374409d905 [file] [log] [blame]
// Copyright 2022 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/interop/parser.h"
#include <stddef.h>
#include <stdint.h>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
#include "base/check.h"
#include "base/functional/function_ref.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/abseil_string_number_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "base/values.h"
#include "components/attribution_reporting/parsing_utils.h"
#include "components/attribution_reporting/privacy_math.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "components/attribution_reporting/test_utils.h"
#include "content/browser/attribution_reporting/attribution_config.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_version.h"
#include "net/http/structured_headers.h"
#include "services/network/public/mojom/attribution.mojom.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "third_party/abseil-cpp/absl/numeric/int128.h"
#include "url/gurl.h"
namespace content {
namespace {
using ::attribution_reporting::SuitableOrigin;
using ::network::mojom::AttributionReportingEligibility;
constexpr char kEligibleKey[] = "Attribution-Reporting-Eligible";
constexpr char kPayloadKey[] = "payload";
constexpr char kRegistrationRequestKey[] = "registration_request";
constexpr char kReportTimeKey[] = "report_time";
constexpr char kReportUrlKey[] = "report_url";
constexpr char kReportsKey[] = "reports";
constexpr char kResponseKey[] = "response";
constexpr char kResponsesKey[] = "responses";
constexpr char kTimestampKey[] = "timestamp";
using Context = std::variant<std::string_view, size_t>;
using ContextPath = std::vector<Context>;
std::string TimeAsUnixMillisecondString(base::Time time) {
return base::NumberToString(
(time - base::Time::UnixEpoch()).InMilliseconds());
}
class ScopedContext {
public:
ScopedContext(ContextPath& path, Context context) : path_(path) {
path_->push_back(context);
}
~ScopedContext() { path_->pop_back(); }
ScopedContext(const ScopedContext&) = delete;
ScopedContext(ScopedContext&&) = delete;
ScopedContext& operator=(const ScopedContext&) = delete;
ScopedContext& operator=(ScopedContext&&) = delete;
private:
const raw_ref<ContextPath> path_;
};
std::ostream& operator<<(std::ostream& out, const ContextPath& path) {
if (path.empty()) {
return out << "input root";
}
for (Context context : path) {
std::visit(absl::Overload{
[&](std::string_view key) { out << "[\"" << key << "\"]"; },
[&](size_t index) { out << '[' << index << ']'; },
},
context);
}
return out;
}
// Writes a newline on destruction.
class ErrorWriter {
public:
explicit ErrorWriter(std::ostringstream& stream) : stream_(stream) {}
~ErrorWriter() { *stream_ << std::endl; }
ErrorWriter(const ErrorWriter&) = delete;
ErrorWriter(ErrorWriter&&) = default;
ErrorWriter& operator=(const ErrorWriter&) = delete;
ErrorWriter& operator=(ErrorWriter&&) = delete;
std::ostringstream& operator*() { return *stream_; }
private:
const raw_ref<std::ostringstream> stream_;
};
class AttributionInteropParser {
public:
AttributionInteropParser() = default;
~AttributionInteropParser() = default;
AttributionInteropParser(const AttributionInteropParser&) = delete;
AttributionInteropParser(AttributionInteropParser&&) = delete;
AttributionInteropParser& operator=(const AttributionInteropParser&) = delete;
AttributionInteropParser& operator=(AttributionInteropParser&&) = delete;
base::expected<std::vector<AttributionSimulationEvent>, std::string>
ParseInput(base::Value::Dict input) && {
std::vector<AttributionSimulationEvent> events;
static constexpr char kKeyRegistrations[] = "registrations";
if (base::Value* registrations = input.Find(kKeyRegistrations)) {
auto context = PushContext(kKeyRegistrations);
int64_t request_id = 0;
ParseListOfDicts(registrations, [&](base::Value::Dict registration) {
ParseRegistration(std::move(registration), events, request_id++);
});
}
if (has_error_) {
return base::unexpected(error_stream_.str());
}
return events;
}
base::expected<AttributionInteropOutput, std::string> ParseOutput(
base::Value::Dict dict) && {
AttributionInteropOutput output;
{
auto context = PushContext(kReportsKey);
ParseListOfDicts(dict.Find(kReportsKey), [&](base::Value::Dict report) {
ParseReport(std::move(report), output.reports);
});
}
if (has_error_) {
return base::unexpected(error_stream_.str());
}
return output;
}
base::expected<void, std::string> ParseConfig(
base::Value::Dict& dict,
AttributionInteropConfig& interop_config,
bool required) && {
interop_config.needs_cross_app_web =
ParseBool(dict, "needs_cross_app_web").value_or(false);
interop_config.needs_retry_after_new_navigation =
ParseNavigationRetryAttempt(dict);
AttributionConfig& config = interop_config.attribution_config;
ParseInt(dict, "max_sources_per_origin", config.max_sources_per_origin,
required);
ParseInt(dict, "max_destinations_per_source_site_reporting_site",
config.max_destinations_per_source_site_reporting_site, required);
ParseInt(dict, "max_destinations_per_rate_limit_window_reporting_site",
config.destination_rate_limit.max_per_reporting_site, required);
ParseInt(dict, "max_destinations_per_rate_limit_window",
config.destination_rate_limit.max_total, required);
int destination_rate_limit_window_in_minutes;
if (ParseInt(dict, "destination_rate_limit_window_in_minutes",
destination_rate_limit_window_in_minutes, required)) {
config.destination_rate_limit.rate_limit_window =
base::Minutes(destination_rate_limit_window_in_minutes);
}
ParseInt(dict, "max_destinations_per_reporting_site_per_day",
config.destination_rate_limit.max_per_reporting_site_per_day,
required);
ParseDouble(dict, "max_event_level_channel_capacity_navigation",
config.privacy_math_config.max_channel_capacity_navigation,
required);
ParseDouble(dict, "max_event_level_channel_capacity_event",
config.privacy_math_config.max_channel_capacity_event,
required);
ParseDouble(
dict, "max_event_level_channel_capacity_scopes_navigation",
config.privacy_math_config.max_channel_capacity_scopes_navigation,
required);
ParseDouble(dict, "max_event_level_channel_capacity_scopes_event",
config.privacy_math_config.max_channel_capacity_scopes_event,
required);
ParseUInt32(dict, "max_trigger_state_cardinality",
interop_config.max_trigger_state_cardinality, required);
int rate_limit_time_window_in_days;
if (ParseInt(dict, "rate_limit_time_window_in_days",
rate_limit_time_window_in_days, required)) {
config.rate_limit.time_window =
base::Days(rate_limit_time_window_in_days);
}
ParseInt64(dict, "rate_limit_max_source_registration_reporting_origins",
config.rate_limit.max_source_registration_reporting_origins,
required);
ParseInt64(dict, "rate_limit_max_attribution_reporting_origins",
config.rate_limit.max_attribution_reporting_origins, required);
ParseInt64(dict, "rate_limit_max_attributions",
config.rate_limit.max_attributions, required);
ParseInt(dict, "rate_limit_max_reporting_origins_per_source_reporting_site",
config.rate_limit.max_reporting_origins_per_source_reporting_site,
required);
int rate_limit_origins_per_site_window_in_days;
if (ParseInt(dict, "rate_limit_origins_per_site_window_in_days",
rate_limit_origins_per_site_window_in_days, required)) {
config.rate_limit.origins_per_site_window =
base::Days(rate_limit_origins_per_site_window_in_days);
}
ParseInt(dict, "max_event_level_reports_per_destination",
config.event_level_limit.max_reports_per_destination, required);
ParseDouble(dict, "max_settable_event_level_epsilon",
interop_config.max_event_level_epsilon, required);
ParseInt(dict, "max_aggregatable_reports_per_destination",
config.aggregate_limit.max_reports_per_destination, required);
int aggregatable_report_min_delay;
if (ParseInt(dict, "aggregatable_report_min_delay",
aggregatable_report_min_delay, required,
/*allow_zero=*/true)) {
config.aggregate_limit.min_delay =
base::Minutes(aggregatable_report_min_delay);
}
int aggregatable_report_delay_span;
if (ParseInt(dict, "aggregatable_report_delay_span",
aggregatable_report_delay_span, required,
/*allow_zero=*/true)) {
config.aggregate_limit.delay_span =
base::Minutes(aggregatable_report_delay_span);
}
int max_aggregatable_debug_budget_per_context_site;
if (ParseInt(dict, "max_aggregatable_debug_budget_per_context_site",
max_aggregatable_debug_budget_per_context_site, required,
/*allow_zero=*/false)) {
config.aggregatable_debug_rate_limit.max_budget_per_context_site =
max_aggregatable_debug_budget_per_context_site;
}
int max_aggregatable_debug_reports_per_source;
if (ParseInt(dict, "max_aggregatable_debug_reports_per_source",
max_aggregatable_debug_reports_per_source, required,
/*allow_zero=*/false)) {
config.aggregatable_debug_rate_limit.max_reports_per_source =
max_aggregatable_debug_reports_per_source;
}
if (int max_aggregatable_reports_per_source;
ParseInt(dict, "max_aggregatable_reports_per_source",
max_aggregatable_reports_per_source, required,
/*allow_zero=*/false)) {
config.aggregate_limit.max_aggregatable_reports_per_source =
max_aggregatable_reports_per_source;
}
{
static constexpr char kAggregationCoordinatorOrigins[] =
"aggregation_coordinator_origins";
auto context = PushContext(kAggregationCoordinatorOrigins);
base::Value* values = dict.Find(kAggregationCoordinatorOrigins);
if (values) {
// Ensure that the list is replaced, not unioned, when being merged.
interop_config.aggregation_coordinator_origins.clear();
}
ParseList(
values,
[&](base::Value v) {
if (std::optional<SuitableOrigin> origin = ParseOrigin(&v)) {
interop_config.aggregation_coordinator_origins.emplace_back(
*std::move(origin));
}
},
required,
/*allow_empty=*/false);
}
if (has_error_) {
return base::unexpected(std::move(error_stream_).str());
}
return base::ok();
}
private:
std::ostringstream error_stream_;
ContextPath context_path_;
bool has_error_ = false;
[[nodiscard]] ScopedContext PushContext(Context context) {
return ScopedContext(context_path_, context);
}
ErrorWriter Error() {
has_error_ = true;
error_stream_ << context_path_ << ": ";
return ErrorWriter(error_stream_);
}
void ParseList(base::Value* values,
base::FunctionRef<void(base::Value)> parse_element,
bool required = true,
bool allow_empty = true) {
if (!values) {
if (required) {
*Error() << "must be present";
}
return;
}
base::Value::List* list = values->GetIfList();
if (!list) {
*Error() << "must be a list";
return;
}
if (list->empty() && !allow_empty) {
*Error() << "must be non-empty";
}
for (size_t index = 0; auto& value : *list) {
auto index_context = PushContext(index);
parse_element(std::move(value));
index++;
}
}
void ParseListOfDicts(
base::Value* values,
base::FunctionRef<void(base::Value::Dict)> parse_element) {
ParseList(values, [&](base::Value value) {
if (!EnsureDictionary(&value)) {
return;
}
parse_element(std::move(value).TakeDict());
});
}
void ParseRegistration(base::Value::Dict dict,
std::vector<AttributionSimulationEvent>& events,
int64_t request_id) {
const base::Time time =
ParseTime(dict, kTimestampKey,
/*previous_time=*/events.empty() ? base::Time::Min()
: events.back().time,
/*strictly_greater=*/true);
if (dict.contains("connection")) {
bool connected = *dict.FindBool("connection");
events.emplace_back(time,
AttributionSimulationEvent::Connection(connected));
return;
}
if (dict.FindBool("navigation").value_or(false)) {
events.emplace_back(time, AttributionSimulationEvent::Navigation());
return;
}
std::optional<SuitableOrigin> context_origin;
AttributionReportingEligibility eligibility;
bool fenced = false;
ParseDict(dict, kRegistrationRequestKey, [&](base::Value::Dict reg_req) {
context_origin = ParseOrigin(reg_req, "context_origin");
eligibility = ParseEligibility(reg_req);
fenced = ParseBool(reg_req, "fenced").value_or(false);
});
if (has_error_) {
return;
}
events.emplace_back(
time, AttributionSimulationEvent::StartRequest(
request_id, *std::move(context_origin), eligibility, fenced));
std::optional<base::Time> default_response_time = time;
{
auto context = PushContext(kResponsesKey);
ParseListOfDicts(
dict.Find(kResponsesKey), [&](base::Value::Dict response) {
auto url = ParseUrl(response, "url");
const bool debug_permission = ParseDebugPermission(response);
attribution_reporting::RandomizedResponse randomized_response =
ParseRandomizedResponse(response);
base::flat_set<int> null_aggregatable_reports_days =
ParseNullAggregatableReportsDays(response);
// The timestamp is required for all but the first response. If
// omitted on the first response, it defaults to the registration
// time.
base::Time response_time = ParseTime(
response, kTimestampKey, events.back().time,
/*strictly_greater=*/!default_response_time.has_value(),
default_response_time);
default_response_time = std::nullopt;
if (has_error_) {
return;
}
ParseDict(
response, kResponseKey, [&](base::Value::Dict response_dict) {
net::HttpResponseHeaders::Builder builder(
net::HttpVersion(1, 1),
/*status=*/"200 OK");
for (auto [header, value] : response_dict) {
if (const std::string* str = value.GetIfString()) {
builder.AddHeader(header, *str);
} else {
std::optional<std::string> json = base::WriteJson(value);
CHECK(json.has_value());
// The string must outlive the call to
// `net::HttpResponseHeaders::Build()`, so put it back in
// the dict.
value = base::Value(*std::move(json));
builder.AddHeader(header, value.GetString());
}
}
events.emplace_back(
response_time,
AttributionSimulationEvent::Response(
request_id, std::move(url), builder.Build(),
std::move(randomized_response),
std::move(null_aggregatable_reports_days),
debug_permission));
});
});
}
// The request ends at the time of the last response, if any; otherwise it
// ends at the registration time.
events.emplace_back(events.back().time,
AttributionSimulationEvent::EndRequest(request_id));
}
void ParseReport(base::Value::Dict dict,
std::vector<AttributionInteropOutput::Report>& reports) {
AttributionInteropOutput::Report report;
report.time =
ParseTime(dict, kReportTimeKey,
/*previous_time=*/reports.empty() ? base::Time::Min()
: reports.back().time,
/*strictly_greater=*/false);
if (const std::string* url = dict.FindString(kReportUrlKey)) {
report.url = GURL(*url);
}
if (!report.url.is_valid()) {
auto context = PushContext(kReportUrlKey);
*Error() << "must be a valid URL";
}
if (std::optional<base::Value> payload = dict.Extract(kPayloadKey)) {
report.payload = *std::move(payload);
} else {
auto context = PushContext(kPayloadKey);
*Error() << "required";
}
if (!has_error_) {
reports.push_back(std::move(report));
}
}
std::optional<SuitableOrigin> ParseOrigin(const base::Value::Dict& dict,
std::string_view key) {
auto context = PushContext(key);
return ParseOrigin(dict.Find(key));
}
std::optional<SuitableOrigin> ParseOrigin(const base::Value* v) {
std::optional<SuitableOrigin> origin;
if (v) {
if (const std::string* s = v->GetIfString()) {
origin = SuitableOrigin::Deserialize(*s);
}
}
if (!origin.has_value()) {
*Error() << "must be a valid, secure origin";
}
return origin;
}
GURL ParseUrl(const base::Value::Dict& dict, std::string_view key) {
auto context = PushContext(key);
GURL url;
if (const std::string* s = dict.FindString(key)) {
url = GURL(*s);
}
if (!url.is_valid()) {
*Error() << "must be a valid URL";
}
return url;
}
base::Time ParseTime(const base::Value::Dict& dict,
std::string_view key,
base::Time previous_time,
bool strictly_greater,
std::optional<base::Time> if_absent = std::nullopt) {
auto context = PushContext(key);
base::Time time;
if (const std::string* v = dict.FindString(key)) {
if (int64_t milliseconds; base::StringToInt64(*v, &milliseconds)) {
time = base::Time::UnixEpoch() + base::Milliseconds(milliseconds);
}
} else if (if_absent.has_value()) {
time = *if_absent;
}
if (!time.is_null() && !time.is_inf()) {
if (strictly_greater && time <= previous_time) {
*Error() << "must be greater than previous time";
} else if (!strictly_greater && time < previous_time) {
*Error() << "must be greater than or equal to previous time";
}
return time;
}
*Error() << "must be an integer number of milliseconds since the Unix "
"epoch formatted as a base-10 string";
return base::Time();
}
std::optional<bool> ParseBool(const base::Value::Dict& dict,
std::string_view key) {
auto context = PushContext(key);
const base::Value* v = dict.Find(key);
if (!v) {
return std::nullopt;
}
if (!v->is_bool()) {
*Error() << "must be a bool";
return std::nullopt;
}
return v->GetBool();
}
bool ParseDebugPermission(const base::Value::Dict& dict) {
return ParseBool(dict, "debug_permission").value_or(false);
}
// TODO(apaseltiner): Consider moving this for general use to
// services/network/attribution/request_headers_internal.h.
AttributionReportingEligibility ParseEligibility(
const base::Value::Dict& dict) {
static constexpr char kNavigationSource[] = "navigation-source";
static constexpr char kEventSource[] = "event-source";
static constexpr char kTrigger[] = "trigger";
const std::string* v = dict.FindString(kEligibleKey);
if (!v) {
return AttributionReportingEligibility::kUnset;
}
auto context = PushContext(kEligibleKey);
auto structured_dict = net::structured_headers::ParseDictionary(*v);
if (!structured_dict.has_value()) {
*Error() << "must be a structured dictionary";
return AttributionReportingEligibility::kEmpty;
}
const bool navigation_source = structured_dict->contains(kNavigationSource);
const bool event_source = structured_dict->contains(kEventSource);
const bool trigger = structured_dict->contains(kTrigger);
if (navigation_source && (event_source || trigger)) {
*Error() << kNavigationSource << " is mutually exclusive with "
<< kEventSource << " and " << kTrigger;
return AttributionReportingEligibility::kEmpty;
}
if (event_source && trigger) {
return AttributionReportingEligibility::kEventSourceOrTrigger;
}
if (event_source) {
return AttributionReportingEligibility::kEventSource;
}
if (trigger) {
return AttributionReportingEligibility::kTrigger;
}
if (navigation_source) {
return AttributionReportingEligibility::kNavigationSource;
}
return AttributionReportingEligibility::kEmpty;
}
void ParseFakeReport(
const base::Value::Dict& dict,
std::vector<attribution_reporting::FakeEventLevelReport>& fake_reports) {
std::optional<uint32_t> trigger_data;
{
static constexpr char kTriggerData[] = "trigger_data";
auto context = PushContext(kTriggerData);
if (const base::Value* v = dict.Find(kTriggerData)) {
auto result = attribution_reporting::ParseUint32(*v);
if (result.has_value()) {
trigger_data = *result;
}
}
if (!trigger_data.has_value()) {
*Error() << "must be a uint32";
}
}
int report_window_index = -1;
{
static constexpr char kReportWindowIndex[] = "report_window_index";
auto context = PushContext(kReportWindowIndex);
report_window_index = dict.FindInt(kReportWindowIndex).value_or(-1);
if (report_window_index < 0) {
*Error() << "must be a non-negative integer";
}
}
if (!has_error_) {
fake_reports.emplace_back(*trigger_data, report_window_index);
}
}
attribution_reporting::RandomizedResponse ParseRandomizedResponse(
base::Value::Dict& dict) {
attribution_reporting::RandomizedResponse randomized_response;
static constexpr char kRandomizedResponse[] = "randomized_response";
if (base::Value* v = dict.Find(kRandomizedResponse); v && !v->is_none()) {
auto context = PushContext(kRandomizedResponse);
std::vector<attribution_reporting::FakeEventLevelReport>& fake_reports =
randomized_response.emplace();
ParseListOfDicts(v, [&](base::Value::Dict dict) {
ParseFakeReport(dict, fake_reports);
});
}
return randomized_response;
}
base::flat_set<int> ParseNullAggregatableReportsDays(
base::Value::Dict& dict) {
base::flat_set<int> null_aggregatable_reports_days;
static constexpr char kNullAggregatableReports[] =
"null_aggregatable_reports_days";
if (base::Value* v = dict.Find(kNullAggregatableReports);
v && !v->is_none()) {
auto context = PushContext(kNullAggregatableReports);
ParseList(v, [&](base::Value value) {
std::optional<int> int_value = value.GetIfInt();
if (!int_value || *int_value < 0) {
*Error() << "must be a non-negative integer";
} else {
null_aggregatable_reports_days.emplace(*int_value);
}
});
}
return null_aggregatable_reports_days;
}
std::optional<std::string> ParseNavigationRetryAttempt(
const base::Value::Dict& dict) {
if (const std::string* retry_config =
dict.FindString("retry_after_new_navigation")) {
if (*retry_config == "first_retry" || *retry_config == "second_retry" ||
*retry_config == "third_retry") {
return *retry_config;
}
}
return std::nullopt;
}
bool ParseDict(base::Value::Dict& value,
std::string_view key,
base::FunctionRef<void(base::Value::Dict)> parse_dict) {
auto context = PushContext(key);
base::Value* dict = value.Find(key);
if (!EnsureDictionary(dict)) {
return false;
}
parse_dict(std::move(*dict).TakeDict());
return true;
}
bool EnsureDictionary(const base::Value* value) {
if (!value) {
*Error() << "must be present";
return false;
}
if (!value->is_dict()) {
*Error() << "must be a dictionary";
return false;
}
return true;
}
// Returns true if `key` is present in `dict` and the integer is parsed
// successfully.
template <typename T>
bool ParseInteger(const base::Value::Dict& dict,
std::string_view key,
T& result,
bool (*convert_func)(std::string_view, T*),
bool required,
bool allow_zero) {
auto context = PushContext(key);
const base::Value* value = dict.Find(key);
if (value) {
const std::string* s = value->GetIfString();
if (s && convert_func(*s, &result) &&
(result > 0 || (result == 0 && allow_zero))) {
return true;
}
} else if (!required) {
return false;
}
if (allow_zero) {
*Error() << "must be a non-negative integer formatted as base-10 string";
} else {
*Error() << "must be a positive integer formatted as base-10 string";
}
return false;
}
bool ParseInt(const base::Value::Dict& dict,
std::string_view key,
int& result,
bool required,
bool allow_zero = false) {
return ParseInteger(dict, key, result, &base::StringToInt, required,
allow_zero);
}
bool ParseInt64(const base::Value::Dict& dict,
std::string_view key,
int64_t& result,
bool required,
bool allow_zero = false) {
return ParseInteger(dict, key, result, &base::StringToInt64, required,
allow_zero);
}
bool ParseUInt32(const base::Value::Dict& dict,
std::string_view key,
uint32_t& result,
bool required,
bool allow_zero = false) {
int64_t result_64;
// This works because `ParseInteger()` only accepts positive values, and
// uint32 and [0, INT64_MAX] encompasses the same values.
if (ParseInteger(dict, key, result_64, &base::StringToInt64, required,
allow_zero)) {
if (base::internal::IsValueInRangeForNumericType<uint32_t>(result_64)) {
result = static_cast<uint32_t>(result_64);
return true;
} else {
auto context = PushContext(key);
*Error() << "must be representable by an unsigned 32-bit integer";
}
}
return false;
}
void ParseDouble(const base::Value::Dict& dict,
std::string_view key,
double& result,
bool required) {
auto context = PushContext(key);
const base::Value* value = dict.Find(key);
if (value) {
const std::string* s = value->GetIfString();
if (s) {
if (*s == "inf") {
result = std::numeric_limits<double>::infinity();
return;
}
if (base::StringToDouble(*s, &result) && result >= 0) {
return;
}
}
} else if (!required) {
return;
}
*Error() << "must be \"inf\" or a non-negative double formated as a "
"base-10 string";
}
};
} // namespace
AttributionSimulationEvent::AttributionSimulationEvent(base::Time time,
Data data)
: time(time), data(std::move(data)) {}
AttributionSimulationEvent::~AttributionSimulationEvent() = default;
AttributionSimulationEvent::AttributionSimulationEvent(
AttributionSimulationEvent&&) = default;
AttributionSimulationEvent& AttributionSimulationEvent::operator=(
AttributionSimulationEvent&&) = default;
AttributionSimulationEvent::Response::Response(
int64_t request_id,
GURL url,
scoped_refptr<net::HttpResponseHeaders> response_headers,
attribution_reporting::RandomizedResponse randomized_response,
base::flat_set<int> null_aggregatable_reports_days,
bool debug_permission)
: request_id(request_id),
url(std::move(url)),
response_headers(std::move(response_headers)),
randomized_response(std::move(randomized_response)),
null_aggregatable_reports_days(std::move(null_aggregatable_reports_days)),
debug_permission(debug_permission) {}
AttributionSimulationEvent::Response::~Response() = default;
AttributionSimulationEvent::Response::Response(Response&&) = default;
AttributionSimulationEvent::Response&
AttributionSimulationEvent::Response::operator=(Response&&) = default;
base::expected<std::vector<AttributionSimulationEvent>, std::string>
ParseAttributionInteropInput(base::Value::Dict input) {
return AttributionInteropParser().ParseInput(std::move(input));
}
base::expected<AttributionInteropConfig, std::string>
ParseAttributionInteropConfig(base::Value::Dict dict) {
AttributionInteropConfig config;
RETURN_IF_ERROR(
AttributionInteropParser().ParseConfig(dict, config, /*required=*/true));
return config;
}
base::expected<void, std::string> MergeAttributionInteropConfig(
base::Value::Dict dict,
AttributionInteropConfig& config) {
return AttributionInteropParser().ParseConfig(dict, config,
/*required=*/false);
}
// static
base::expected<AttributionInteropOutput, std::string>
AttributionInteropOutput::Parse(base::Value::Dict dict) {
return AttributionInteropParser().ParseOutput(std::move(dict));
}
AttributionInteropOutput::AttributionInteropOutput() = default;
AttributionInteropOutput::~AttributionInteropOutput() = default;
AttributionInteropOutput::AttributionInteropOutput(AttributionInteropOutput&&) =
default;
AttributionInteropOutput& AttributionInteropOutput::operator=(
AttributionInteropOutput&&) = default;
AttributionInteropOutput::Report::Report() = default;
AttributionInteropOutput::Report::Report(base::Time time,
GURL url,
base::Value payload)
: time(time), url(std::move(url)), payload(std::move(payload)) {}
AttributionInteropOutput::Report::Report(const Report& other)
: Report(other.time, other.url, other.payload.Clone()) {}
base::Value::Dict AttributionInteropOutput::Report::ToJson() const {
return base::Value::Dict()
.Set(kReportTimeKey, TimeAsUnixMillisecondString(time))
.Set(kReportUrlKey, url.spec())
.Set(kPayloadKey, payload.Clone());
}
base::Value::Dict AttributionInteropOutput::ToJson() const {
base::Value::List report_list;
for (const auto& report : reports) {
report_list.Append(report.ToJson());
}
return base::Value::Dict().Set(kReportsKey, std::move(report_list));
}
AttributionInteropOutput::Report& AttributionInteropOutput::Report::operator=(
const Report& other) {
time = other.time;
url = other.url;
payload = other.payload.Clone();
return *this;
}
std::ostream& operator<<(std::ostream& out,
const AttributionInteropOutput::Report& report) {
return out << report.ToJson();
}
std::ostream& operator<<(std::ostream& out,
const AttributionInteropOutput& output) {
return out << output.ToJson();
}
// static
base::expected<AttributionInteropRun, std::string> AttributionInteropRun::Parse(
base::Value::Dict dict,
const AttributionInteropConfig& default_config) {
AttributionInteropRun run;
run.config = default_config;
if (base::Value* api_config = dict.Find("api_config")) {
base::Value::Dict* config_dict = api_config->GetIfDict();
if (!config_dict) {
return base::unexpected("api_config must be a dict");
}
RETURN_IF_ERROR(
MergeAttributionInteropConfig(std::move(*config_dict), run.config));
}
std::optional<base::Value> input = dict.Extract("input");
if (!input.has_value() || !input->is_dict()) {
return base::unexpected("input must be a dict");
}
ASSIGN_OR_RETURN(run.events,
ParseAttributionInteropInput(std::move(*input).TakeDict()));
return run;
}
AttributionInteropRun::AttributionInteropRun() = default;
AttributionInteropRun::~AttributionInteropRun() = default;
AttributionInteropRun::AttributionInteropRun(AttributionInteropRun&&) = default;
AttributionInteropRun& AttributionInteropRun::operator=(
AttributionInteropRun&&) = default;
AttributionInteropConfig::AttributionInteropConfig() = default;
AttributionInteropConfig::~AttributionInteropConfig() = default;
AttributionInteropConfig::AttributionInteropConfig(
const AttributionInteropConfig&) = default;
AttributionInteropConfig& AttributionInteropConfig::operator=(
const AttributionInteropConfig&) = default;
AttributionInteropConfig::AttributionInteropConfig(AttributionInteropConfig&&) =
default;
AttributionInteropConfig& AttributionInteropConfig::operator=(
AttributionInteropConfig&&) = default;
} // namespace content