blob: 652e8ecaa7ad8f4b821c2ecd0f7cb2ab757d6646 [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 <algorithm>
#include <optional>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/feature_list.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/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/features.h"
#include "components/attribution_reporting/filters.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));
}
}
} // namespace
void RecordSourceRegistrationError(SourceRegistrationError error) {
base::UmaHistogramEnumeration("Conversions.SourceRegistrationError13", error);
}
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;
// static
base::expected<SourceRegistration, SourceRegistrationError>
SourceRegistration::Parse(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),
[](ParseError) {
return SourceRegistrationError::kExpiryValueInvalid;
});
result.expiry =
std::clamp(result.expiry, kMinSourceExpiry, kMaxSourceExpiry);
result.expiry = AdjustExpiry(result.expiry, source_type);
}
if (const base::Value* value = registration.Find(kAggregatableReportWindow)) {
ASSIGN_OR_RETURN(
result.aggregatable_report_window, ParseLegacyDuration(*value),
[](ParseError) {
return SourceRegistrationError::kAggregatableReportWindowValueInvalid;
});
result.aggregatable_report_window = std::clamp(
result.aggregatable_report_window, kMinReportWindow, result.expiry);
} 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(
auto default_event_report_windows,
EventReportWindows::FromJSON(registration, result.expiry, source_type));
ASSIGN_OR_RETURN(
result.trigger_specs,
TriggerSpecs::ParseTopLevelTriggerData(
registration, source_type, std::move(default_event_report_windows),
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.attribution_scopes_data,
AttributionScopesData::FromJSON(registration));
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);
}
if (base::FeatureList::IsEnabled(attribution_reporting::features::
kAttributionSourceDestinationLimit)) {
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));
return result;
}
// static
base::expected<SourceRegistration, SourceRegistrationError>
SourceRegistration::Parse(std::string_view json, SourceType source_type) {
base::expected<SourceRegistration, SourceRegistrationError> source =
base::unexpected(SourceRegistrationError::kInvalidJson);
std::optional<base::Value> value =
base::JSONReader::Read(json, base::JSON_PARSE_RFC);
if (value) {
if (base::Value::Dict* dict = value->GetIfDict()) {
source = Parse(std::move(*dict), source_type);
} else {
source = base::unexpected(SourceRegistrationError::kRootWrongType);
}
}
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);
trigger_specs.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);
attribution_scopes_data.Serialize(dict);
if (base::FeatureList::IsEnabled(attribution_reporting::features::
kAttributionSourceDestinationLimit)) {
SerializeInt64(dict, kDestinationLimitPriority, destination_limit_priority);
}
return dict;
}
bool SourceRegistration::IsValid() const {
if (expiry < kMinSourceExpiry || expiry > kMaxSourceExpiry) {
return false;
}
for (const auto& spec : trigger_specs.specs()) {
if (!spec.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