blob: 66c26d9de2d6f49f7d5b49a8893651793de74129 [file] [log] [blame]
// Copyright 2023 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_CACHING_STORAGE_H_
#define CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_CACHING_STORAGE_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/queue.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "content/browser/interest_group/for_debugging_only_report_util.h"
#include "content/browser/interest_group/interest_group_storage.h"
#include "content/browser/interest_group/interest_group_update.h"
#include "content/browser/interest_group/storage_interest_group.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "third_party/blink/public/common/interest_group/interest_group.h"
#include "url/origin.h"
namespace content {
struct DebugReportLockoutAndCooldowns;
class StorageInterestGroups;
// SingleStorageInterestGroup ensures that pointers to values inside
// StorageInterestGroups are accompanied by a
// scoped_refptr<StorageInterestGroups> to prevent dangling pointers and
// ensures the scoped_refptr<StorageInterestGroups> and
// raw_ptr<StorageInterestGroup> are destructed in the correct order.
class CONTENT_EXPORT SingleStorageInterestGroup {
public:
explicit SingleStorageInterestGroup(
scoped_refptr<StorageInterestGroups> storage_interest_groups_for_owner,
const StorageInterestGroup* storage_interest_group);
SingleStorageInterestGroup(const SingleStorageInterestGroup& other);
// Create a SingleStorageInterestGroup from scratch, including generating a
// StorageInterestGroups featuring just `interest_group`.
explicit SingleStorageInterestGroup(StorageInterestGroup&& interest_group);
~SingleStorageInterestGroup();
SingleStorageInterestGroup& operator=(SingleStorageInterestGroup&& other) =
default;
const StorageInterestGroup* operator->() const;
const StorageInterestGroup& operator*() const;
private:
scoped_refptr<StorageInterestGroups> storage_interest_groups_for_owner;
raw_ptr<const StorageInterestGroup> storage_interest_group;
};
// StorageInterestGroups is needed for InterestGroupCachingStorage
// because it requires weak pointers and ref counted pointers to
// std::vector<StorageInterestGroup>.
class CONTENT_EXPORT StorageInterestGroups
: public base::RefCounted<StorageInterestGroups> {
public:
explicit StorageInterestGroups(
std::vector<StorageInterestGroup>&& interest_groups);
StorageInterestGroups(const StorageInterestGroup& other) = delete;
base::WeakPtr<StorageInterestGroups> GetWeakPtr();
size_t size() { return storage_interest_groups_.size(); }
std::vector<SingleStorageInterestGroup> GetInterestGroups() {
std::vector<SingleStorageInterestGroup> storage_interest_groups;
for (const StorageInterestGroup& interest_group :
storage_interest_groups_) {
storage_interest_groups.emplace_back(this, &interest_group);
}
return storage_interest_groups;
}
std::optional<SingleStorageInterestGroup> FindGroup(std::string_view name);
bool IsExpired() { return expiry_ < base::Time::Now(); }
private:
friend class RefCounted<StorageInterestGroups>;
friend class InterestGroupCachingStorage;
~StorageInterestGroups();
std::vector<StorageInterestGroup> storage_interest_groups_;
base::Time expiry_;
base::WeakPtrFactory<StorageInterestGroups> weak_ptr_factory_{this};
};
// InterestGroupCachingStorage controls access to the Interest Group Database
// through its owned InterestGroupStorage. InterestGroupStorage should
// not be accessed outside of this class. InterestGroupCachingStorage provides a
// pointer to in-memory values for GetInterestGroupsForOwner when available and
// invalidates the cached values when necessary (when an update to the values
// occurs). It also provides cached values of the owner and bidding signals
// origins so that they can be prefetched before loading interest groups.
class CONTENT_EXPORT InterestGroupCachingStorage {
public:
static constexpr base::TimeDelta kMinimumCacheHoldTime = base::Seconds(10);
// The most the entry will be kept in the cache, even if people keep using
// it. (Only effective if clickiness is on, since that requires this for
// some degree of freshness).
static constexpr base::TimeDelta kMaximumCacheHoldTime = base::Seconds(120);
struct CONTENT_EXPORT CachedOriginsInfo {
CachedOriginsInfo();
explicit CachedOriginsInfo(const blink::InterestGroup& group);
CachedOriginsInfo(const CachedOriginsInfo& other) = delete;
CachedOriginsInfo& operator=(const CachedOriginsInfo& other) = delete;
CachedOriginsInfo(CachedOriginsInfo&& other);
CachedOriginsInfo& operator=(CachedOriginsInfo&& other);
~CachedOriginsInfo();
// The name of an owner's latest expiring interest group (of the interest
// groups encountered by the cache via a join or load).
std::string interest_group_name;
// The expiry of the interest group.
base::Time expiry = base::Time::Min();
// The bidding signals origin of the interest group, if it's non-null and
// different from the owner.
std::optional<url::Origin> bidding_signals_origin;
};
explicit InterestGroupCachingStorage(const base::FilePath& path,
bool in_memory);
~InterestGroupCachingStorage();
InterestGroupCachingStorage(const InterestGroupCachingStorage& other) =
delete;
InterestGroupCachingStorage& operator=(
const InterestGroupCachingStorage& other) = delete;
// Gets a list of all interest groups with their bidding information
// associated with the provided owner. If the result is cached,
// a pointer to the in-memory StorageInterestGroups is returned. Otherwise, it
// is loaded fresh from the database or the request is combined with an
// outstanding database call (if an outstanding call exists and the cache has
// not been invalidated since that call).
void GetInterestGroupsForOwner(
const url::Origin& owner,
base::OnceCallback<void(scoped_refptr<StorageInterestGroups>)> callback);
// For a given `owner`, return whether the owner origin and bidding signal
// origin were cached in-memory via UpdateCachedOriginsIfEnabled.
// If the `owner` origin was cached, update `signals_origin` to the one that
// was cached -- or set to nullopt if no bidding signals origin was cached or
// if it would be the same as the owner origin. The cache includes at most one
// entry per origin, and may not reflect the results of interest group
// updates. It's intended to be used for best-effort preconnecting, and should
// not be considered authoritative. It is guaranteed not to contain interest
// groups that have are beyond the max expiration time limit, so preconnecting
// should not leak data the bidder would otherwise have access to, if it so
// desired. That is, manual voluntarily removing or expiring of an interest
// group may not be reflected in the result, but hitting the the global
// interest group lifetime cap will be respected.
bool GetCachedOwnerAndSignalsOrigins(
const url::Origin& owner,
std::optional<url::Origin>& signals_origin);
// Update the cached owner and signal origins for an owner's interest groups
// if kFledgeUsePreconnectCache or kFledgeStartAnticipatoryProcesses are
// enabled and the owner's IGs are still in memory.
void UpdateCachedOriginsIfEnabled(const url::Origin& owner);
// 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.
void JoinInterestGroup(
const blink::InterestGroup& group,
const GURL& main_frame_joining_url,
base::OnceCallback<void(std::optional<InterestGroupKanonUpdateParameter>)>
callback);
// Remove the interest group if it exists.
void LeaveInterestGroup(const blink::InterestGroupKey& group_key,
const url::Origin& main_frame,
base::OnceClosure callback);
// Removes all interest groups owned by `owner` joined from
// `main_frame_origin` except `interest_groups_to_keep`, if they exist.
void ClearOriginJoinedInterestGroups(
const url::Origin& owner,
const std::set<std::string>& interest_groups_to_keep,
const url::Origin& main_frame_origin,
base::OnceCallback<void(std::vector<std::string>)> callback);
// 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.
void UpdateInterestGroup(
const blink::InterestGroupKey& group_key,
InterestGroupUpdate update,
base::OnceCallback<void(std::optional<InterestGroupKanonUpdateParameter>)>
callback);
// 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& groups);
// 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);
// Records a view or a click event. Aggregate time bucketed view and click
// information is provided to bidder's browsing signals in generateBid().
void RecordViewClick(network::AdAuctionEventRecord event_record);
// Invokes `callback` with whether the database has a record of click/view
// events for given combination of provider & eligible origins.
//
// nullopt is passed in in case of an error.
void CheckViewClickInfoInDbForTesting(
url::Origin provider_origin,
url::Origin eligible_origin,
base::OnceCallback<void(std::optional<bool>)> callback);
// 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 = true);
// Gets the last time that the key was reported to the k-anonymity server.
void GetLastKAnonymityReported(
const std::string& hashed_key,
base::OnceCallback<void(std::optional<base::Time>)> callback);
// Updates the last time that the key was reported to the k-anonymity server.
void UpdateLastKAnonymityReported(const std::string& hashed_key);
// Gets a single interest group.
void GetInterestGroup(
const blink::InterestGroupKey& group_key,
base::OnceCallback<void(std::optional<SingleStorageInterestGroup>)>
callback);
// Gets a list of all interest group owners. Each owner will only appear
// once.
void GetAllInterestGroupOwners(
base::OnceCallback<void(std::vector<url::Origin>)> callback);
// For a given owner, gets interest group keys along with their update urls.
// `groups_limit` sets a limit on the maximum number of interest group keys
// that may be returned.
void GetInterestGroupsForUpdate(
const url::Origin& owner,
int groups_limit,
base::OnceCallback<void(std::vector<InterestGroupUpdateParameter>)>
callback);
// Gets lockout and cooldowns of `origins` for sending forDebuggingOnly
// reports.
void GetDebugReportLockoutAndCooldowns(
base::flat_set<url::Origin> origins,
base::OnceCallback<void(std::optional<DebugReportLockoutAndCooldowns>)>
callback);
// Gets lockout and all cooldowns for sending forDebuggingOnly reports.
void GetDebugReportLockoutAndAllCooldowns(
base::OnceCallback<void(std::optional<DebugReportLockoutAndCooldowns>)>
callback);
// Gets a list of all interest group joining origins. Each joining origin
// will only appear once.
void GetAllInterestGroupJoiningOrigins(
base::OnceCallback<void(std::vector<url::Origin>)> callback);
void GetAllInterestGroupOwnerJoinerPairs(
base::OnceCallback<void(std::vector<std::pair<url::Origin, url::Origin>>)>
callback);
void RemoveInterestGroupsMatchingOwnerAndJoiner(url::Origin owner,
url::Origin joining_origin,
base::OnceClosure callback);
// Clear out storage for the matching owning storage key.
void DeleteInterestGroupData(
StoragePartition::StorageKeyMatcherFunction storage_key_matcher,
bool user_initiated_deletion,
base::OnceClosure callback);
// Clear out all interest group storage including k-anonymity store.
void DeleteAllInterestGroupData(base::OnceClosure callback);
// 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);
// 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.
void GetBiddingAndAuctionServerKeys(
const url::Origin& coordinator,
base::OnceCallback<void(std::pair<base::Time, std::string>)> callback);
// Writes all of these keys to the cache, the first vector with
// `is_kanon = true`, and the second vector with `is_kanon = false`.
void WriteHashedKAnonymityKeysToCache(
const std::vector<std::string>& positive_hashed_keys,
const std::vector<std::string>& negative_hashed_keys,
base::Time time_fetched);
// Takes a vector of keys to lookup from the cache. Calls a callback that
// provides 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.
void LoadPositiveHashedKAnonymityKeysFromCache(
const std::vector<std::string>& keys,
base::Time min_valid_time,
base::OnceCallback<void(InterestGroupStorage::KAnonymityCacheResponse)>
callback);
void GetLastMaintenanceTimeForTesting(
base::RepeatingCallback<void(base::Time)> callback) const;
private:
// Once JoinInterestGroup completes successfully, maybe update the cached
// origins and run the callback.
void OnJoinInterestGroup(
const url::Origin& owner,
CachedOriginsInfo cached_origins_info,
base::OnceCallback<void(std::optional<InterestGroupKanonUpdateParameter>)>
callback,
std::optional<InterestGroupKanonUpdateParameter> update);
// After the async call to load interest groups from storage, cache the result
// in a StorageInterestGroups. Also call
// callbacks in outstanding_interest_group_for_owner_callbacks_ with a
// pointer to the just-stored result if the callbacks reference the same
// version.
void OnLoadInterestGroupsForOwner(
const url::Origin& owner,
uint32_t version,
std::vector<StorageInterestGroup> interest_groups);
void InvalidateCachedInterestGroupsForOwner(const url::Origin& owner);
void InvalidateAllCachedInterestGroups();
void MarkOutstandingInterestGroupLoadResultOutdated(const url::Origin& owner);
// Start a timer that holds a reference to `groups` so that it stays in memory
// for a minimum amount of time (kMinimumCacheHoldTime). If such a timer
// already exists, restart it. Staying in memory is relevant because
// `cached_interest_groups_` contains weak pointers.
void StartTimerForInterestGroupHold(
const url::Origin& owner,
scoped_refptr<StorageInterestGroups> groups);
// Callback for the timers in `timed_holds_of_interest_groups_` in
// order to keep `groups` in memory for a minimum amount of time
// (kMinimumCacheHoldTime). When a timer in `timed_holds_of_interest_groups_`
// is done, make sure to delete the timer.
void OnMinimumCacheHoldTimeCompleted(
const url::Origin& owner,
scoped_refptr<StorageInterestGroups> groups) {
timed_holds_of_interest_groups_.erase(owner);
}
base::SequenceBound<InterestGroupStorage> interest_group_storage_;
// Used to retrieve interest groups that are still in memory (e.g. because
// they're bidding in an auction).
std::map<url::Origin, base::WeakPtr<StorageInterestGroups>>
cached_interest_groups_;
// Holds timers that have references to StorageInterestGroups so that the
// StorageInterestGroups stay in memory for a minimum amount of time
// (kMinimumCacheHoldTime). The timers can also be cancelled early upon cache
// invalidation.
std::map<url::Origin, std::unique_ptr<base::OneShotTimer>>
timed_holds_of_interest_groups_;
// Holds callbacks to be run once a load from the database
// (GetInterestGroupsForOwner) is complete. Callbacks are keyed by version
// number in addition to owner so that OnLoadInterestGroupsForOwner does not
// load callbacks asking for a later version of the interest groups.
std::map<std::pair<url::Origin, uint32_t>,
base::queue<
base::OnceCallback<void(scoped_refptr<StorageInterestGroups>)>>>
interest_groups_sequenced_callbacks_;
// For each owner, store the current data version for interest group results.
// A version is incremented when an owner's interest group results are
// invalidated. The versions are reset when
// interest_groups_sequenced_callbacks_ becomes empty.
std::map<url::Origin, uint32_t> valid_interest_group_versions_;
// For each owner for which we've run UpdateCachedOriginsIfEnabled,
// hold onto the owner origin and the origin of the bidding signals url for
// the purpose of preconnecting to them or starting worklets for them in later
// auctions. CachedOriginsInfo tracks the latest expiring interest group that
// we know about to prevent preconnecting to origins no longer in the
// database. Owners may be cleared from the map if the corresponding interest
// group is left or expired. A flat map is used because the number of interest
// group owners is expected to be relatively small.
base::flat_map<url::Origin, CachedOriginsInfo>
cached_owners_and_signals_origins_;
base::WeakPtrFactory<InterestGroupCachingStorage> weak_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_CACHING_STORAGE_H_