blob: 469634a286139a3fe6922fcc4447122d2ef482fb [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.
#ifndef CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_BUDGETER_H_
#define CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_BUDGETER_H_
#include <memory>
#include <set>
#include <vector>
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "content/browser/private_aggregation/private_aggregation_budget_key.h"
#include "content/common/content_export.h"
#include "content/public/browser/private_aggregation_data_model.h"
#include "content/public/browser/storage_partition.h"
namespace base {
class FilePath;
class UpdateableSequencedTaskRunner;
} // namespace base
namespace content {
class PrivateAggregationBudgetStorage;
// UI thread class that provides an interface for querying and updating the
// budget used by each key, i.e. the sum of contributions, by interacting with
// the storage layer. This class is responsible for owning the storage class.
class CONTENT_EXPORT PrivateAggregationBudgeter {
public:
// Public for testing
enum class StorageStatus {
// The database initialization process hasn't started yet.
kPendingInitialization,
// The database is in the process of being initialized.
kInitializing,
// The database initialization did not succeed.
kInitializationFailed,
// The database successfully initialized and can be used.
kOpen,
};
// The result of a request to consume some budget. All results other than
// `kApproved` enumerate different reasons the request was rejected.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class RequestResult {
kApproved = 0,
kInsufficientSmallerScopeBudget = 1,
kInsufficientLargerScopeBudget = 2,
kRequestedMoreThanTotalBudget = 3,
kTooManyPendingCalls = 4,
kInvalidRequest = 5,
kStorageInitializationFailed = 6,
kBadValuesOnDisk = 7,
kMaxValue = kBadValuesOnDisk,
};
// Represents the validity status of the stored budget data for the provided
// site and API retrieved during a `ConsumeBudget()` call. In case multiple
// statuses apply, the first one encountered/detected will be used.
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class BudgetValidityStatus {
kValid = 0,
kValidAndEmpty = 1,
kValidButContainsStaleWindow = 2,
kContainsTimestampInFuture = 3,
kContainsValueExceedingLimit = 4,
kContainsTimestampNotRoundedToMinute = 5,
kSpansMoreThanADay = 6,
kContainsNonPositiveValue = 7,
kMaxValue = kContainsNonPositiveValue,
};
// Represents the two different types of budgets, which differ on the duration
// of time that they apply to and what the allowable budget for that time is.
enum class BudgetScope {
// Scope is per-site per-API per-10 min
kSmallerScope,
// Scope is per-site per-API per-day
kLargerScope,
};
// Encapsulates constants that differ for the two scopes, allowing them to be
// passed around more easily.
struct BudgetScopeValues {
BudgetScope budget_scope;
// Maximum budget allowed to be claimed for this scope.
int max_budget_per_scope;
// The total length of time that per-site per-API budgets are enforced
// against in this scope. (Note that there are 10 time windows per
// `kBudgetSmallerScopeDuration` and 1440 time windows per
// `kBudgetLargerScopeDuration`.)
base::TimeDelta budget_scope_duration;
};
static constexpr BudgetScopeValues kSmallerScopeValues = {
BudgetScope::kSmallerScope, /*max_budget_per_scope=*/65536,
/*budget_scope_duration=*/base::Minutes(10)};
static constexpr BudgetScopeValues kLargerScopeValues = {
BudgetScope::kLargerScope, /*max_budget_per_scope=*/1048576,
/*budget_scope_duration=*/base::Days(1)};
static_assert(kSmallerScopeValues.budget_scope_duration %
PrivateAggregationBudgetKey::TimeWindow::kDuration ==
base::TimeDelta());
static_assert(kLargerScopeValues.budget_scope_duration %
PrivateAggregationBudgetKey::TimeWindow::kDuration ==
base::TimeDelta());
// To avoid unbounded memory growth, limit the number of pending calls during
// initialization. Data clearing calls can be posted even if it would exceed
// this limit.
static constexpr int kMaxPendingCalls = 1000;
// `db_task_runner` should not be nullptr.
PrivateAggregationBudgeter(
scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner,
bool exclusively_run_in_memory,
const base::FilePath& path_to_db_dir);
PrivateAggregationBudgeter(const PrivateAggregationBudgeter& other) = delete;
PrivateAggregationBudgeter& operator=(
const PrivateAggregationBudgeter& other) = delete;
virtual ~PrivateAggregationBudgeter();
// Attempts to consume `budget` for `budget_key`. The callback `on_done` is
// then run with the appropriate `RequestResult`.
//
// The attempt is rejected if it would cause a contribution budget to be
// exceeded, i.e. if the site's per-10 min per-API budget would exceed
// `kSmallerScopeValues.max_budget_per_scope` and/or if the site's daily
// per-API budget would exceed `kLargerScopeValues.max_budget_per_scope` (for
// the 10-min and 24-hour period, respectively, ending at the *end* of
// `budget_key.time_window`, see the budget scope durations above and
// `PrivateAggregationBudgetKey` for more detail). The attempt is also
// rejected if the requested `budget` is non-positive, if `budget_key.origin`
// is not potentially trustworthy or if the database is closed. If the
// database is initializing, this query is queued until the initialization is
// complete. Otherwise, the budget use is recorded and the attempt is
// successful. May clean up stale budget storage. Note that this call assumes
// that budget time windows are non-decreasing. In very rare cases, a network
// time update could allow budget to be used slightly early. Virtual for
// testing.
virtual void ConsumeBudget(int budget,
const PrivateAggregationBudgetKey& budget_key,
base::OnceCallback<void(RequestResult)> on_done);
// Deletes all data in storage for any budgets that could have been set
// between `delete_begin` and `delete_end` time (inclusive). Note that the
// discrete time windows used may lead to more data being deleted than
// strictly necessary. Null times are treated as unbounded lower or upper
// range. If `!filter.is_null()`, budget entries without an origin that
// matches the `filter` are retained (i.e. not cleared). Note that budgets are
// scoped per-site, not per-origin. So, the budget storage keeps track of any
// reporting origins used in the last day and will delete that corresponding
// site's data if the `filter` matches any of those origins.
virtual void ClearData(base::Time delete_begin,
base::Time delete_end,
StoragePartition::StorageKeyMatcherFunction filter,
base::OnceClosure done);
// Runs `callback` with all reporting origins as DataKeys for the Browsing
// Data Model. Partial data will still be returned in the event of an error.
virtual void GetAllDataKeys(
base::OnceCallback<void(std::set<PrivateAggregationDataModel::DataKey>)>
callback);
// Deletes all data in storage for storage keys matching the provided
// reporting origin in the data key.
virtual void DeleteByDataKey(const PrivateAggregationDataModel::DataKey& key,
base::OnceClosure callback);
// TODO(crbug.com/1449005): Clear stale data periodically and on startup.
protected:
// Should only be used for testing/mocking to avoid creating the underlying
// storage.
PrivateAggregationBudgeter();
// Virtual for testing.
virtual void OnStorageDoneInitializing(
std::unique_ptr<PrivateAggregationBudgetStorage> storage);
StorageStatus storage_status_ = StorageStatus::kPendingInitialization;
private:
void EnsureStorageInitializationBegun();
void InitializeStorage(bool exclusively_run_in_memory,
base::FilePath path_to_db_dir);
void ConsumeBudgetImpl(int additional_budget,
const PrivateAggregationBudgetKey& budget_key,
base::OnceCallback<void(RequestResult)> on_done);
void ClearDataImpl(base::Time delete_begin,
base::Time delete_end,
StoragePartition::StorageKeyMatcherFunction filter,
base::OnceClosure done);
void GetAllDataKeysImpl(
base::OnceCallback<void(std::set<PrivateAggregationDataModel::DataKey>)>
callback);
void OnUserVisibleTaskComplete();
void ProcessAllPendingCalls();
bool DidStorageInitializationSucceed();
void OnUserVisibleTaskStarted();
// While the storage initializes, queues calls (e.g. to `ConsumeBudget()`) in
// the order the calls are received. Should be empty after storage is
// initialized. The size is limited to `kMaxPendingCalls` except that
// `ClearData()` can store additional tasks beyond that limit.
std::vector<base::OnceClosure> pending_calls_;
// The task runner for all private aggregation storage operations. Updateable
// to allow for priority to be temporarily increased to `USER_VISIBLE` when a
// clear data task is queued or running. Otherwise `BEST_EFFORT` is used.
scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner_;
// How many user visible storage tasks are queued or running currently, i.e.
// have been posted but the reply has not been run.
int num_pending_user_visible_tasks_ = 0;
// `nullptr` until initialization is complete or if initialization failed.
// Otherwise, owned by this class until destruction. Iff present,
// `storage_status_` should be `kOpen`.
std::unique_ptr<PrivateAggregationBudgetStorage> storage_;
// Holds a closure that will shut down the initializing storage until
// initialization is complete. After then, it is null.
base::OnceClosure shutdown_initializing_storage_;
// When constructing this class, we create a closure that contains the storage
// initialization parameters. On the first call to `ConsumeBudget` or
// `ClearData`, the closure is run to call `InitializeStorage`. This ensures
// that the storage is only initialized when it is needed and avoid incurring
// delay on startup. After then, it is null;
base::OnceClosure initialize_storage_;
base::WeakPtrFactory<PrivateAggregationBudgeter> weak_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_PRIVATE_AGGREGATION_PRIVATE_AGGREGATION_BUDGETER_H_