blob: 6f05ae3b894d7a5a15d9d3a0f5afc97c6c8fed23 [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/aggregatable_attribution_utils.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <iterator>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "base/check.h"
#include "base/containers/flat_tree.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/attribution_reporting/aggregatable_named_budget_defs.h"
#include "components/attribution_reporting/aggregatable_trigger_config.h"
#include "components/attribution_reporting/aggregatable_trigger_data.h"
#include "components/attribution_reporting/aggregatable_utils.h"
#include "components/attribution_reporting/aggregatable_values.h"
#include "components/attribution_reporting/aggregation_keys.h"
#include "components/attribution_reporting/constants.h"
#include "components/attribution_reporting/filters.h"
#include "components/attribution_reporting/source_registration_time_config.mojom.h"
#include "components/attribution_reporting/source_type.mojom-forward.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "content/browser/aggregation_service/aggregatable_report.h"
#include "content/browser/attribution_reporting/aggregatable_named_budget_pair.h"
#include "content/browser/attribution_reporting/attribution_info.h"
#include "content/browser/attribution_reporting/attribution_report.h"
#include "content/browser/attribution_reporting/stored_source.h"
#include "net/base/schemeful_site.h"
#include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom.h"
namespace content {
namespace {
// Note: use the same time serialization as in aggregatable_report.cc.
// Consider sharing logic if more call-sites need this.
std::string SerializeTimeRoundedDownToWholeDayInSeconds(base::Time time) {
// TODO(csharrison, linnan): Validate that `time` is valid (e.g. not null /
// inf).
base::Time rounded =
attribution_reporting::RoundDownToWholeDaySinceUnixEpoch(time);
return base::NumberToString(rounded.InMillisecondsSinceUnixEpoch() /
base::Time::kMillisecondsPerSecond);
}
} // namespace
std::vector<blink::mojom::AggregatableReportHistogramContribution>
CreateAggregatableHistogram(
const attribution_reporting::FilterData& source_filter_data,
attribution_reporting::mojom::SourceType source_type,
base::Time source_time,
base::Time trigger_time,
const attribution_reporting::AggregationKeys& keys,
const std::vector<attribution_reporting::AggregatableTriggerData>&
aggregatable_trigger_data,
const std::vector<attribution_reporting::AggregatableValues>&
aggregatable_values) {
size_t num_trigger_data_filtered = 0;
attribution_reporting::AggregationKeys::Keys buckets = keys.keys();
// For each piece of trigger data specified, check if its filters/not_filters
// match for the given source, and if applicable modify the bucket based on
// the given key piece.
for (const auto& data : aggregatable_trigger_data) {
if (!source_filter_data.Matches(source_type, source_time, trigger_time,
data.filters())) {
++num_trigger_data_filtered;
continue;
}
for (const auto& source_key : data.source_keys()) {
auto bucket = buckets.find(source_key);
if (bucket == buckets.end()) {
continue;
}
bucket->second |= data.key_piece();
}
}
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions;
for (const auto& aggregatable_value : aggregatable_values) {
if (source_filter_data.Matches(source_type, source_time, trigger_time,
aggregatable_value.filters())) {
const attribution_reporting::AggregatableValues::Values& values =
aggregatable_value.values();
for (const auto& [key_id, key] : buckets) {
auto value = values.find(key_id);
if (value == values.end()) {
continue;
}
contributions.emplace_back(
key, base::checked_cast<int32_t>(value->second.value()),
value->second.filtering_id());
}
break;
}
}
if (!aggregatable_trigger_data.empty()) {
base::ClampedNumeric<size_t> percentage = num_trigger_data_filtered;
percentage *= 100;
percentage /= aggregatable_trigger_data.size();
base::UmaHistogramPercentage(
"Conversions.AggregatableReport.FilteredTriggerDataPercentage",
percentage);
}
if (!buckets.empty()) {
base::ClampedNumeric<size_t> percentage = buckets.size();
percentage -= contributions.size();
percentage *= 100;
percentage /= buckets.size();
base::UmaHistogramPercentage(
"Conversions.AggregatableReport.DroppedKeysPercentage", percentage);
}
static_assert(attribution_reporting::kMaxAggregationKeysPerSource == 20,
"Bump the version for histogram "
"Conversions.AggregatableReport.NumContributionsPerReport2");
base::UmaHistogramExactLinear(
"Conversions.AggregatableReport.NumContributionsPerReport2",
base::saturated_cast<int>(contributions.size()),
attribution_reporting::kMaxAggregationKeysPerSource + 1);
// If total values exceeds the max, log the metrics as 100,000 to measure
// how often the max is exceeded.
static_assert(attribution_reporting::kMaxAggregatableValue == 65536);
const int64_t max_value = attribution_reporting::kMaxAggregatableValue + 1;
int64_t adjusted_value = std::min(
base::MakeStrictNum(max_value),
GetTotalAggregatableValues(contributions).ValueOrDefault(max_value));
base::UmaHistogramCounts100000(
"Conversions.AggregatableReport.TotalBudgetPerReport",
adjusted_value == max_value ? 100000
: base::saturated_cast<int>(adjusted_value));
return contributions;
}
std::optional<AggregatableReportRequest> CreateAggregatableReportRequest(
const AttributionReport& report) {
const auto* aggregatable_data =
std::get_if<AttributionReport::AggregatableData>(&report.data());
CHECK(aggregatable_data);
const AttributionInfo& attribution_info = report.attribution_info();
AggregatableReportSharedInfo::DebugMode debug_mode =
report.CanDebuggingBeEnabled()
? AggregatableReportSharedInfo::DebugMode::kEnabled
: AggregatableReportSharedInfo::DebugMode::kDisabled;
base::Value::Dict additional_fields;
switch (aggregatable_data->aggregatable_trigger_config()
.source_registration_time_config()) {
case attribution_reporting::mojom::SourceRegistrationTimeConfig::kInclude:
additional_fields.Set("source_registration_time",
SerializeTimeRoundedDownToWholeDayInSeconds(
aggregatable_data->source_time()));
break;
case attribution_reporting::mojom::SourceRegistrationTimeConfig::kExclude:
break;
}
SetAttributionDestination(
additional_fields, net::SchemefulSite(attribution_info.context_origin));
return AggregatableReportRequest::Create(
AggregationServicePayloadContents(
AggregationServicePayloadContents::Operation::kHistogram,
aggregatable_data->contributions(),
aggregatable_data->aggregation_coordinator_origin()
? std::make_optional(
**aggregatable_data->aggregation_coordinator_origin())
: std::nullopt,
/*max_contributions_allowed=*/
attribution_reporting::kMaxAggregationKeysPerSource,
aggregatable_data->aggregatable_trigger_config()
.aggregatable_filtering_id_max_bytes()
.value()),
AggregatableReportSharedInfo(
report.initial_report_time(), report.external_report_id(),
report.reporting_origin(), debug_mode, std::move(additional_fields),
AttributionReport::AggregatableData::kVersion,
AttributionReport::AggregatableData::kApiIdentifier),
// The returned request cannot be serialized due to the null `delay_type`.
/*delay_type=*/std::nullopt);
}
base::CheckedNumeric<int64_t> GetTotalAggregatableValues(
const std::vector<blink::mojom::AggregatableReportHistogramContribution>&
contributions) {
base::CheckedNumeric<int64_t> total_value = 0;
for (const blink::mojom::AggregatableReportHistogramContribution&
contribution : contributions) {
total_value += contribution.value;
}
return total_value;
}
void SetAttributionDestination(base::Value::Dict& dict,
const net::SchemefulSite& destination) {
dict.Set("attribution_destination", destination.Serialize());
}
StoredSource::AggregatableNamedBudgets ConvertNamedBudgetsMap(
const attribution_reporting::AggregatableNamedBudgetDefs& reg_budgets) {
StoredSource::AggregatableNamedBudgets::container_type named_budgets;
const auto& reg_budget_map = reg_budgets.budgets();
named_budgets.reserve(reg_budget_map.size());
for (const auto& [name, original_budget] : reg_budget_map) {
// Budget already validated from parsing.
auto budget_pair = AggregatableNamedBudgetPair::Create(
original_budget, /*remaining_budget=*/original_budget);
CHECK(budget_pair.has_value());
named_budgets.emplace_back(name, *std::move(budget_pair));
};
return StoredSource::AggregatableNamedBudgets(base::sorted_unique,
std::move(named_budgets));
}
} // namespace content