blob: 2a5665ebaa963be4f4330039eed981797486ce7b [file] [log] [blame]
// Copyright 2021 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_INTEREST_GROUP_INTEREST_GROUP_STORAGE_H_
#define CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_STORAGE_H_
#include <optional>
#include <set>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/sequence_checker.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "content/browser/interest_group/for_debugging_only_report_util.h"
#include "content/browser/interest_group/interest_group_update.h"
#include "content/browser/interest_group/storage_interest_group.h"
#include "content/common/content_export.h"
#include "content/public/browser/storage_partition.h"
#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom-forward.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace blink {
struct InterestGroup;
}
namespace network {
struct AdAuctionEventRecord;
}
namespace content {
// InterestGroupStorage controls access to the Interest Group Database. All
// public functions perform operations on the database and may block. This
// implementation is not thread-safe so all functions should be called from
// within the same sequence.
class CONTENT_EXPORT InterestGroupStorage {
public:
static constexpr base::TimeDelta kMaintenanceInterval = base::Hours(1);
// Gets the default time the database waits idle before maintenance is
// triggered.
//
// NOTE: This is the default value, which can be overridden by
// CreateWithIdlePeriodForTesting().
static constexpr base::TimeDelta kDefaultIdlePeriod = base::Seconds(30);
// How long to store a k-anon key's last join time.
static constexpr base::TimeDelta kAdditionalKAnonStoragePeriod =
base::Days(1);
// After a successful interest group update, delay the next update until
// kUpdateSucceededBackoffPeriod time has passed.
static constexpr base::TimeDelta kUpdateSucceededBackoffPeriod =
base::Days(1);
// After a failed interest group update, delay the next update until
// kUpdateFailedBackoffPeriod time has passed.
static constexpr base::TimeDelta kUpdateFailedBackoffPeriod = base::Hours(1);
// Constructs an interest group storage based on a SQLite database in the
// `path`/InterestGroups file. If the path passed in is empty, then the
// database is instead stored in memory and not persisted to disk.
explicit InterestGroupStorage(const base::FilePath& path);
InterestGroupStorage(const InterestGroupStorage& other) = delete;
InterestGroupStorage& operator=(const InterestGroupStorage& other) = delete;
~InterestGroupStorage();
// Joins an interest group. If the interest group does not exist, a new one
// is created based on the provided group information. If the interest group
// exists, the existing interest group is overwritten. In either case a join
// record for this interest group is created. Returns the necessary
// information for a k-anon update if the join was successful, or nullopt if
// not.
std::optional<InterestGroupKanonUpdateParameter> JoinInterestGroup(
const blink::InterestGroup& group,
const GURL& main_frame_joining_url);
// Remove the interest group if it exists.
void LeaveInterestGroup(const blink::InterestGroupKey& group_key,
const url::Origin& main_frame);
// Removes all interest groups owned by `owner` joined from
// `main_frame_origin` except `interest_groups_to_keep`, if they exist.
// Returns a (possibly empty) list of all interest groups that were cleared.
std::vector<std::string> ClearOriginJoinedInterestGroups(
const url::Origin& owner,
const std::set<std::string>& interest_groups_to_keep,
const url::Origin& main_frame_origin);
// Gets lockout and cooldowns of `origins` for sending forDebuggingOnly
// reports.
std::optional<DebugReportLockoutAndCooldowns>
GetDebugReportLockoutAndCooldowns(const base::flat_set<url::Origin>& origins);
// Gets lockout and all cooldowns for sending forDebuggingOnly reports.
std::optional<DebugReportLockoutAndCooldowns>
GetDebugReportLockoutAndAllCooldowns();
// Updates the interest group `name` of `owner` with the populated fields of
// `update`.
//
// If it fails for any reason (e.g., the interest group does not exist, or the
// data in `update` is not valid), returns nullopt. Otherwise, returns the
// information required a k-anon update.
std::optional<InterestGroupKanonUpdateParameter> UpdateInterestGroup(
const blink::InterestGroupKey& group_key,
InterestGroupUpdate update);
// Allows the interest group specified by `group_key` to be updated if it was
// last updated before `update_if_older_than`.
void AllowUpdateIfOlderThan(blink::InterestGroupKey group_key,
base::TimeDelta update_if_older_than);
// Report that updating of the interest group with owner `owner` and name
// `name` failed. With the exception of parse failures, the rate limit
// duration for failed updates is shorter than for those that succeed -- for
// successes, UpdateInterestGroup() automatically updates the rate limit
// duration.
void ReportUpdateFailed(const blink::InterestGroupKey& group_key,
bool parse_failure);
// Adds an entry to the bidding history for these interest groups.
void RecordInterestGroupBids(const blink::InterestGroupSet& group);
// Adds an entry to the win history for this interest group. `ad_json` is a
// piece of opaque data to identify the winning ad.
void RecordInterestGroupWin(const blink::InterestGroupKey& group_key,
const std::string& ad_json);
// Adds an entry to forDebuggingOnly report lockout table if the table is
// empty. Otherwise replaces the existing entry.
void RecordDebugReportLockout(base::Time starting_time,
base::TimeDelta duration);
// Adds an entry to forDebuggingOnly report cooldown table for `origin` if it
// does not exist, otherwise replaces the existing entry.
void RecordDebugReportCooldown(const url::Origin& origin,
base::Time cooldown_start,
DebugReportCooldownType cooldown_type);
// Clear out all entries for debug report cooldown information.
void DeleteAllDebugReportCooldowns();
// Records a K-anonymity update for an interest group. If
// `replace_existing_values` is true, this update will store the new
// `update_time` and `positive_hashed_values`, replacing the interest
// group's existing update time and keys. If `replace_existing_values` is
// false, `positive_hashed_keys` will be added to the existing positive keys
// without updating the stored update time. No value is stored if
// `update_time` is older than the `update_time` already stored in the
// database.
void UpdateKAnonymity(const blink::InterestGroupKey& interest_group_key,
const std::vector<std::string>& positive_hashed_keys,
const base::Time update_time,
bool replace_existing_values);
// Gets the last time that the key was reported to the k-anonymity server.
std::optional<base::Time> GetLastKAnonymityReported(
const std::string& hashed_key);
// Updates the last time that the key was reported to the k-anonymity server.
void UpdateLastKAnonymityReported(const std::string& hashed_key);
// Stores the view or click data in `record` so that it may be later included
// in view / click counts loaded for generateBid() browser signals.
void RecordViewClick(const network::AdAuctionEventRecord& record);
// Gets a single interest group.
std::optional<StorageInterestGroup> GetInterestGroup(
const blink::InterestGroupKey& group_key);
// Gets a list of all interest group owners. Each owner will only appear
// once.
std::vector<url::Origin> GetAllInterestGroupOwners();
// Gets a list of all interest groups with their bidding information
// associated with the provided owner.
std::vector<StorageInterestGroup> GetInterestGroupsForOwner(
const url::Origin& owner);
// For a given owner, gets interest group keys along with their update URLs
// and joining origin.
// `groups_limit` sets a limit on the maximum number of interest group keys
// that may be returned.
std::vector<InterestGroupUpdateParameter> GetInterestGroupsForUpdate(
const url::Origin& owner,
size_t groups_limit);
// Gets a list of all interest group joining origins. Each joining origin
// will only appear once.
std::vector<url::Origin> GetAllInterestGroupJoiningOrigins();
std::vector<std::pair<url::Origin, url::Origin>>
GetAllInterestGroupOwnerJoinerPairs();
// Set forDebuggingOnly lockout to the time until all interest groups that
// previously joined expires.
void SetDebugReportLockoutUntilIGExpires();
void RemoveInterestGroupsMatchingOwnerAndJoiner(
const url::Origin& owner,
const url::Origin& joining_origin);
// Clear out storage for the matching owning storage key.
void DeleteInterestGroupData(
StoragePartition::StorageKeyMatcherFunction storage_key_matcher,
bool user_initiated_deletion);
// Clear out all interest group storage including k-anonymity store.
void DeleteAllInterestGroupData();
// Update the interest group priority.
void SetInterestGroupPriority(const blink::InterestGroupKey& group_key,
double priority);
// Merges `update_priority_signals_overrides` with the currently stored
// priority signals of `group`. Doesn't take the cached overrides from the
// caller, which may already have them, in favor of reading them from the
// database, as the values may have been updated on disk since they were read
// by the caller.
void UpdateInterestGroupPriorityOverrides(
const blink::InterestGroupKey& group_key,
base::flat_map<std::string,
auction_worklet::mojom::PrioritySignalsDoublePtr>
update_priority_signals_overrides);
std::vector<StorageInterestGroup> GetAllInterestGroupsUnfilteredForTesting();
// Update B&A keys for a coordinator. This function will overwrite any
// existing keys for the coordinator.
void SetBiddingAndAuctionServerKeys(const url::Origin& coordinator,
std::string serialized_keys,
base::Time expiration);
// Load stored B&A server keys for a coordinator along with the keys'
// expiration.
std::pair<base::Time, std::string> GetBiddingAndAuctionServerKeys(
const url::Origin& coordinator);
// Writes all of these keys to the cache, the first vector with
// `is_kanon = true`, and the second vector with `is_kanon = false`.
// On error, returns false; otherwise true. This function will overwrite any
// previously cached keys.
bool WriteHashedKAnonymityKeysToCache(
const std::vector<std::string>& positive_hashed_keys,
const std::vector<std::string>& negative_hashed_keys,
base::Time fetch_time);
struct CONTENT_EXPORT KAnonymityCacheResponse {
// Unexpired keys found in the cache that are k-anonymous.
std::vector<std::string> positive_hashed_keys_from_cache;
// Includes all keys not found in the cache.
std::vector<std::string> ids_to_query_from_server;
KAnonymityCacheResponse(
std::vector<std::string> _positive_hashed_keys_from_cache,
std::vector<std::string> _ids_to_query_from_server);
KAnonymityCacheResponse(const KAnonymityCacheResponse& other);
KAnonymityCacheResponse& operator=(const KAnonymityCacheResponse& other);
~KAnonymityCacheResponse();
};
// Takes a vector of keys to lookup from the cache. Returns two vectors of
// keys: the first a vector that includes those unexpired keys for which it
// was found in the cache that that key is k-anonymous, the second a vector
// that includes all keys not found in the cache. On error, this returns a
// `KAnonymityCacheResponse` with empty `positive_hashed_keys_from_cache`
// and all `keys` in `ids_to_query_from_server`.
KAnonymityCacheResponse LoadPositiveHashedKAnonymityKeysFromCache(
const std::vector<std::string>& keys,
base::Time check_time);
// Returns various resource limits, as configured by feature params.
static size_t MaxOwnerRegularInterestGroups();
static size_t MaxOwnerNegativeInterestGroups();
static size_t MaxOwnerStorageSize();
base::Time GetLastMaintenanceTimeForTesting() const;
static int GetCurrentVersionNumberForTesting();
// Creates an instance where the idle period is set to `idle_period` instead
// of kDefaultIdlePeriod.
static std::unique_ptr<InterestGroupStorage> CreateWithIdlePeriodForTesting(
const base::FilePath& path,
base::TimeDelta idle_period);
// Resets the internal idle timer without performing any database operation.
//
// Technically, this test hook isn't necessary, since tests could just call
// GetAllInterestGroupOwners() and throw away the result, but this method is
// *much* more performant (due to not doing any I/O), especially when calling
// in a loop.
//
// NOTE: This just calls EnsureDBInitialized() internally.
void ResetIdleTimerForTesting();
// Used when compacting clickiness information. Represents the raw uncompacted
// and compacted protobuf events loaded directly from the
// view_and_click_events table, for one of either views or clicks.
//
// Compacted events have a timestamp and count and are older than 1 hour,
// whereas uncompacted events only have a timestamp.
//
// Exposed for use with `ComputeCompactClickinessForTesting()`.
struct ClickinessCompactionRawEvents {
std::string uncompacted_events;
std::string compacted_events;
};
// Computes compacted form of clickiness information stored as protos in
// the database, combining older events at lower time resolution.
static std::optional<InterestGroupStorage::ClickinessCompactionRawEvents>
ComputeCompactClickinessForTesting(
base::Time now,
const InterestGroupStorage::ClickinessCompactionRawEvents& raw);
// Returns whether there is no entry for given
// (provider_origin, eligible_origin) in database view/click table.
//
// Returns nullopt in case of an error.
std::optional<bool> CheckViewClickCountsForProviderAndEligibleInDbForTesting(
const url::Origin& provider_origin,
const url::Origin& eligible_origin);
private:
// Private constructor that allows changing the idle period, used by
// CreateWithIdlePeriodForTesting().
//
// `idle_period` may be optionally specified to override in tests the default
// time the database waits idle before maintenance is triggered
// (kDefaultIdlePeriod).
InterestGroupStorage(const base::FilePath& path, base::TimeDelta idle_period);
bool EnsureDBInitialized();
bool InitializeDB();
bool InitializeSchema();
void PerformDBMaintenance();
void DatabaseErrorCallback(int extended_error, sql::Statement* stmt);
const base::FilePath path_to_database_;
// Maximum number of interest groups, or interest group owners to keep in the
// database.
// Set by the related blink::feature parameters
// kInterestGroupStorageMaxOwners,
// kInterestGroupStorageMaxGroupsPerOwner, and
// kInterestGroupStorageMaxNegativeGroupsPerOwner.
const size_t max_owners_;
const size_t max_owner_regular_interest_groups_;
const size_t max_owner_negative_interest_groups_;
const size_t max_owner_storage_size_;
// Maximum number of operations allowed between maintenance calls.
// Set by the related blink::feature parameter
// kInterestGroupStorageMaxOpsBeforeMaintenance.
const size_t max_ops_before_maintenance_;
std::unique_ptr<sql::Database> db_ GUARDED_BY_CONTEXT(sequence_checker_);
base::DelayTimer db_maintenance_timer_ GUARDED_BY_CONTEXT(sequence_checker_);
base::Time last_access_time_ GUARDED_BY_CONTEXT(sequence_checker_) =
base::Time::Min();
base::Time last_maintenance_time_ GUARDED_BY_CONTEXT(sequence_checker_) =
base::Time::Min();
unsigned int ops_since_last_maintenance_
GUARDED_BY_CONTEXT(sequence_checker_) = 0;
SEQUENCE_CHECKER(sequence_checker_);
};
} // namespace content
#endif // CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_STORAGE_H_