[go: nahoru, domu]

blob: cc09360933df3fbc4e50263da02cedbee37e5201 [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/private_aggregation/private_aggregation_budgeter.h"
#include <stdint.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.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/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/updateable_sequenced_task_runner.h"
#include "base/time/time.h"
#include "content/browser/private_aggregation/private_aggregation_budget_key.h"
#include "content/browser/private_aggregation/private_aggregation_budget_storage.h"
#include "content/browser/private_aggregation/proto/private_aggregation_budgets.pb.h"
#include "content/public/browser/private_aggregation_data_model.h"
#include "net/base/schemeful_site.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
using ValidityStatus = PrivateAggregationBudgeter::BudgetValidityStatus;
int64_t SerializeTimeForStorage(base::Time time) {
return time.ToDeltaSinceWindowsEpoch().InMicroseconds();
}
void RecordBudgetValidity(ValidityStatus status) {
static_assert(
ValidityStatus::kContainsNonPositiveValue == ValidityStatus::kMaxValue,
"Bump version of "
"PrivacySandbox.PrivateAggregation.Budgeter."
"BudgetValidityStatus histogram.");
base::UmaHistogramEnumeration(
"PrivacySandbox.PrivateAggregation.Budgeter.BudgetValidityStatus2",
status);
}
void ComputeAndRecordBudgetValidity(
google::protobuf::RepeatedPtrField<proto::PrivateAggregationBudgetEntry>*
budget_entries,
int64_t earliest_window_in_larger_scope_start,
int64_t current_window_start) {
if (budget_entries->empty()) {
RecordBudgetValidity(ValidityStatus::kValidAndEmpty);
return;
}
constexpr int64_t kWindowDuration =
PrivateAggregationBudgetKey::TimeWindow::kDuration.InMicroseconds();
ValidityStatus status = ValidityStatus::kValid;
for (proto::PrivateAggregationBudgetEntry& elem : *budget_entries) {
int64_t entry_start = elem.entry_start_timestamp();
int budget = elem.budget_used();
if (budget <= 0) {
RecordBudgetValidity(ValidityStatus::kContainsNonPositiveValue);
return;
} else if (entry_start % kWindowDuration != 0) {
RecordBudgetValidity(
ValidityStatus::kContainsTimestampNotRoundedToMinute);
return;
} else if (budget > PrivateAggregationBudgeter::kSmallerScopeValues
.max_budget_per_scope) {
// It should not be possible for any one-minute period to have usage
// exceeding the ten-minute limit.
RecordBudgetValidity(ValidityStatus::kContainsValueExceedingLimit);
return;
} else if (entry_start >= current_window_start + kWindowDuration) {
RecordBudgetValidity(ValidityStatus::kContainsTimestampInFuture);
return;
} else if (entry_start < earliest_window_in_larger_scope_start) {
// Data older than 24 hours is no longer needed (for either scope).
status = ValidityStatus::kValidButContainsStaleWindow;
}
}
// As the budget data for both scopes is stored in the same entries, we expect
// to maintain data for a period representing up to 24 hours in duration.
constexpr int64_t kMaximumWindowStartDifference =
PrivateAggregationBudgeter::kLargerScopeValues.budget_scope_duration
.InMicroseconds() -
kWindowDuration;
const auto minmax = base::ranges::minmax(
*budget_entries, /*comp=*/{},
&proto::PrivateAggregationBudgetEntry::entry_start_timestamp);
DCHECK_EQ(kMaximumWindowStartDifference,
current_window_start - earliest_window_in_larger_scope_start);
if (minmax.second.entry_start_timestamp() -
minmax.first.entry_start_timestamp() >
kMaximumWindowStartDifference) {
RecordBudgetValidity(ValidityStatus::kSpansMoreThanADay);
return;
}
RecordBudgetValidity(status);
}
google::protobuf::RepeatedPtrField<proto::PrivateAggregationBudgetEntry>*
GetBudgetEntries(PrivateAggregationBudgetKey::Api api,
proto::PrivateAggregationBudgets& budgets) {
switch (api) {
case PrivateAggregationBudgetKey::Api::kProtectedAudience:
return budgets.mutable_protected_audience_budgets();
case PrivateAggregationBudgetKey::Api::kSharedStorage:
return budgets.mutable_shared_storage_budgets();
}
}
// Returns whether any entries were deleted.
bool CleanUpStaleBudgetEntries(
google::protobuf::RepeatedPtrField<proto::PrivateAggregationBudgetEntry>*
budget_entries,
const int64_t earliest_window_in_larger_scope_start) {
auto new_end = base::ranges::remove_if(
*budget_entries, [&earliest_window_in_larger_scope_start](
const proto::PrivateAggregationBudgetEntry& elem) {
return elem.entry_start_timestamp() <
earliest_window_in_larger_scope_start;
});
bool was_modified = new_end != budget_entries->end();
budget_entries->erase(new_end, budget_entries->end());
return was_modified;
}
// Returns whether any entries were deleted.
bool CleanUpStaleReportingOrigins(
google::protobuf::RepeatedPtrField<proto::ReportingOrigin>*
reporting_origins,
const int64_t earliest_window_in_larger_scope_start) {
auto new_end = base::ranges::remove_if(
*reporting_origins, [&earliest_window_in_larger_scope_start](
const proto::ReportingOrigin& elem) {
return elem.last_used_timestamp() <
earliest_window_in_larger_scope_start;
});
bool was_modified = new_end != reporting_origins->end();
reporting_origins->erase(new_end, reporting_origins->end());
return was_modified;
}
// `current_window_start` should be in microseconds since the Windows epoch,
// e.g. a value returned by `SerializeTimeForStorage()`. Returns a value in
// microseconds since the Windows epoch.
int64_t CalculateEarliestWindowStartInScope(
int64_t current_window_start,
base::TimeDelta budget_scope_duration) {
return current_window_start +
PrivateAggregationBudgetKey::TimeWindow::kDuration.InMicroseconds() -
budget_scope_duration.InMicroseconds();
}
} // namespace
PrivateAggregationBudgeter::PrivateAggregationBudgeter(
scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner,
bool exclusively_run_in_memory,
const base::FilePath& path_to_db_dir)
: db_task_runner_(std::move(db_task_runner)) {
DCHECK(db_task_runner_);
initialize_storage_ = base::BindOnce(
&PrivateAggregationBudgeter::InitializeStorage,
weak_factory_.GetWeakPtr(), exclusively_run_in_memory, path_to_db_dir);
}
PrivateAggregationBudgeter::PrivateAggregationBudgeter() = default;
PrivateAggregationBudgeter::~PrivateAggregationBudgeter() {
if (shutdown_initializing_storage_) {
// As the budget storage's lifetime is extended until initialization is
// complete, its destructor could run after browser shutdown has begun (when
// tasks can no longer be posted). We post the database deletion task now
// instead.
std::move(shutdown_initializing_storage_).Run();
}
}
void PrivateAggregationBudgeter::EnsureStorageInitializationBegun() {
if (storage_status_ == StorageStatus::kPendingInitialization) {
CHECK(initialize_storage_);
std::move(initialize_storage_).Run();
}
}
void PrivateAggregationBudgeter::InitializeStorage(
bool exclusively_run_in_memory,
base::FilePath path_to_db_dir) {
CHECK_EQ(storage_status_, StorageStatus::kPendingInitialization);
storage_status_ = StorageStatus::kInitializing;
shutdown_initializing_storage_ = PrivateAggregationBudgetStorage::CreateAsync(
db_task_runner_, exclusively_run_in_memory, std::move(path_to_db_dir),
/*on_done_initializing=*/
base::BindOnce(&PrivateAggregationBudgeter::OnStorageDoneInitializing,
weak_factory_.GetWeakPtr()));
CHECK(initialize_storage_.is_null());
}
void PrivateAggregationBudgeter::ConsumeBudget(
int budget,
const PrivateAggregationBudgetKey& budget_key,
base::OnceCallback<void(RequestResult)> on_done) {
EnsureStorageInitializationBegun();
if (storage_status_ == StorageStatus::kInitializing) {
if (pending_calls_.size() >= kMaxPendingCalls) {
std::move(on_done).Run(RequestResult::kTooManyPendingCalls);
return;
}
// `base::Unretained` is safe as `pending_calls_` is owned by `this`.
pending_calls_.push_back(base::BindOnce(
&PrivateAggregationBudgeter::ConsumeBudgetImpl, base::Unretained(this),
budget, budget_key, std::move(on_done)));
} else {
ConsumeBudgetImpl(budget, budget_key, std::move(on_done));
}
}
void PrivateAggregationBudgeter::OnUserVisibleTaskStarted() {
// When a user visible task is queued or running, we use a higher priority.
// We do this even if the storage hasn't finished initializing.
++num_pending_user_visible_tasks_;
db_task_runner_->UpdatePriority(base::TaskPriority::USER_VISIBLE);
}
void PrivateAggregationBudgeter::ClearData(
base::Time delete_begin,
base::Time delete_end,
StoragePartition::StorageKeyMatcherFunction filter,
base::OnceClosure done) {
OnUserVisibleTaskStarted();
EnsureStorageInitializationBegun();
done = base::BindOnce(&PrivateAggregationBudgeter::OnUserVisibleTaskComplete,
weak_factory_.GetWeakPtr())
.Then(std::move(done));
if (storage_status_ == StorageStatus::kInitializing) {
// To ensure that data deletion always succeeds, we don't check
// `pending_calls.size()` here.
// `base::Unretained` is safe as `pending_calls_` is owned by `this`.
pending_calls_.push_back(base::BindOnce(
&PrivateAggregationBudgeter::ClearDataImpl, base::Unretained(this),
delete_begin, delete_end, std::move(filter), std::move(done)));
} else {
ClearDataImpl(delete_begin, delete_end, std::move(filter), std::move(done));
}
}
void PrivateAggregationBudgeter::OnUserVisibleTaskComplete() {
DCHECK_GT(num_pending_user_visible_tasks_, 0);
--num_pending_user_visible_tasks_;
// No more pending tasks, so we can reset the priority.
if (num_pending_user_visible_tasks_ == 0) {
db_task_runner_->UpdatePriority(base::TaskPriority::BEST_EFFORT);
}
}
void PrivateAggregationBudgeter::OnStorageDoneInitializing(
std::unique_ptr<PrivateAggregationBudgetStorage> storage) {
DCHECK(shutdown_initializing_storage_);
DCHECK(!storage_);
DCHECK_EQ(storage_status_, StorageStatus::kInitializing);
if (storage) {
storage_status_ = StorageStatus::kOpen;
storage_ = std::move(storage);
} else {
storage_status_ = StorageStatus::kInitializationFailed;
}
shutdown_initializing_storage_.Reset();
ProcessAllPendingCalls();
// No-op if storage initialization failed.
CleanUpStaleDataSoon();
}
void PrivateAggregationBudgeter::ProcessAllPendingCalls() {
for (base::OnceClosure& cb : pending_calls_) {
std::move(cb).Run();
}
pending_calls_.clear();
}
void PrivateAggregationBudgeter::GetAllDataKeys(
base::OnceCallback<void(std::set<PrivateAggregationDataModel::DataKey>)>
callback) {
OnUserVisibleTaskStarted();
EnsureStorageInitializationBegun();
base::OnceCallback<void(std::set<PrivateAggregationDataModel::DataKey>)>
impl_callback = std::move(callback).Then(
base::BindOnce(&PrivateAggregationBudgeter::OnUserVisibleTaskComplete,
weak_factory_.GetWeakPtr()));
if (storage_status_ == StorageStatus::kInitializing) {
// `base::Unretained` is safe as `pending_calls_` is owned by `this`.
pending_calls_.push_back(
base::BindOnce(&PrivateAggregationBudgeter::GetAllDataKeysImpl,
base::Unretained(this), std::move(impl_callback)));
} else {
return GetAllDataKeysImpl(std::move(impl_callback));
}
}
void PrivateAggregationBudgeter::GetAllDataKeysImpl(
base::OnceCallback<void(std::set<PrivateAggregationDataModel::DataKey>)>
callback) {
if (!DidStorageInitializationSucceed()) {
std::move(callback).Run(std::set<PrivateAggregationDataModel::DataKey>());
return;
}
std::set<PrivateAggregationDataModel::DataKey> keys;
for (const auto& [site_key, budgets] :
storage_->budgets_data()->GetAllCached()) {
for (const proto::ReportingOrigin& elem :
budgets.reporting_origins_for_deletion()) {
url::Origin reporting_origin = url::Origin::Create(GURL(elem.origin()));
if (reporting_origin.opaque()) {
continue;
}
keys.emplace(std::move(reporting_origin));
}
}
std::move(callback).Run(std::move(keys));
}
void PrivateAggregationBudgeter::DeleteByDataKey(
const PrivateAggregationDataModel::DataKey& key,
base::OnceClosure callback) {
ClearData(/*delete_begin=*/base::Time::Min(),
/*delete_end=*/base::Time::Max(),
/*filter=*/
base::BindRepeating(
std::equal_to<blink::StorageKey>(),
blink::StorageKey::CreateFirstParty(key.reporting_origin())),
std::move(callback));
}
// TODO(crbug.com/1336733): Consider enumerating different error cases and log
// metrics and/or expose to callers.
void PrivateAggregationBudgeter::ConsumeBudgetImpl(
int additional_budget,
const PrivateAggregationBudgetKey& budget_key,
base::OnceCallback<void(RequestResult)> on_done) {
CHECK_GT(additional_budget, 0);
if (!DidStorageInitializationSucceed()) {
std::move(on_done).Run(RequestResult::kStorageInitializationFailed);
return;
}
static_assert(
kSmallerScopeValues.max_budget_per_scope <
kLargerScopeValues.max_budget_per_scope,
"The larger scope must have a larger budget than the smaller scope.");
if (additional_budget > kSmallerScopeValues.max_budget_per_scope) {
std::move(on_done).Run(RequestResult::kRequestedMoreThanTotalBudget);
return;
}
std::string site_key = net::SchemefulSite(budget_key.origin()).Serialize();
// If there is no budget proto stored for this origin already, we use the
// default initialization of `budgets` (untouched by `TryGetData()`).
proto::PrivateAggregationBudgets budgets;
storage_->budgets_data()->TryGetData(site_key, &budgets);
const int64_t current_window_start =
SerializeTimeForStorage(budget_key.time_window().start_time());
DCHECK_EQ(current_window_start % base::Time::kMicrosecondsPerMinute, 0);
// Budget windows must start on or after this timestamp to be counted in the
// current 10 minutes and day (for the smaller and larger scopes,
// respectively).
int64_t earliest_window_in_smaller_scope_start =
CalculateEarliestWindowStartInScope(
current_window_start, kSmallerScopeValues.budget_scope_duration);
int64_t earliest_window_in_larger_scope_start =
CalculateEarliestWindowStartInScope(
current_window_start, kLargerScopeValues.budget_scope_duration);
google::protobuf::RepeatedPtrField<proto::PrivateAggregationBudgetEntry>*
budget_entries = GetBudgetEntries(budget_key.api(), budgets);
ComputeAndRecordBudgetValidity(budget_entries,
earliest_window_in_larger_scope_start,
current_window_start);
proto::PrivateAggregationBudgetEntry* window_for_key = nullptr;
base::CheckedNumeric<int> total_budget_used_smaller_scope = 0;
base::CheckedNumeric<int> total_budget_used_larger_scope = 0;
for (proto::PrivateAggregationBudgetEntry& elem : *budget_entries) {
if (elem.entry_start_timestamp() < earliest_window_in_larger_scope_start) {
continue;
}
if (elem.entry_start_timestamp() == current_window_start) {
window_for_key = &elem;
}
// Protect against bad values on disk
if (elem.budget_used() <= 0) {
std::move(on_done).Run(RequestResult::kBadValuesOnDisk);
return;
}
if (elem.entry_start_timestamp() >=
earliest_window_in_smaller_scope_start) {
total_budget_used_smaller_scope += elem.budget_used();
}
total_budget_used_larger_scope += elem.budget_used();
}
total_budget_used_smaller_scope += additional_budget;
total_budget_used_larger_scope += additional_budget;
RequestResult budget_increase_request_result;
if (!total_budget_used_smaller_scope.IsValid() ||
!total_budget_used_larger_scope.IsValid()) {
budget_increase_request_result = RequestResult::kBadValuesOnDisk;
} else if (total_budget_used_smaller_scope.ValueOrDie() >
kSmallerScopeValues.max_budget_per_scope) {
budget_increase_request_result =
RequestResult::kInsufficientSmallerScopeBudget;
} else if (total_budget_used_larger_scope.ValueOrDie() >
kLargerScopeValues.max_budget_per_scope) {
budget_increase_request_result =
RequestResult::kInsufficientLargerScopeBudget;
} else {
budget_increase_request_result = RequestResult::kApproved;
}
if (budget_increase_request_result == RequestResult::kApproved) {
if (!window_for_key) {
window_for_key = budget_entries->Add();
window_for_key->set_entry_start_timestamp(current_window_start);
window_for_key->set_budget_used(0);
}
int budget_used_for_key = window_for_key->budget_used() + additional_budget;
DCHECK_GT(budget_used_for_key, 0);
DCHECK_LE(budget_used_for_key, kSmallerScopeValues.max_budget_per_scope);
window_for_key->set_budget_used(budget_used_for_key);
}
google::protobuf::RepeatedPtrField<proto::ReportingOrigin>*
reporting_origins_for_deletion =
budgets.mutable_reporting_origins_for_deletion();
if (budget_increase_request_result == RequestResult::kApproved) {
std::string reporting_origin_serialized = budget_key.origin().Serialize();
proto::ReportingOrigin* reporting_origin_entry = nullptr;
auto reporting_origin_entry_it = base::ranges::find_if(
*reporting_origins_for_deletion,
[&reporting_origin_serialized](const proto::ReportingOrigin& elem) {
return elem.origin() == reporting_origin_serialized;
});
if (reporting_origin_entry_it != reporting_origins_for_deletion->end()) {
reporting_origin_entry = &*reporting_origin_entry_it;
}
if (!reporting_origin_entry) {
reporting_origin_entry = reporting_origins_for_deletion->Add();
reporting_origin_entry->set_origin(
std::move(reporting_origin_serialized));
}
reporting_origin_entry->set_last_used_timestamp(current_window_start);
}
base::UmaHistogramCounts100(
"PrivacySandbox.PrivateAggregation.Budgeter."
"NumReportingOriginsStoredPerSite",
reporting_origins_for_deletion->size());
storage_->budgets_data()->UpdateData(site_key, budgets);
std::move(on_done).Run(budget_increase_request_result);
CleanUpStaleDataSoon();
}
void PrivateAggregationBudgeter::ClearDataImpl(
base::Time delete_begin,
base::Time delete_end,
StoragePartition::StorageKeyMatcherFunction filter,
base::OnceClosure done) {
if (!DidStorageInitializationSucceed()) {
std::move(done).Run();
return;
}
// Treat null times as unbounded lower or upper range. This is used by
// browsing data remover.
if (delete_begin.is_null()) {
delete_begin = base::Time::Min();
}
if (delete_end.is_null()) {
delete_end = base::Time::Max();
}
bool is_all_time_covered = delete_begin.is_min() && delete_end.is_max();
if (is_all_time_covered && filter.is_null()) {
storage_->budgets_data()->DeleteAllData();
// Runs `done` once flushing is complete.
storage_->budgets_data()->FlushDataToDisk(std::move(done));
return;
}
// Ensure we round down to capture any time windows that partially overlap.
const int64_t serialized_delete_begin = SerializeTimeForStorage(
PrivateAggregationBudgetKey::TimeWindow(delete_begin).start_time());
// No need to round up as we compare against the time window's start time.
const int64_t serialized_delete_end = SerializeTimeForStorage(delete_end);
std::vector<std::string> sites_to_delete;
for (const auto& [site_key, budgets] :
storage_->budgets_data()->GetAllCached()) {
for (const proto::ReportingOrigin& elem :
budgets.reporting_origins_for_deletion()) {
// If the filter matches the origin and the origin was last used on or
// after the beginning of the deletion window, we include this site.
// This may result in more data being deleted than strictly necessary.
if ((filter.is_null() ||
filter.Run(blink::StorageKey::CreateFirstParty(
url::Origin::Create(GURL(elem.origin()))))) &&
(is_all_time_covered ||
serialized_delete_begin <= elem.last_used_timestamp())) {
sites_to_delete.push_back(site_key);
break;
}
}
}
if (is_all_time_covered) {
storage_->budgets_data()->DeleteData(sites_to_delete);
// Runs `done` once flushing is complete.
storage_->budgets_data()->FlushDataToDisk(std::move(done));
return;
}
const int64_t earliest_window_in_larger_scope_start =
CalculateEarliestWindowStartInScope(
/*current_window_start=*/SerializeTimeForStorage(
PrivateAggregationBudgetKey::TimeWindow(base::Time::Now())
.start_time()),
kLargerScopeValues.budget_scope_duration);
for (const std::string& site_key : sites_to_delete) {
proto::PrivateAggregationBudgets budgets;
storage_->budgets_data()->TryGetData(site_key, &budgets);
for (PrivateAggregationBudgetKey::Api api :
PrivateAggregationBudgetKey::kAllApis) {
google::protobuf::RepeatedPtrField<proto::PrivateAggregationBudgetEntry>*
budget_entries = GetBudgetEntries(api, budgets);
DCHECK(budget_entries);
auto new_end = base::ranges::remove_if(
*budget_entries,
[=](const proto::PrivateAggregationBudgetEntry& elem) {
return elem.entry_start_timestamp() >= serialized_delete_begin &&
elem.entry_start_timestamp() <= serialized_delete_end;
});
budget_entries->erase(new_end, budget_entries->end());
CleanUpStaleBudgetEntries(budget_entries,
earliest_window_in_larger_scope_start);
}
for (proto::ReportingOrigin& elem :
*budgets.mutable_reporting_origins_for_deletion()) {
// If the last used time is in the deletion window, we update it to the
// start of the window. We do this even for reporting origins that don't
// match the filter as the whole site's data was deleted. This may result
// in more data being deleted than strictly necessary.
if (elem.last_used_timestamp() >= serialized_delete_begin &&
elem.last_used_timestamp() <= serialized_delete_end) {
elem.set_last_used_timestamp(serialized_delete_begin);
}
}
CleanUpStaleReportingOrigins(
budgets.mutable_reporting_origins_for_deletion(),
earliest_window_in_larger_scope_start);
storage_->budgets_data()->UpdateData(site_key, budgets);
}
// Force the database to be flushed immediately instead of waiting up to
// `PrivateAggregationBudgetStorage::kFlushDelay`. Runs the `done` callback
// once flushing is complete.
storage_->budgets_data()->FlushDataToDisk(std::move(done));
}
void PrivateAggregationBudgeter::CleanUpStaleDataSoon() {
if (!DidStorageInitializationSucceed()) {
return;
}
if (clean_up_stale_data_timer_.IsRunning()) {
return;
}
// Wait for `kMinStaleDataCleanUpGap` to pass between invocations.
base::TimeTicks now = base::TimeTicks::Now();
base::TimeTicks earliest_allowed_clean_up_time =
last_clean_up_time_ + kMinStaleDataCleanUpGap;
// If enough time has already passed, post a zero-delay task as it does not
// need to be invoked synchronously.
base::TimeDelta wait_time =
std::max(earliest_allowed_clean_up_time - now, base::TimeDelta());
clean_up_stale_data_timer_.Start(
FROM_HERE, wait_time,
base::BindOnce(&PrivateAggregationBudgeter::CleanUpStaleData,
weak_factory_.GetWeakPtr()));
}
void PrivateAggregationBudgeter::CleanUpStaleData() {
CHECK(DidStorageInitializationSucceed());
last_clean_up_time_ = base::TimeTicks::Now();
std::vector<std::string> all_sites;
for (const auto& [site_key, budgets] :
storage_->budgets_data()->GetAllCached()) {
all_sites.push_back(site_key);
}
const int64_t earliest_non_stale_window_start =
CalculateEarliestWindowStartInScope(
/*current_window_start=*/SerializeTimeForStorage(
PrivateAggregationBudgetKey::TimeWindow(base::Time::Now())
.start_time()),
kLargerScopeValues.budget_scope_duration);
for (const std::string& site_key : all_sites) {
proto::PrivateAggregationBudgets budgets;
bool success = storage_->budgets_data()->TryGetData(site_key, &budgets);
CHECK(success);
bool was_modified = false;
for (PrivateAggregationBudgetKey::Api api :
PrivateAggregationBudgetKey::kAllApis) {
google::protobuf::RepeatedPtrField<proto::PrivateAggregationBudgetEntry>*
budget_entries = GetBudgetEntries(api, budgets);
CHECK(budget_entries);
was_modified |= CleanUpStaleBudgetEntries(
budget_entries, earliest_non_stale_window_start);
}
google::protobuf::RepeatedPtrField<proto::ReportingOrigin>*
reporting_origins_for_deletion =
budgets.mutable_reporting_origins_for_deletion();
was_modified |= CleanUpStaleReportingOrigins(
reporting_origins_for_deletion, earliest_non_stale_window_start);
if (!was_modified) {
continue;
}
bool is_entry_empty = budgets.protected_audience_budgets().empty() &&
budgets.shared_storage_budgets().empty() &&
budgets.reporting_origins_for_deletion().empty();
if (is_entry_empty) {
storage_->budgets_data()->DeleteData({site_key});
} else {
storage_->budgets_data()->UpdateData(site_key, budgets);
}
}
}
bool PrivateAggregationBudgeter::DidStorageInitializationSucceed() {
switch (storage_status_) {
case StorageStatus::kPendingInitialization:
case StorageStatus::kInitializing:
NOTREACHED_NORETURN();
case StorageStatus::kInitializationFailed:
return false;
case StorageStatus::kOpen:
return true;
}
}
} // namespace content