blob: 281baab99c4927faf418621adfde5f5133a513b1 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_PENDING_CONTRIBUTIONS_H_
#define CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_PENDING_CONTRIBUTIONS_H_
#include <stddef.h>
#include <stdint.h>
#include <map>
#include <optional>
#include <set>
#include <string_view>
#include <variant>
#include <vector>
#include "base/numerics/safe_conversions.h"
#include "content/browser/private_aggregation/private_aggregation_budgeter.h"
#include "content/common/content_export.h"
#include "third_party/abseil-cpp/absl/numeric/int128.h"
#include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom.h"
#include "third_party/blink/public/mojom/private_aggregation/private_aggregation_host.mojom.h"
namespace content {
// Holds the pending histogram contributions for a particular aggregatable
// report through the Private Aggregation layer -- i.e. from the
// PrivateAggregationHost until just before the final budgeting round. This
// class also stores contributions that are conditional on error events,
// triggering or dropping those contributions based on whether the event
// occurred, as well as contribution merging and truncation.
//
// This class is only usable/constructible when the
// `kPrivateAggregationApiErrorReporting` feature is enabled. However, see
// `PrivateAggregationPendingContributions::Wrapper` for a class that holds
// either this type or a bare vector of contributions based on the state of that
// feature.
class CONTENT_EXPORT PrivateAggregationPendingContributions {
public:
// Contributions can be merged if they have matching keys.
struct ContributionMergeKey {
explicit ContributionMergeKey(
const blink::mojom::AggregatableReportHistogramContribution&
contribution)
: bucket(contribution.bucket),
filtering_id(contribution.filtering_id.value_or(0)) {}
explicit ContributionMergeKey(
const blink::mojom::AggregatableReportHistogramContributionPtr&
contribution)
: ContributionMergeKey(*contribution) {}
auto operator<=>(const ContributionMergeKey& a) const = default;
absl::uint128 bucket;
uint64_t filtering_id;
};
class Wrapper;
using BudgeterResult = PrivateAggregationBudgeter::ResultForContribution;
using PendingReportLimitResult =
PrivateAggregationBudgeter::PendingReportLimitResult;
// Indicates the reason for the PrivateAggregationHost mojo pipe closing.
// TODO(crbug.com/381788013): Consider moving this enum to
// `PrivateAggregationHost` once the feature is fully launched and the
// circular dependency is avoided.
enum TimeoutOrDisconnect {
// The timeout was reached.
kTimeout,
// The pipe was disconnected by the caller API (e.g. due to the script
// completing).
kDisconnect
};
// Mirrors `PrivateAggregationHost::NullReportBehavior` to avoid a circular
// dependency.
// TODO(crbug.com/381788013): Merge these enums once the feature is fully
// launched and the circular dependency is avoided.
enum class NullReportBehavior {
kSendNullReport,
kDontSendReport,
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(TruncationResult)
enum class TruncationResult {
kNoTruncation = 0,
kTruncationDueToUnconditionalContributions = 1,
kTruncationNotDueToUnconditionalContributions = 2,
kMaxValue = kTruncationNotDueToUnconditionalContributions,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/privacy/enums.xml:PrivacySandboxPrivateAggregationTruncationResult)
// The elements of `histogram_suffixes` must outlive this object.
PrivateAggregationPendingContributions(
base::StrictNumeric<size_t> max_num_contributions,
std::vector<std::string_view> histogram_suffixes);
PrivateAggregationPendingContributions(
PrivateAggregationPendingContributions&& other);
PrivateAggregationPendingContributions& operator=(
PrivateAggregationPendingContributions&& other);
~PrivateAggregationPendingContributions();
const std::vector<blink::mojom::AggregatableReportHistogramContribution>&
unconditional_contributions() const {
return unconditional_contributions_;
}
bool are_contributions_finalized() const {
return are_contributions_finalized_;
}
// Returns false if this object has any unconditional or conditional
// contributions.
bool IsEmpty() const;
const std::map<
blink::mojom::PrivateAggregationErrorEvent,
std::vector<blink::mojom::AggregatableReportHistogramContribution>>&
GetConditionalContributionsForTesting() const;
// Tracks contributions not conditional on any error event (i.e. passed via
// `ContributeToHistogram()`). Drops contributions with value 0.
void AddUnconditionalContributions(
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions);
// Tracks contributions conditional on an error event (i.e. passed via
// `ContributeToHistogramOnEvent()`). Drops contributions with value 0.
void AddConditionalContributions(
blink::mojom::PrivateAggregationErrorEvent error_event,
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions);
// Should be called exactly once per object when no further contributions can
// be made. `finalization_cause` should indicate whether the mojo pipe
// disconnected or timed out.
void MarkContributionsFinalized(TimeoutOrDisconnect finalization_cause);
// Applies the results of the test budget call, i.e. filtering out
// unconditional contributions that were denied in that call and possibly
// triggering conditional contributions, and then compiles (and returns) a
// final list of unmerged contributions. This consists of any conditional
// contributions for error events that were triggered followed by any
// unconditional contributions. 'Truncates' the resulting list by assuming all
// contributions would be approved by the budgeter and removing any
// contributions that would not fit into the report *after merging is
// performed*. In other words, this 'truncation' determines the first n
// `ContributionMergeKey`s in this list, where n is `max_contributions_` and
// removes any contributions with other `ContributionMergeKey`s. Note that
// `test_budgeter_results` must a length equal to the number of unconditional
// contributions before the call. Can only be called after
// `MarkContributionsFinalized()`.
const std::vector<blink::mojom::AggregatableReportHistogramContribution>&
CompileFinalUnmergedContributions(
std::vector<BudgeterResult> test_budgeter_results,
PendingReportLimitResult pending_report_limit_result,
NullReportBehavior null_report_behavior);
// Applies the results of the final budget call to the result of
// `CompileFinalUnmergedContributions()`, i.e. filtering out any contributions
// denied by the budgeter, and then merges (approved) contributions where
// possible (i.e. have the same `ContributionMergeKey`). Given the
// 'truncation' performed earlier, no further truncation is needed to ensure
// the overall length fits in limits. Consumes this object and returns the
// final vector of merged (and truncated) contributions. Can only be called
// after `CompileFinalUnmergedContributions()`.
std::vector<blink::mojom::AggregatableReportHistogramContribution>
TakeFinalContributions(std::vector<BudgeterResult> final_budgeter_results) &&;
private:
// Adds `contributions` to the end of the `final_unmerged_contributions_`
// vector, adding each associated `ContributionMergeKey` to
// `accepted_merge_keys`. However, if a contribution would cause
// `accepted_merge_keys.size()` to grow beyond `max_contributions_`, the
// contribution is instead dropped and its `ContributionMergeKey` is added to
// `truncated_merge_keys`. The contributions are processed in the order given.
void AddToFinalUnmergedContributions(
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions,
std::set<ContributionMergeKey>& accepted_merge_keys,
std::set<ContributionMergeKey>& truncated_merge_keys);
void ApplyTestBudgeterResults(
std::vector<BudgeterResult> results,
PendingReportLimitResult pending_report_limit_result,
NullReportBehavior null_report_behavior);
void ApplyFinalBudgeterResults(std::vector<BudgeterResult> results);
void RecordNumberOfContributionMergeKeysHistogram(
size_t num_merge_keys_sent_or_truncated) const;
void RecordNumberOfFinalUnmergedContributionsHistogram(
size_t num_final_unmerged_contributions) const;
void RecordTruncationHistogram(
size_t num_truncations_due_to_unconditional_contributions,
size_t total_truncations) const;
bool are_contributions_finalized_ = false;
// Contributions passed to `ContributeToHistogram()` for the associated mojo
// receiver. Only very loose truncation (to limit worst-case memory usage) and
// dropping zero-value contributions has occurred. No contribution merging has
// been occurred. This is consumed in `CompileFinalUnmergedContributions()`.
std::vector<blink::mojom::AggregatableReportHistogramContribution>
unconditional_contributions_;
// Same considerations as `unconditional_contributions_`, but for
// contributions passed to `ContributeToHistogramOnEvent()`.
std::map<blink::mojom::PrivateAggregationErrorEvent,
std::vector<blink::mojom::AggregatableReportHistogramContribution>>
conditional_contributions_;
// For each error event, whether the error has been triggered or not. No entry
// for an error event if it hasn't yet been determined.
std::map<blink::mojom::PrivateAggregationErrorEvent, bool>
was_error_triggered_;
// Only populated when `CompileFinalUnmergedContributions()` is called. The
// return value of that call is a const reference to this object.
std::vector<blink::mojom::AggregatableReportHistogramContribution>
final_unmerged_contributions_;
size_t max_contributions_;
std::vector<std::string_view> histogram_suffixes_;
};
// This is a simple union class that holds contributions in the appropriate
// type given the state of the `kPrivateAggregationApiErrorReporting` feature.
//
// When the feature is disabled, this class is a wrapper around a vector of
// contributions (accessed via `GetContributionsVector()`), with contribution
// merging and truncation occurring before construction.
//
// When the feature is enabled, this class is a wrapper around
// `PrivateAggregationPendingContributions`, which also stores contributions
// that are conditional on error events, triggering or dropping those
// contributions based on whether the event occurred, as well as contribution
// merging and truncation.
//
// TODO(crbug.com/381788013): Remove this wrapper (replacing with a bare
// `PrivateAggregationPendingContributions`) after the feature is fully
// launched and the flag can be removed.
class CONTENT_EXPORT PrivateAggregationPendingContributions::Wrapper {
public:
// Usable iff `PrivateAggregationPendingContributions` is enabled.
explicit Wrapper(
PrivateAggregationPendingContributions pending_contributions);
// Usable iff `PrivateAggregationPendingContributions` is disabled.
explicit Wrapper(
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions_vector);
Wrapper(Wrapper&& other);
Wrapper& operator=(Wrapper&& other);
~Wrapper();
// Usable iff `PrivateAggregationPendingContributions` is enabled.
PrivateAggregationPendingContributions& GetPendingContributions();
// Usable iff `PrivateAggregationPendingContributions` is disabled.
std::vector<blink::mojom::AggregatableReportHistogramContribution>&
GetContributionsVector();
private:
std::variant<
PrivateAggregationPendingContributions,
std::vector<blink::mojom::AggregatableReportHistogramContribution>>
contributions_;
};
} // namespace content
#endif // CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_PENDING_CONTRIBUTIONS_H_