#include "content/browser/private_aggregation/private_aggregation_manager_impl.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/checked_math.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/task/updateable_sequenced_task_runner.h"
#include "base/time/time.h"
#include "content/browser/aggregation_service/aggregatable_report.h"
#include "content/browser/aggregation_service/aggregation_service.h"
#include "content/browser/private_aggregation/private_aggregation_budget_key.h"
#include "content/browser/private_aggregation/private_aggregation_budgeter.h"
#include "content/browser/private_aggregation/private_aggregation_caller_api.h"
#include "content/browser/private_aggregation/private_aggregation_host.h"
#include "content/browser/private_aggregation/private_aggregation_pending_contributions.h"
#include "content/browser/private_aggregation/private_aggregation_utils.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/private_aggregation_data_model.h"
#include "content/public/browser/storage_partition.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.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"
#include "url/origin.h"
namespace content {
namespace {
void RecordBudgeterResultHistogram(
PrivateAggregationBudgeter::RequestResult request_result) {
base::UmaHistogramEnumeration(
"PrivacySandbox.PrivateAggregation.Budgeter.RequestResult3",
request_result);
}
void RecordManagerResultHistogram(
PrivateAggregationManagerImpl::RequestResult request_result) {
base::UmaHistogramEnumeration(
"PrivacySandbox.PrivateAggregation.Manager.RequestResult",
request_result);
}
}
struct PrivateAggregationManagerImpl::InProgressBudgetRequest {
PrivateAggregationHost::ReportRequestGenerator report_request_generator;
PrivateAggregationPendingContributions pending_contributions;
const PrivateAggregationBudgetKey budget_key;
const PrivateAggregationHost::NullReportBehavior null_report_behavior;
std::optional<PrivateAggregationBudgeter::RequestResult>
inspect_request_result;
};
PrivateAggregationManagerImpl::PrivateAggregationManagerImpl(
bool exclusively_run_in_memory,
const base::FilePath& user_data_directory,
StoragePartitionImpl* storage_partition)
: PrivateAggregationManagerImpl(
std::make_unique<PrivateAggregationBudgeter>(
base::ThreadPool::CreateUpdateableSequencedTaskRunner(
base::TaskTraits(base::TaskPriority::BEST_EFFORT,
base::MayBlock(),
base::TaskShutdownBehavior::BLOCK_SHUTDOWN,
base::ThreadPolicy::MUST_USE_FOREGROUND)),
exclusively_run_in_memory,
user_data_directory),
std::make_unique<PrivateAggregationHost>(
base::BindRepeating(
&PrivateAggregationManagerImpl::
OnReportRequestDetailsReceivedFromHost,
base::Unretained(this)),
storage_partition ? storage_partition->browser_context()
: nullptr),
storage_partition) {}
PrivateAggregationManagerImpl::PrivateAggregationManagerImpl(
std::unique_ptr<PrivateAggregationBudgeter> budgeter,
std::unique_ptr<PrivateAggregationHost> host,
StoragePartitionImpl* storage_partition)
: budgeter_(std::move(budgeter)),
host_(std::move(host)),
storage_partition_(storage_partition) {
CHECK(budgeter_);
CHECK(host_);
}
PrivateAggregationManagerImpl::~PrivateAggregationManagerImpl() = default;
bool PrivateAggregationManagerImpl::BindNewReceiver(
url::Origin worklet_origin,
url::Origin top_frame_origin,
PrivateAggregationCallerApi caller_api,
std::optional<std::string> context_id,
std::optional<base::TimeDelta> timeout,
std::optional<url::Origin> aggregation_coordinator_origin,
size_t filtering_id_max_bytes,
std::optional<size_t> max_contributions,
mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
pending_receiver) {
return host_->BindNewReceiver(
std::move(worklet_origin), std::move(top_frame_origin), caller_api,
std::move(context_id), std::move(timeout),
std::move(aggregation_coordinator_origin), filtering_id_max_bytes,
std::move(max_contributions), std::move(pending_receiver));
}
void PrivateAggregationManagerImpl::ClearBudgetData(
base::Time delete_begin,
base::Time delete_end,
StoragePartition::StorageKeyMatcherFunction filter,
base::OnceClosure done) {
budgeter_->ClearData(delete_begin, delete_end, std::move(filter),
std::move(done));
}
bool PrivateAggregationManagerImpl::IsDebugModeAllowed(
const url::Origin& top_frame_origin,
const url::Origin& reporting_origin) {
return host_->IsDebugModeAllowed(top_frame_origin, reporting_origin);
}
void PrivateAggregationManagerImpl::OnReportRequestDetailsReceivedFromHost(
PrivateAggregationHost::ReportRequestGenerator report_request_generator,
PrivateAggregationPendingContributions::Wrapper contributions_wrapper,
PrivateAggregationBudgetKey budget_key,
PrivateAggregationHost::NullReportBehavior null_report_behavior) {
if (base::FeatureList::IsEnabled(
blink::features::kPrivateAggregationApiErrorReporting)) {
if (contributions_wrapper.GetPendingContributions().IsEmpty()) {
CHECK_EQ(null_report_behavior,
PrivateAggregationHost::NullReportBehavior::kSendNullReport);
RecordManagerResultHistogram(RequestResult::kSentWithoutContributions);
OnContributionsFinalized(std::move(report_request_generator),
{}, budget_key.caller_api());
return;
}
BudgetRequestId budget_request_id =
BudgetRequestId(num_requests_processed_++);
auto emplace_pair = in_progress_budget_requests_.emplace(
budget_request_id,
InProgressBudgetRequest{
.report_request_generator = std::move(report_request_generator),
.pending_contributions =
std::move(contributions_wrapper.GetPendingContributions()),
.budget_key = std::move(budget_key),
.null_report_behavior = null_report_behavior});
InProgressBudgetRequest& current_request = emplace_pair.first->second;
budgeter_->InspectBudgetAndLock(
current_request.pending_contributions.unconditional_contributions(),
current_request.budget_key,
base::BindOnce(
&PrivateAggregationManagerImpl::OnTestBudgetAndLockReturned,
base::Unretained(this), budget_request_id));
return;
}
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions = std::move(contributions_wrapper.GetContributionsVector());
base::CheckedNumeric<int> budget_needed = std::accumulate(
contributions.begin(), contributions.end(),
base::CheckedNumeric<int>(0),
[](base::CheckedNumeric<int> running_sum,
const blink::mojom::AggregatableReportHistogramContribution&
contribution) { return running_sum + contribution.value; });
PrivateAggregationCallerApi caller_api = budget_key.caller_api();
if (!budget_needed.IsValid()) {
OnConsumeBudgetReturned(std::move(report_request_generator),
std::move(contributions), caller_api,
null_report_behavior,
PrivateAggregationBudgeter::RequestResult::
kRequestedMoreThanTotalBudget);
return;
}
if (budget_needed.ValueOrDie() == 0) {
RecordManagerResultHistogram(RequestResult::kSentWithoutContributions);
CHECK(contributions.empty());
OnContributionsFinalized(std::move(report_request_generator),
std::move(contributions), caller_api);
return;
}
CHECK(!contributions.empty());
int minimum_value_for_metrics =
std::ranges::min(
contributions, {},
&blink::mojom::AggregatableReportHistogramContribution::value)
.value;
budgeter_->ConsumeBudget(
budget_needed.ValueOrDie(), std::move(budget_key),
minimum_value_for_metrics,
base::BindOnce(
&PrivateAggregationManagerImpl::OnConsumeBudgetReturned,
base::Unretained(this), std::move(report_request_generator),
std::move(contributions), caller_api, null_report_behavior));
}
AggregationService* PrivateAggregationManagerImpl::GetAggregationService() {
CHECK(storage_partition_);
return AggregationService::GetService(storage_partition_->browser_context());
}
void PrivateAggregationManagerImpl::OnTestBudgetAndLockReturned(
BudgetRequestId budget_request_id,
PrivateAggregationBudgeter::InspectBudgetCallResult result) {
CHECK(base::FeatureList::IsEnabled(
blink::features::kPrivateAggregationApiErrorReporting));
CHECK(in_progress_budget_requests_.contains(budget_request_id));
InProgressBudgetRequest& current_request =
in_progress_budget_requests_.at(budget_request_id);
current_request.inspect_request_result = result.query_result.overall_result;
CHECK(current_request.inspect_request_result.has_value());
PrivateAggregationPendingContributions::NullReportBehavior
pending_contributions_null_report_behavior =
current_request.null_report_behavior ==
PrivateAggregationHost::NullReportBehavior::kSendNullReport
? PrivateAggregationPendingContributions::NullReportBehavior::
kSendNullReport
: PrivateAggregationPendingContributions::NullReportBehavior::
kDontSendReport;
const std::vector<blink::mojom::AggregatableReportHistogramContribution>&
final_unmerged_contributions =
current_request.pending_contributions
.CompileFinalUnmergedContributions(
std::move(result.query_result.result_for_each_contribution),
result.pending_report_limit_result,
pending_contributions_null_report_behavior);
if (!result.lock.has_value()) {
result.query_result.result_for_each_contribution =
std::vector<PrivateAggregationBudgeter::ResultForContribution>(
final_unmerged_contributions.size(),
PrivateAggregationBudgeter::ResultForContribution::kDenied);
OnConsumeBudgetWithLockReturned(budget_request_id,
std::move(result.query_result));
return;
}
budgeter_->ConsumeBudget(
std::move(result.lock).value(), final_unmerged_contributions,
current_request.budget_key,
base::BindOnce(
&PrivateAggregationManagerImpl::OnConsumeBudgetWithLockReturned,
base::Unretained(this), budget_request_id));
}
void PrivateAggregationManagerImpl::OnConsumeBudgetWithLockReturned(
BudgetRequestId budget_request_id,
PrivateAggregationBudgeter::BudgetQueryResult result) {
CHECK(base::FeatureList::IsEnabled(
blink::features::kPrivateAggregationApiErrorReporting));
CHECK(in_progress_budget_requests_.contains(budget_request_id));
InProgressBudgetRequest current_request = std::move(
in_progress_budget_requests_.extract(budget_request_id).mapped());
PrivateAggregationBudgeter::RequestResult overall_result_both_phases =
PrivateAggregationBudgeter::CombineRequestResults(
current_request.inspect_request_result
.value(),
result.overall_result);
RecordBudgeterResultHistogram(overall_result_both_phases);
std::vector<blink::mojom::AggregatableReportHistogramContribution>
final_contributions = std::move(current_request.pending_contributions)
.TakeFinalContributions(std::move(
result.result_for_each_contribution));
if (final_contributions.empty()) {
switch (current_request.null_report_behavior) {
case PrivateAggregationHost::NullReportBehavior::kDontSendReport:
RecordManagerResultHistogram(RequestResult::kNotSent);
return;
case PrivateAggregationHost::NullReportBehavior::kSendNullReport:
RecordManagerResultHistogram(
overall_result_both_phases !=
PrivateAggregationBudgeter::RequestResult::kApproved
? RequestResult::kSentButContributionsClearedDueToBudgetDenial
: RequestResult::kSentWithoutContributions);
break;
}
} else {
RecordManagerResultHistogram(RequestResult::kSentWithContributions);
}
OnContributionsFinalized(std::move(current_request.report_request_generator),
std::move(final_contributions),
current_request.budget_key.caller_api());
}
void PrivateAggregationManagerImpl::OnConsumeBudgetReturned(
PrivateAggregationHost::ReportRequestGenerator report_request_generator,
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions,
PrivateAggregationCallerApi caller_api,
PrivateAggregationHost::NullReportBehavior null_report_behavior,
PrivateAggregationBudgeter::RequestResult request_result) {
CHECK(!base::FeatureList::IsEnabled(
blink::features::kPrivateAggregationApiErrorReporting));
RecordBudgeterResultHistogram(request_result);
if (request_result == PrivateAggregationBudgeter::RequestResult::kApproved) {
CHECK(!contributions.empty());
RecordManagerResultHistogram(RequestResult::kSentWithContributions);
} else {
switch (null_report_behavior) {
case PrivateAggregationHost::NullReportBehavior::kDontSendReport:
RecordManagerResultHistogram(RequestResult::kNotSent);
return;
case PrivateAggregationHost::NullReportBehavior::kSendNullReport:
RecordManagerResultHistogram(
RequestResult::kSentButContributionsClearedDueToBudgetDenial);
contributions.clear();
break;
}
}
OnContributionsFinalized(std::move(report_request_generator),
std::move(contributions), caller_api);
}
void PrivateAggregationManagerImpl::OnContributionsFinalized(
PrivateAggregationHost::ReportRequestGenerator report_request_generator,
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions,
PrivateAggregationCallerApi caller_api) {
AggregationService* aggregation_service = GetAggregationService();
if (!aggregation_service) {
return;
}
AggregatableReportRequest report_request =
std::move(report_request_generator).Run(std::move(contributions));
if (report_request.shared_info().debug_mode ==
AggregatableReportSharedInfo::DebugMode::kEnabled) {
std::string immediate_debug_reporting_path =
private_aggregation::GetReportingPath(
caller_api,
true);
std::optional<AggregatableReportRequest> debug_request =
AggregatableReportRequest::Create(
report_request.payload_contents(),
report_request.shared_info().Clone(),
AggregatableReportRequest::DelayType::Unscheduled,
std::move(immediate_debug_reporting_path),
report_request.debug_key(), report_request.additional_fields());
CHECK(debug_request.has_value());
aggregation_service->AssembleAndSendReport(
std::move(debug_request.value()));
}
aggregation_service->ScheduleReport(std::move(report_request));
}
void PrivateAggregationManagerImpl::GetAllDataKeys(
base::OnceCallback<void(std::set<DataKey>)> callback) {
budgeter_->GetAllDataKeys(base::BindOnce(
&PrivateAggregationManagerImpl::OnBudgeterGetAllDataKeysReturned,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void PrivateAggregationManagerImpl::OnBudgeterGetAllDataKeysReturned(
base::OnceCallback<void(std::set<DataKey>)> callback,
std::set<DataKey> all_keys) {
AggregationService* aggregation_service = GetAggregationService();
if (!aggregation_service) {
std::move(callback).Run(std::move(all_keys));
return;
}
aggregation_service->GetPendingReportReportingOrigins(base::BindOnce(
[](base::OnceCallback<void(std::set<DataKey>)> callback,
std::set<DataKey> all_keys, std::set<url::Origin> pending_origins) {
std::ranges::transform(
std::make_move_iterator(pending_origins.begin()),
std::make_move_iterator(pending_origins.end()),
std::inserter(all_keys, all_keys.begin()), [](url::Origin elem) {
return PrivateAggregationDataModel::DataKey(std::move(elem));
});
std::move(callback).Run(std::move(all_keys));
},
std::move(callback), std::move(all_keys)));
}
void PrivateAggregationManagerImpl::RemovePendingDataKey(
const DataKey& data_key,
base::OnceClosure callback) {
base::RepeatingClosure barrier = base::BarrierClosure(2, std::move(callback));
budgeter_->DeleteByDataKey(data_key, barrier);
AggregationService* aggregation_service = GetAggregationService();
if (!aggregation_service) {
std::move(barrier).Run();
return;
}
aggregation_service->ClearData(
base::Time::Min(), base::Time::Max(),
base::BindRepeating(
std::equal_to<blink::StorageKey>(),
blink::StorageKey::CreateFirstParty(data_key.reporting_origin())),
std::move(barrier));
}
}