blob: 008b8a6d7f1e33ab2a44c2807a33572ac2fd4b34 [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 "components/attribution_reporting/source_registration.h"
#include <stdint.h>
#include <optional>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/json/json_reader.h"
#include "base/metrics/histogram_functions.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/aggregatable_debug_reporting_config.h"
#include "components/attribution_reporting/aggregatable_named_budget_defs.h"
#include "components/attribution_reporting/aggregation_keys.h"
#include "components/attribution_reporting/attribution_scopes_data.h"
#include "components/attribution_reporting/constants.h"
#include "components/attribution_reporting/destination_set.h"
#include "components/attribution_reporting/event_level_epsilon.h"
#include "components/attribution_reporting/event_report_windows.h"
#include "components/attribution_reporting/filters.h"
#include "components/attribution_reporting/max_event_level_reports.h"
#include "components/attribution_reporting/parsing_utils.h"
#include "components/attribution_reporting/source_registration_error.mojom.h"
#include "components/attribution_reporting/source_type.mojom.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "components/attribution_reporting/trigger_config.h"
#include "mojo/public/cpp/bindings/default_construct_tag.h"
namespace attribution_reporting {
namespace {
using ::attribution_reporting::mojom::SourceRegistrationError;
using ::attribution_reporting::mojom::SourceType;
base::TimeDelta AdjustExpiry(base::TimeDelta expiry, SourceType source_type) {
switch (source_type) {
case SourceType::kNavigation:
return expiry;
case SourceType::kEvent:
return expiry.RoundToMultiple(base::Days(1));
}
}
void RecordFeatureUsage(const SourceRegistration& result) {
base::UmaHistogramExactLinear(
"Conversions.ScopesPerSourceRegistration",
result.attribution_scopes_data.has_value()
? result.attribution_scopes_data->attribution_scopes_set()
.scopes()
.size()
: 0,
/*exclusive_max=*/attribution_reporting::kMaxScopesPerSource + 1);
base::UmaHistogramExactLinear(
"Conversions.NamedBudgetsPerSourceRegistration",
result.aggregatable_named_budget_defs.budgets().size(),
/*exclusive_max=*/25 + 1);
static_assert(attribution_reporting::kMaxAggregatableNamedBudgetsPerSource ==
25);
base::UmaHistogramEnumeration("Conversions.TriggerDataMatchingRegistration",
result.trigger_data_matching);
}
void RecordSourceRegistrationError(SourceRegistrationError error) {
base::UmaHistogramEnumeration("Conversions.SourceRegistrationError13", error);
}
} // namespace
SourceRegistration::SourceRegistration(mojo::DefaultConstruct::Tag tag)
: destination_set(tag) {}
SourceRegistration::SourceRegistration(DestinationSet destination_set)
: destination_set(std::move(destination_set)) {}
SourceRegistration::~SourceRegistration() = default;
SourceRegistration::SourceRegistration(const SourceRegistration&) = default;
SourceRegistration& SourceRegistration::operator=(const SourceRegistration&) =
default;
SourceRegistration::SourceRegistration(SourceRegistration&&) = default;
SourceRegistration& SourceRegistration::operator=(SourceRegistration&&) =
default;
namespace {
base::expected<SourceRegistration, SourceRegistrationError> ParseDict(
base::Value::Dict registration,
SourceType source_type) {
ASSIGN_OR_RETURN(DestinationSet destination_set,
DestinationSet::FromJSON(registration.Find(kDestination)));
SourceRegistration result(std::move(destination_set));
ASSIGN_OR_RETURN(result.source_event_id,
ParseUint64(registration, kSourceEventId)
.transform(&ValueOrZero<uint64_t>),
[](ParseError) {
return SourceRegistrationError::kSourceEventIdValueInvalid;
});
ASSIGN_OR_RETURN(result.priority, ParsePriority(registration),
[](ParseError) {
return SourceRegistrationError::kPriorityValueInvalid;
});
if (const base::Value* value = registration.Find(kExpiry)) {
ASSIGN_OR_RETURN(result.expiry,
ParseLegacyDuration(*value,
/*clamp_min=*/kMinSourceExpiry,
/*clamp_max=*/kMaxSourceExpiry),
[](ParseError) {
return SourceRegistrationError::kExpiryValueInvalid;
});
result.expiry = AdjustExpiry(result.expiry, source_type);
}
if (const base::Value* value = registration.Find(kAggregatableReportWindow)) {
ASSIGN_OR_RETURN(
result.aggregatable_report_window,
ParseLegacyDuration(*value,
/*clamp_min=*/kMinReportWindow,
/*clamp_max=*/result.expiry),
[](ParseError) {
return SourceRegistrationError::kAggregatableReportWindowValueInvalid;
});
} else {
result.aggregatable_report_window = result.expiry;
}
ASSIGN_OR_RETURN(result.trigger_data_matching,
ParseTriggerDataMatching(registration));
ASSIGN_OR_RETURN(result.event_level_epsilon,
EventLevelEpsilon::Parse(registration));
ASSIGN_OR_RETURN(
result.event_report_windows,
EventReportWindows::FromJSON(registration, result.expiry, source_type));
ASSIGN_OR_RETURN(result.max_event_level_reports,
MaxEventLevelReports::Parse(registration, source_type));
ASSIGN_OR_RETURN(result.trigger_data,
TriggerDataSet::Parse(registration, source_type,
result.trigger_data_matching));
ASSIGN_OR_RETURN(result.filter_data,
FilterData::FromJSON(registration.Find(kFilterData)));
ASSIGN_OR_RETURN(
result.aggregation_keys,
AggregationKeys::FromJSON(registration.Find(kAggregationKeys)));
ASSIGN_OR_RETURN(result.aggregatable_named_budget_defs,
AggregatableNamedBudgetDefs::FromJSON(
registration.Find(kAggregatableNamedBudgets)));
if (base::Value* scopes_value = registration.Find(kAttributionScopes)) {
ASSIGN_OR_RETURN(result.attribution_scopes_data,
AttributionScopesData::FromJSON(*scopes_value));
}
result.debug_key = ParseDebugKey(registration);
result.debug_reporting = ParseDebugReporting(registration);
// Deliberately ignoring errors for now to avoid dropping the registration
// from the optional debug reporting feature.
if (auto aggregatable_debug_reporting_config =
SourceAggregatableDebugReportingConfig::Parse(registration);
aggregatable_debug_reporting_config.has_value()) {
result.aggregatable_debug_reporting_config =
*std::move(aggregatable_debug_reporting_config);
}
ASSIGN_OR_RETURN(
result.destination_limit_priority,
ParseInt64(registration, kDestinationLimitPriority)
.transform(&ValueOrZero<int64_t>),
[](ParseError) {
return SourceRegistrationError::kDestinationLimitPriorityInvalid;
});
CHECK(result.IsValid());
CHECK(result.IsValidForSourceType(source_type));
RecordFeatureUsage(result);
return result;
}
} // namespace
// static
base::expected<SourceRegistration, SourceRegistrationError>
SourceRegistration::Parse(base::Value value, SourceType source_type) {
if (base::Value::Dict* dict = value.GetIfDict()) {
return ParseDict(std::move(*dict), source_type);
} else {
return base::unexpected(SourceRegistrationError::kRootWrongType);
}
}
// static
base::expected<SourceRegistration, SourceRegistrationError>
SourceRegistration::Parse(std::string_view json, SourceType source_type) {
base::expected<SourceRegistration, SourceRegistrationError> source =
base::unexpected(SourceRegistrationError::kInvalidJson);
if (std::optional<base::Value> value =
base::JSONReader::Read(json, base::JSON_PARSE_RFC)) {
source = Parse(*std::move(value), source_type);
}
if (!source.has_value()) {
RecordSourceRegistrationError(source.error());
}
return source;
}
base::Value::Dict SourceRegistration::ToJson() const {
base::Value::Dict dict;
dict.Set(kDestination, destination_set.ToJson());
if (!filter_data.filter_values().empty()) {
dict.Set(kFilterData, filter_data.ToJson());
}
if (!aggregation_keys.keys().empty()) {
dict.Set(kAggregationKeys, aggregation_keys.ToJson());
}
SerializeUint64(dict, kSourceEventId, source_event_id);
SerializePriority(dict, priority);
SerializeTimeDeltaInSeconds(dict, kExpiry, expiry);
event_report_windows.Serialize(dict);
max_event_level_reports.Serialize(dict);
trigger_data.Serialize(dict);
SerializeTimeDeltaInSeconds(dict, kAggregatableReportWindow,
aggregatable_report_window);
SerializeDebugKey(dict, debug_key);
SerializeDebugReporting(dict, debug_reporting);
Serialize(dict, trigger_data_matching);
event_level_epsilon.Serialize(dict);
aggregatable_debug_reporting_config.Serialize(dict);
if (attribution_scopes_data.has_value()) {
dict.Set(kAttributionScopes, attribution_scopes_data->ToJson());
}
SerializeInt64(dict, kDestinationLimitPriority, destination_limit_priority);
aggregatable_named_budget_defs.Serialize(dict);
return dict;
}
bool SourceRegistration::IsValid() const {
if (expiry < kMinSourceExpiry || expiry > kMaxSourceExpiry) {
return false;
}
if (!event_report_windows.IsValidForExpiry(expiry)) {
return false;
}
if (aggregatable_report_window < kMinReportWindow ||
aggregatable_report_window > expiry) {
return false;
}
return true;
}
bool SourceRegistration::IsValidForSourceType(SourceType source_type) const {
return expiry == AdjustExpiry(expiry, source_type);
}
} // namespace attribution_reporting