blob: 296e9db66bf1045b0d81b5c44f3175f144e0f8d3 [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.
#include "content/browser/interest_group/interest_group_storage.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/adapters.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/sequence_checker.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/trace_event/typed_macros.h"
#include "base/types/pass_key.h"
#include "content/browser/interest_group/bidding_and_auction_server_key_fetcher.h"
#include "content/browser/interest_group/for_debugging_only_report_util.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/interest_group_k_anonymity_manager.h"
#include "content/browser/interest_group/interest_group_storage.pb.h"
#include "content/browser/interest_group/interest_group_storage_metric_types.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 "crypto/sha2.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/network/public/cpp/ad_auction/event_record.h"
#include "services/network/public/cpp/features.h"
#include "sql/database.h"
#include "sql/error_delegate_util.h"
#include "sql/meta_table.h"
#include "sql/recovery.h"
#include "sql/statement.h"
#include "sql/statement_id.h"
#include "sql/transaction.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/interest_group/ad_auction_constants.h"
#include "third_party/blink/public/common/interest_group/interest_group.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"
#include "third_party/snappy/src/snappy.h"
#include "third_party/sqlite/sqlite3.h"
#include "url/origin.h"
namespace content {
namespace {
using PassKey = base::PassKey<InterestGroupStorage>;
using blink::mojom::BiddingBrowserSignalsPtr;
using blink::mojom::PreviousWinPtr;
using blink::mojom::ViewAndClickCountsPtr;
using blink::mojom::ViewOrClickCountsPtr;
using SellerCapabilitiesType = blink::SellerCapabilitiesType;
using network::AdAuctionEventRecord;
// Allow lookups using `std::string_view`.
struct StringViewHasher : public std::hash<std::string_view> {
using is_transparent = void;
};
using InterestGroupsByName = std::unordered_map<std::string,
StorageInterestGroup,
StringViewHasher,
std::equal_to<>>;
// The raw view and click data for a given (provider_origin, eligible_origin)
// tuple.
struct ViewClickCountsForProviderAndEligible {
ListOfTimestamps uncompacted_view_events;
ListOfTimestampAndCounts compacted_view_events;
ListOfTimestamps uncompacted_click_events;
ListOfTimestampAndCounts compacted_click_events;
};
// Reason why a database lookup couldn't produce a result. It could be an
// error, or just because there is nothing there.
enum class MissingReason { kNotInDb, kDbError, kDecodeError };
const base::FilePath::CharType kDatabasePath[] =
FILE_PATH_LITERAL("InterestGroups");
// Version number of the database.
//
// Version 1 - 2021/03 - crrev.com/c/2757425
// Version 2 - 2021/08 - crrev.com/c/3097715
// Version 3 - 2021/09 - crrev.com/c/3165576
// Version 4 - 2021/10 - crrev.com/c/3172863
// Version 5 - 2021/10 - crrev.com/c/3067804
// Version 6 - 2021/12 - crrev.com/c/3330516
// Version 7 - 2022/03 - crrev.com/c/3517534
// Version 8 - 2022/06 - crrev.com/c/3696265
// Version 9 - 2022/07 - crrev.com/c/3780305
// Version 10 - 2022/08 - crrev.com/c/3818142
// Version 13 - 2023/01 - crrev.com/c/4167800
// Version 14 - 2023/08 - crrev.com/c/4739632
// Version 15 - 2023/08 - crrev.com/c/4808727
// Version 16 - 2023/08 - crrev.com/c/4822944
// Version 17 - 2023/09 - crrev.com/c/4852051
// Version 18 - 2023/09 - crrev.com/c/4902233
// Version 19 - 2023/10 - crrev.com/c/4891458
// Version 20 - 2023/11 - crrev.com/c/5050989
// Version 21 - 2023/11 - crrev.com/c/5063314
// Version 22 - 2023/12 - crrev.com/c/5063589
// Version 23 - 2024/01 - crrev.com/c/5173733
// Version 24 - 2024/01 - crrev.com/c/5245196
// Version 25 - 2024/04 - crrev.com/c/5497898
// Version 26 - 2024/05 - crrev.com/c/5555460
// Version 27 - 2024/05 - crrev.com/c/5521957
// Version 28 - 2024/06 - crrev.com/c/5647523
// Version 29 - 2024/06 - crrev.com/c/5753049
// Version 30 - 2024/08 - crrev.com/c/5707491
// Version 31 - 2025/01 - crrev.com/c/6084483
// Version 32 - 2025/02 - crrev.com/c/6239846
// Version 33 - 2025/02 - crrev.com/c/6248184
// Version 34 - 2025/02 - crrev.com/c/6256880
// Version 35 - 2025/03 - crrev.com/c/6361984
//
// Version 1 adds a table for interest groups.
// Version 2 adds a column for rate limiting interest group updates.
// Version 3 adds a field for ad components.
// Version 4 adds joining origin and url.
// Version 5 adds k-anonymity tables and fields.
// Version 6 adds WebAssembly helper url.
// Version 7 changes an index, adds interest group priority.
// Version 8 adds the execution_mode field to interest groups.
// Version 9 changes bid_history and join_history to daily counts.
// Version 10 changes k-anonymity table so it doesn't split by type.
// Version 11 adds priority vector support and time a group was joined.
// Version 12 adds seller capabilities fields.
// Version 13 adds ad size-related fields (ad_sizes & size_groups).
// Version 14 adds auction server request flags.
// Version 15 adds an additional bid key field.
// Version 16 changes the ads and ad component columns of the interest group
// table to protobuf format.
// Version 17 adds interest group name and owner columns to the k-anonymity
// table.
// Version 18 adds a new index on IG type (regular vs negative) to support
// split caps on max interest groups per owner.
// Version 19 adds the aggregation_coordinator_origin and storage_size columns
// to the interest group table.
// Version 20 adds the lockout_debugging_only_report and
// cooldown_debugging_only_report tables.
// Version 21 adds the trusted_bidding_signals_slot_size_mode column to the
// interest group table.
// Version 22 adds id column to the debug report lockout table, and changes
// starting and duration columns to starting_time and type columns to the debug
// report cooldown table.
// Version 23 adds trusted bidding signals URL length limit.
// Version 24 adds cached B&A server keys.
// Version 25 uses hashed k-anon keys instead of the unhashed versions.
// Version 26 runs a VACUUM command.
// Version 27 stores k-anon values and update times in interest group table.
// Version 28 adds trusted bidding signals coordinator.
// Version 29 adds selectableBuyerAndSellerReportingIds field to ad object.
// Version 30 compresses the AdsProto field using Snappy compression and runs a
// VACUUM command.
// Version 31 adds creative_scanning_metadata field to ad object.
// Version 32 adds duration column to the debug report lockout table, and
// renames its last_report_sent_time column to starting_time.
// Version 33 adds view_and_click_counts_providers interest_groups field.
// Version 34 adds view_and_click_events table.
// Version 35 adds a cache of k-anon hashes previously retrieved from the
// k-anonymity server with a TTL of the QueryInterval.
const int kCurrentVersionNumber = 35;
// Earliest version of the code which can use a |kCurrentVersionNumber| database
// without failing. This is used to determine if an upgraded version of the
// database -- upgraded by running a newer version of the client -- can still
// work when later running on an older version of the client. Note that if the
// older version of the client isn't identified as compatible, the client will
// wipe the database entirely before proceeding. As a rule of thumb, this
// probably needs to be incremented to the `kCurrentVersionNumber` if you're
// adding or deleting columns to an existing table, but not if you're just
// adding a new table.
const int kCompatibleVersionNumber = 33;
// Latest version of the database that cannot be upgraded to
// |kCurrentVersionNumber| without razing the database.
const int kDeprecatedVersionNumber = 5;
// Truncates `in` down to the hour.
base::Time TruncateToHour(base::Time in) {
return base::Time::FromDeltaSinceWindowsEpoch(
in.ToDeltaSinceWindowsEpoch().FloorToMultiple(base::Hours(1)));
}
// Truncates `in` down to the day.
base::Time TruncateToDay(base::Time in) {
return base::Time::FromDeltaSinceWindowsEpoch(
in.ToDeltaSinceWindowsEpoch().FloorToMultiple(base::Days(1)));
}
std::string Serialize(base::ValueView value_view) {
std::optional<std::string> json_output = base::WriteJson(value_view);
if (json_output.has_value()) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.JSONSerializationResult",
InterestGroupStorageJSONSerializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.JSONSerializationResult",
InterestGroupStorageJSONSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
return std::move(json_output).value_or(std::string());
}
std::optional<base::Value> DeserializeValue(std::string_view serialized_value) {
if (serialized_value.empty()) {
return {};
}
std::optional<base::Value> result = base::JSONReader::Read(
serialized_value, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
if (result) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.JSONDeserializationResult",
InterestGroupStorageJSONDeserializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.JSONDeserializationResult",
InterestGroupStorageJSONDeserializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
return result;
}
std::string Serialize(const url::Origin& origin) {
return origin.Serialize();
}
url::Origin DeserializeOrigin(std::string_view serialized_origin) {
return url::Origin::Create(GURL(serialized_origin));
}
std::string Serialize(const std::optional<GURL>& url) {
if (!url) {
return std::string();
}
return url->spec();
}
std::optional<GURL> DeserializeURL(std::string_view serialized_url) {
GURL result(serialized_url);
if (result.is_empty()) {
return std::nullopt;
}
return result;
}
blink::InterestGroup::Ad FromInterestGroupAdValue(const PassKey& passkey,
const base::Value::Dict& dict,
bool for_components) {
const std::string* maybe_url = dict.FindString("url");
if (!maybe_url) {
return blink::InterestGroup::Ad();
}
blink::InterestGroup::Ad result(passkey, *maybe_url);
const std::string* maybe_size_group = dict.FindString("size_group");
if (maybe_size_group) {
result.size_group = *maybe_size_group;
}
if (!for_components) {
const std::string* maybe_buyer_reporting_id =
dict.FindString("buyer_reporting_id");
if (maybe_buyer_reporting_id) {
result.buyer_reporting_id = *maybe_buyer_reporting_id;
}
const std::string* maybe_buyer_and_seller_reporting_id =
dict.FindString("buyer_and_seller_reporting_id");
if (maybe_buyer_and_seller_reporting_id) {
result.buyer_and_seller_reporting_id =
*maybe_buyer_and_seller_reporting_id;
}
const auto* maybe_selectable_buyer_and_seller_reporting_ids =
dict.FindList("selectable_buyer_and_seller_reporting_ids");
if (maybe_selectable_buyer_and_seller_reporting_ids) {
std::vector<std::string> selectable_buyer_and_seller_reporting_ids;
for (const auto& id : *maybe_selectable_buyer_and_seller_reporting_ids) {
// TODO: `base::Value::GetString()` will crash if this value is not a
// string, but this data originates from a potentially untrusted
// database. Operations like this, including `DCHECK`s, should be
// audited throughout this file.
selectable_buyer_and_seller_reporting_ids.emplace_back(id.GetString());
}
result.selectable_buyer_and_seller_reporting_ids =
std::move(selectable_buyer_and_seller_reporting_ids);
}
const auto* maybe_allowed_reporting_origins =
dict.FindList("allowed_reporting_origins");
if (maybe_allowed_reporting_origins) {
std::vector<url::Origin> allowed_reporting_origins_vector;
for (const auto& origin : *maybe_allowed_reporting_origins) {
const std::string* origin_str = origin.GetIfString();
DCHECK(origin_str);
allowed_reporting_origins_vector.emplace_back(
DeserializeOrigin(*origin_str));
}
result.allowed_reporting_origins =
std::move(allowed_reporting_origins_vector);
}
}
const std::string* maybe_metadata = dict.FindString("metadata");
if (maybe_metadata) {
result.metadata = *maybe_metadata;
}
const std::string* maybe_ad_render_id = dict.FindString("ad_render_id");
if (maybe_ad_render_id) {
result.ad_render_id = *maybe_ad_render_id;
}
return result;
}
std::string Serialize(
const std::optional<base::flat_map<std::string, double>>& flat_map) {
if (!flat_map) {
return std::string();
}
base::Value::Dict dict;
for (const auto& key_value_pair : *flat_map) {
dict.Set(key_value_pair.first, key_value_pair.second);
}
return Serialize(dict);
}
std::optional<base::flat_map<std::string, double>> DeserializeStringDoubleMap(
std::string_view serialized_flat_map) {
std::optional<base::Value> flat_map_value =
DeserializeValue(serialized_flat_map);
if (!flat_map_value || !flat_map_value->is_dict()) {
return std::nullopt;
}
// Extract all key/values pairs to a vector before writing to a flat_map,
// since flat_map insertion is O(n).
std::vector<std::pair<std::string, double>> pairs;
for (const auto pair : flat_map_value->GetDict()) {
if (!pair.second.is_double()) {
return std::nullopt;
}
pairs.emplace_back(pair.first, pair.second.GetDouble());
}
return base::flat_map<std::string, double>(std::move(pairs));
}
AdProtos GetAdProtosFromAds(const std::vector<blink::InterestGroup::Ad>& ads) {
AdProtos ad_protos;
for (const blink::InterestGroup::Ad& ad : ads) {
AdProtos_AdProto* ad_proto = ad_protos.add_ads();
ad_proto->set_render_url(ad.render_url());
if (ad.size_group.has_value()) {
ad_proto->set_size_group(*ad.size_group);
}
if (ad.metadata.has_value()) {
ad_proto->set_metadata(*ad.metadata);
}
if (ad.buyer_reporting_id.has_value()) {
ad_proto->set_buyer_reporting_id(*ad.buyer_reporting_id);
}
if (ad.buyer_and_seller_reporting_id.has_value()) {
ad_proto->set_buyer_and_seller_reporting_id(
*ad.buyer_and_seller_reporting_id);
}
if (ad.selectable_buyer_and_seller_reporting_ids.has_value()) {
for (const auto& id : *ad.selectable_buyer_and_seller_reporting_ids) {
ad_proto->add_selectable_buyer_and_seller_reporting_ids(id);
}
}
if (ad.ad_render_id.has_value()) {
ad_proto->set_ad_render_id(*ad.ad_render_id);
}
if (ad.allowed_reporting_origins.has_value()) {
for (auto allowed_reporting_origin : *ad.allowed_reporting_origins) {
ad_proto->add_allowed_reporting_origins(
allowed_reporting_origin.Serialize());
}
}
if (ad.creative_scanning_metadata.has_value()) {
ad_proto->set_creative_scanning_metadata(*ad.creative_scanning_metadata);
}
}
return ad_protos;
}
// Upgrade code needs to serialize without compression -- otherwise, the
// Serialize() method below is used.
std::string SerializeUncompressed(
const std::optional<std::vector<blink::InterestGroup::Ad>>& ads) {
base::TimeTicks start = base::TimeTicks::Now();
std::string serialized_ads;
AdProtos ad_protos =
ads.has_value() ? GetAdProtosFromAds(ads.value()) : AdProtos();
if (ad_protos.SerializeToString(&serialized_ads)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.AdProtos",
InterestGroupStorageProtoSerializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.AdProtos",
InterestGroupStorageProtoSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
base::UmaHistogramTimes("Storage.InterestGroup.AdProtoSerializationTime",
base::TimeTicks::Now() - start);
return serialized_ads;
}
std::string Serialize(
const std::optional<std::vector<blink::InterestGroup::Ad>>& ads) {
std::string serialized_ads = SerializeUncompressed(ads);
std::string compressed;
base::TimeTicks start = base::TimeTicks::Now();
snappy::Compress(serialized_ads.data(), serialized_ads.size(), &compressed);
base::UmaHistogramTimes("Storage.InterestGroup.AdProtoCompressionTime",
base::TimeTicks::Now() - start);
if (serialized_ads.size() > 0u) {
base::UmaHistogramPercentage(
"Storage.InterestGroup.AdProtoCompressionRatio",
compressed.size() * 100 / serialized_ads.size());
}
base::UmaHistogramCounts1M("Storage.InterestGroup.AdProtoSizeUncompressed",
serialized_ads.size());
base::UmaHistogramCounts1M("Storage.InterestGroup.AdProtoSizeCompressed",
compressed.size());
return compressed;
}
std::optional<std::vector<blink::InterestGroup::Ad>>
DeserializeInterestGroupAdVectorJson(const PassKey& passkey,
std::string_view serialized_ads,
bool for_components) {
std::optional<base::Value> ads_value = DeserializeValue(serialized_ads);
if (!ads_value || !ads_value->is_list()) {
return std::nullopt;
}
std::vector<blink::InterestGroup::Ad> result;
for (const auto& ad_value : ads_value->GetList()) {
const base::Value::Dict* dict = ad_value.GetIfDict();
if (dict) {
result.emplace_back(
FromInterestGroupAdValue(passkey, *dict, for_components));
}
}
return result;
}
// Upgrade code needs to deserialize without decompression -- otherwise,
// DecompressAndDeserializeInterestGroupAdVectorProto() is used.
std::optional<std::vector<blink::InterestGroup::Ad>>
DeserializeInterestGroupAdVectorProto(const PassKey& passkey,
std::string_view serialized_ads) {
base::TimeTicks start = base::TimeTicks::Now();
AdProtos ad_protos;
bool success = ad_protos.ParseFromString(serialized_ads);
if (success) {
UMA_HISTOGRAM_ENUMERATION(
"Storage.InterestGroup.ProtoDeserializationResult.AdProtos",
InterestGroupStorageProtoDeserializationResult::kSucceeded);
} else {
UMA_HISTOGRAM_ENUMERATION(
"Storage.InterestGroup.ProtoDeserializationResult.AdProtos",
InterestGroupStorageProtoDeserializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
if (!success || ad_protos.ads().empty()) {
return std::nullopt;
}
std::vector<blink::InterestGroup::Ad> out;
out.reserve(ad_protos.ads_size());
for (auto& ad_proto : *ad_protos.mutable_ads()) {
blink::InterestGroup::Ad& ad =
out.emplace_back(passkey, std::move(*ad_proto.mutable_render_url()));
if (ad_proto.has_size_group()) {
ad.size_group = std::move(*ad_proto.mutable_size_group());
}
if (ad_proto.has_metadata()) {
ad.metadata = std::move(*ad_proto.mutable_metadata());
}
if (ad_proto.has_buyer_reporting_id()) {
ad.buyer_reporting_id = std::move(*ad_proto.mutable_buyer_reporting_id());
}
if (ad_proto.has_buyer_and_seller_reporting_id()) {
ad.buyer_and_seller_reporting_id =
std::move(*ad_proto.mutable_buyer_and_seller_reporting_id());
}
if (base::FeatureList::IsEnabled(
blink::features::kFledgeAuctionDealSupport) &&
!ad_proto.selectable_buyer_and_seller_reporting_ids().empty()) {
std::vector<std::string> selectable_buyer_and_seller_reporting_ids;
for (const auto& id :
ad_proto.selectable_buyer_and_seller_reporting_ids()) {
selectable_buyer_and_seller_reporting_ids.emplace_back(id);
}
ad.selectable_buyer_and_seller_reporting_ids =
std::move(selectable_buyer_and_seller_reporting_ids);
}
if (ad_proto.has_ad_render_id()) {
ad.ad_render_id = std::move(*ad_proto.mutable_ad_render_id());
}
if (!ad_proto.allowed_reporting_origins().empty()) {
std::vector<url::Origin> allowed_reporting_origins_vector;
allowed_reporting_origins_vector.reserve(
ad_proto.allowed_reporting_origins_size());
for (const std::string& allowed_reporting_origin :
ad_proto.allowed_reporting_origins()) {
allowed_reporting_origins_vector.emplace_back(
DeserializeOrigin(allowed_reporting_origin));
}
ad.allowed_reporting_origins =
std::move(allowed_reporting_origins_vector);
}
if (ad_proto.has_creative_scanning_metadata()) {
ad.creative_scanning_metadata =
std::move(*ad_proto.mutable_creative_scanning_metadata());
}
}
UMA_HISTOGRAM_TIMES("Storage.InterestGroup.AdProtoDeserializationTime",
base::TimeTicks::Now() - start);
return out;
}
std::optional<std::vector<blink::InterestGroup::Ad>>
DecompressAndDeserializeInterestGroupAdVectorProto(
const PassKey& passkey,
std::string_view compressed) {
std::string serialized_ads;
base::TimeTicks start = base::TimeTicks::Now();
if (!snappy::Uncompress(compressed.data(), compressed.size(),
&serialized_ads)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.AdProtoDecompressionOutcome",
AdProtoDecompressionOutcome::kFailure);
return std::nullopt;
}
UMA_HISTOGRAM_TIMES("Storage.InterestGroup.AdProtoDecompressionTime",
base::TimeTicks::Now() - start);
UMA_HISTOGRAM_ENUMERATION("Storage.InterestGroup.AdProtoDecompressionOutcome",
AdProtoDecompressionOutcome::kSuccess);
return DeserializeInterestGroupAdVectorProto(passkey, serialized_ads);
}
std::string Serialize(
const std::optional<base::flat_map<std::string, blink::AdSize>>& ad_sizes) {
if (!ad_sizes) {
return std::string();
}
base::Value::Dict dict;
for (const auto& key_value_pair : *ad_sizes) {
base::Value::Dict size_dict;
size_dict.Set("width", key_value_pair.second.width);
size_dict.Set("width_units",
static_cast<int>(key_value_pair.second.width_units));
size_dict.Set("height", key_value_pair.second.height);
size_dict.Set("height_units",
static_cast<int>(key_value_pair.second.height_units));
dict.Set(key_value_pair.first, Serialize(size_dict));
}
return Serialize(dict);
}
std::optional<base::flat_map<std::string, blink::AdSize>>
DeserializeStringSizeMap(std::string_view serialized_sizes) {
std::optional<base::Value> dict = DeserializeValue(serialized_sizes);
if (!dict || !dict->is_dict()) {
return std::nullopt;
}
std::vector<std::pair<std::string, blink::AdSize>> result;
for (std::pair<const std::string&, base::Value&> entry : dict->GetDict()) {
std::optional<base::Value> ads_size =
DeserializeValue(entry.second.GetString());
const base::Value::Dict* size_dict = ads_size->GetIfDict();
DCHECK(size_dict);
const base::Value* width_val = size_dict->Find("width");
const base::Value* width_units_val = size_dict->Find("width_units");
const base::Value* height_val = size_dict->Find("height");
const base::Value* height_units_val = size_dict->Find("height_units");
if (!width_val || !width_units_val || !height_val || !height_units_val) {
return std::nullopt;
}
result.emplace_back(entry.first,
blink::AdSize(width_val->GetDouble(),
static_cast<blink::AdSize::LengthUnit>(
width_units_val->GetInt()),
height_val->GetDouble(),
static_cast<blink::AdSize::LengthUnit>(
height_units_val->GetInt())));
}
return result;
}
std::string Serialize(
const std::optional<base::flat_map<std::string, std::vector<std::string>>>&
size_groups) {
if (!size_groups) {
return std::string();
}
base::Value::Dict dict;
for (const auto& key_value_pair : *size_groups) {
base::Value::List list;
for (const auto& s : key_value_pair.second) {
list.Append(s);
}
dict.Set(key_value_pair.first, Serialize(list));
}
return Serialize(dict);
}
std::optional<base::flat_map<std::string, std::vector<std::string>>>
DeserializeStringStringVectorMap(std::string_view serialized_groups) {
std::optional<base::Value> dict = DeserializeValue(serialized_groups);
if (!dict || !dict->is_dict()) {
return std::nullopt;
}
std::vector<std::pair<std::string, std::vector<std::string>>> result;
for (std::pair<const std::string&, base::Value&> entry : dict->GetDict()) {
std::optional<base::Value> list =
DeserializeValue(entry.second.GetString());
DCHECK(list && list->is_list());
std::vector<std::string> result_sizes;
for (base::Value& size : list->GetList()) {
result_sizes.emplace_back(size.GetString());
}
result.emplace_back(entry.first, result_sizes);
}
return result;
}
std::string Serialize(const std::optional<std::vector<std::string>>& strings) {
if (!strings) {
return std::string();
}
base::Value::List list;
for (const auto& s : strings.value()) {
list.Append(s);
}
return Serialize(list);
}
std::optional<std::vector<std::string>> DeserializeStringVector(
std::string_view serialized_vector) {
std::optional<base::Value> list = DeserializeValue(serialized_vector);
if (!list || !list->is_list()) {
return std::nullopt;
}
std::vector<std::string> result;
for (const auto& value : list->GetList()) {
result.push_back(value.GetString());
}
return result;
}
std::string Serialize(const std::optional<std::vector<url::Origin>>& origins) {
std::string serialized;
ListOfOrigins list_of_origins;
if (origins) {
for (const url::Origin& o : origins.value()) {
list_of_origins.add_origins(o.Serialize());
}
}
if (list_of_origins.SerializeToString(&serialized)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.ListOfOrigins",
InterestGroupStorageProtoSerializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.ListOfOrigins",
InterestGroupStorageProtoSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
return serialized;
}
std::optional<std::vector<url::Origin>> DeserializeOriginVector(
std::string_view serialized_vector) {
ListOfOrigins list_of_origins;
bool success = list_of_origins.ParseFromString(serialized_vector);
if (success) {
UMA_HISTOGRAM_ENUMERATION(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfOrigins",
InterestGroupStorageProtoDeserializationResult::kSucceeded);
} else {
UMA_HISTOGRAM_ENUMERATION(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfOrigins",
InterestGroupStorageProtoDeserializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
if (!success || list_of_origins.origins().empty()) {
return std::nullopt;
}
std::vector<url::Origin> result;
result.reserve(list_of_origins.origins_size());
for (const std::string& origin_string : list_of_origins.origins()) {
result.emplace_back(DeserializeOrigin(origin_string));
}
return result;
}
int64_t Serialize(SellerCapabilitiesType capabilities) {
uint64_t result = capabilities.ToEnumBitmask();
// Supporting 64 or more seller capabilities will require a different
// serialization format.
DCHECK(result <= std::numeric_limits<int64_t>::max());
return static_cast<int64_t>(result);
}
SellerCapabilitiesType DeserializeSellerCapabilities(int64_t serialized) {
DCHECK(serialized >= 0);
return SellerCapabilitiesType::FromEnumBitmask(serialized);
}
std::string Serialize(
const std::optional<base::flat_map<url::Origin, SellerCapabilitiesType>>&
flat_map) {
if (!flat_map) {
return std::string();
}
base::Value::Dict dict;
for (const auto& key_value_pair : *flat_map) {
dict.Set(Serialize(key_value_pair.first),
base::NumberToString(Serialize(key_value_pair.second)));
}
return Serialize(dict);
}
std::optional<base::flat_map<url::Origin, SellerCapabilitiesType>>
DeserializeSellerCapabilitiesMap(std::string_view serialized) {
std::optional<base::Value> dict = DeserializeValue(serialized);
if (!dict || !dict->is_dict()) {
return std::nullopt;
}
std::vector<std::pair<url::Origin, SellerCapabilitiesType>> result;
for (std::pair<const std::string&, base::Value&> entry : dict->GetDict()) {
std::string* value_string = entry.second.GetIfString();
if (!value_string) {
return std::nullopt;
}
int64_t value_bitmask;
if (!base::StringToInt64(*value_string, &value_bitmask)) {
return std::nullopt;
}
result.emplace_back(DeserializeOrigin(entry.first),
DeserializeSellerCapabilities(value_bitmask));
}
return result;
}
int64_t Serialize(blink::AuctionServerRequestFlags flags) {
// Supporting 64 or more auction server request flags will require a different
// serialization format. That check is done in EnumSet at compile time, so we
// don't need to duplicate it here.
return flags.ToEnumBitmask();
}
blink::AuctionServerRequestFlags DeserializeAuctionServerRequestFlags(
int64_t serialized) {
return blink::AuctionServerRequestFlags::FromEnumBitmask(serialized);
}
std::vector<uint8_t> Serialize(
const std::optional<blink::InterestGroup::AdditionalBidKey>& key) {
if (!key || key->empty()) {
return std::vector<uint8_t>();
}
return std::vector<uint8_t>(key->begin(), key->end());
}
std::optional<blink::InterestGroup::AdditionalBidKey>
DeserializeAdditionalBidKey(base::span<const uint8_t> serialized) {
if (serialized.size() != ED25519_PUBLIC_KEY_LEN) {
return std::nullopt;
}
blink::InterestGroup::AdditionalBidKey deserialized;
std::copy(serialized.begin(), serialized.end(), deserialized.begin());
return deserialized;
}
// Merges new `priority_signals_overrides` received from an update with an
// existing set of overrides store with an interest group. Populates `overrides`
// if it was previously null.
void MergePrioritySignalsOverrides(
const base::flat_map<std::string, std::optional<double>>& update_data,
std::optional<base::flat_map<std::string, double>>&
priority_signals_overrides) {
if (!priority_signals_overrides) {
priority_signals_overrides.emplace();
}
for (const auto& pair : update_data) {
if (!pair.second.has_value()) {
priority_signals_overrides->erase(pair.first);
continue;
}
priority_signals_overrides->insert_or_assign(pair.first, *pair.second);
}
}
// Same as above, but takes a map with PrioritySignalsDoublePtrs instead of
// std::optional<double>s. This isn't much more code than it takes to convert
// the flat_map of PrioritySignalsDoublePtr to one of optionals, so just
// duplicate the logic.
void MergePrioritySignalsOverrides(
const base::flat_map<std::string,
auction_worklet::mojom::PrioritySignalsDoublePtr>&
update_data,
std::optional<base::flat_map<std::string, double>>&
priority_signals_overrides) {
if (!priority_signals_overrides) {
priority_signals_overrides.emplace();
}
for (const auto& pair : update_data) {
if (!pair.second) {
priority_signals_overrides->erase(pair.first);
continue;
}
priority_signals_overrides->insert_or_assign(pair.first,
pair.second->value);
}
}
// These values are persisted to the database. Do not change.
enum KAnonKeyType {
kAdBid = 0,
kAdNameReporting = 1,
kComponentBid = 2,
kUnknown = 3
};
KAnonKeyType GetKAnonType(std::string_view unhashed_key) {
if (unhashed_key.starts_with(blink::kKAnonKeyForAdBidPrefix)) {
return KAnonKeyType::kAdBid;
} else if (unhashed_key.starts_with(
blink::kKAnonKeyForAdComponentBidPrefix)) {
return KAnonKeyType::kComponentBid;
}
return KAnonKeyType::kAdNameReporting;
}
// Adds indices to the `interest_group` table.
// Call this function after the table has been created,
// both when creating a new database in CreateCurrentSchema
// and after dropping/recreating the `interest_groups` table
// in the *latest* UpgradeVxxSchemaToVxx function to do so.
bool CreateInterestGroupIndices(sql::Database& db) {
// Index on group expiration. Owner and Name are only here to speed up
// queries that don't need the full group.
DCHECK(!db.DoesIndexExist("interest_group_expiration"));
static const char kInterestGroupExpirationIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_expiration"
" ON interest_groups(expiration DESC, owner, name)";
// clang-format on
if (!db.Execute(kInterestGroupExpirationIndexSql)) {
return false;
}
// Index on group expiration by owner.
DCHECK(!db.DoesIndexExist("interest_group_owner"));
static const char kInterestGroupOwnerIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_owner"
" ON interest_groups(owner,expiration DESC,next_update_after ASC,name)";
// clang-format on
if (!db.Execute(kInterestGroupOwnerIndexSql)) {
return false;
}
// Index on group expiration by owner and IG type (regular vs negative)
DCHECK(!db.DoesIndexExist("interest_group_owner_and_type"));
static const char kInterestGroupOwnerAndTypeIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_owner_and_type"
" ON interest_groups("
"LENGTH(additional_bid_key) == 0,owner,expiration DESC,name)";
// clang-format on
if (!db.Execute(kInterestGroupOwnerAndTypeIndexSql)) {
return false;
}
// Index on storage size. Used for ClearExcessiveStorage().
DCHECK(!db.DoesIndexExist("interest_group_owner_size"));
static const char kInterestGroupOwnerSizeIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_owner_size"
" ON interest_groups(owner,expiration DESC,storage_size)";
// clang-format on
if (!db.Execute(kInterestGroupOwnerSizeIndexSql)) {
return false;
}
// Index on group expiration by joining origin. Owner and Name are only here
// to speed up queries that don't need the full group.
DCHECK(!db.DoesIndexExist("interest_group_joining_origin"));
static const char kInterestGroupJoiningOriginIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_joining_origin"
" ON interest_groups(joining_origin, expiration DESC, owner, name)";
// clang-format on
if (!db.Execute(kInterestGroupJoiningOriginIndexSql)) {
return false;
}
return true;
}
// Adds indices to the `kanon` table.
// Call this function after the table has been created,
// both when creating a new database in CreateCurrentSchema
// and after dropping/recreating the `kanon` table
// in the *latest* UpgradeVxxSchemaToVxx function to do so.
bool CreateKAnonIndices(sql::Database& db) {
DCHECK(!db.DoesIndexExist("k_anon_last_server_time_idx"));
static const char kCreateKAnonServerTimeIndexSQL[] =
// clang-format off
"CREATE INDEX k_anon_last_server_time_idx "
"ON joined_k_anon(last_reported_to_anon_server_time DESC);";
// clang-format on
return db.Execute(kCreateKAnonServerTimeIndexSQL);
}
bool MaybeCreateKAnonEntryForV17DatabaseUpgrade(
sql::Database& db,
const blink::InterestGroupKey& interest_group_key,
const std::string& key,
const base::Time& now) {
base::Time distant_past = base::Time::Min();
base::Time last_referenced_time = now;
sql::Statement get_previous_kanon_val(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT is_k_anon,"
"last_k_anon_updated_time,"
"last_reported_to_anon_server_time "
"FROM k_anon WHERE key = ? "
"LIMIT 1"));
// We can get any previously added row for a k_anon key because the same data
// is duplicated for each row of the same key.
if (!get_previous_kanon_val.is_valid()) {
return false;
}
get_previous_kanon_val.BindString(0, key);
bool is_kanon = false;
base::Time last_k_anon_updated_time = distant_past;
base::Time last_reported_to_anon_server_time = distant_past;
if (get_previous_kanon_val.Step()) {
is_kanon = get_previous_kanon_val.ColumnBool(0);
last_k_anon_updated_time = get_previous_kanon_val.ColumnTime(1);
last_reported_to_anon_server_time = get_previous_kanon_val.ColumnTime(2);
}
// clang-format off
const char insert_k_anon_str[] =
"INSERT OR REPLACE INTO k_anon_new("
"last_referenced_time,"
"key,"
"owner,"
"name,"
"is_k_anon,"
"last_k_anon_updated_time,"
"last_reported_to_anon_server_time) "
"VALUES(?,?,?,?,?,?,?)";
// clang-format on
sql::Statement maybe_insert_kanon(
db.GetCachedStatement(SQL_FROM_HERE, insert_k_anon_str));
if (!maybe_insert_kanon.is_valid()) {
return false;
}
maybe_insert_kanon.Reset(true);
maybe_insert_kanon.BindTime(0, last_referenced_time);
maybe_insert_kanon.BindString(1, key);
maybe_insert_kanon.BindString(2, Serialize(interest_group_key.owner));
maybe_insert_kanon.BindString(3, interest_group_key.name);
maybe_insert_kanon.BindBool(4, is_kanon);
maybe_insert_kanon.BindTime(5, last_k_anon_updated_time);
maybe_insert_kanon.BindTime(6, last_reported_to_anon_server_time);
return maybe_insert_kanon.Run();
}
// Initializes the tables, returning true on success.
// The tables cannot exist when calling this function.
bool CreateCurrentSchema(sql::Database& db) {
DCHECK(!db.DoesTableExist("interest_groups"));
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
// Most recent time the interest group was joined. Called
// `exact_join_time` to differentiate it from `join_time` in the join
// history table, which is a day, and isn't looked up on InterestGroup
// load.
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"trusted_bidding_signals_slot_size_mode INTEGER NOT NULL,"
"max_trusted_bidding_signals_url_length INTEGER NOT NULL,"
"trusted_bidding_signals_coordinator TEXT,"
"view_and_click_counts_providers TEXT,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"aggregation_coordinator_origin TEXT,"
"storage_size INTEGER NOT NULL,"
"last_k_anon_updated_time INTEGER NOT NULL,"
"kanon_keys BLOB NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
if (!CreateInterestGroupIndices(db)) {
return false;
}
DCHECK(!db.DoesTableExist("k_anon"));
static const char kCreateInterestGroupKAnonTableSql[] =
"CREATE TABLE joined_k_anon("
"hashed_key BLOB NOT NULL,"
"last_reported_to_anon_server_time INTEGER NOT NULL,"
"PRIMARY KEY(hashed_key))";
// clang-format on
if (!db.Execute(kCreateInterestGroupKAnonTableSql)) {
return false;
}
if (!CreateKAnonIndices(db)) {
return false;
}
DCHECK(!db.DoesTableExist("join_history"));
static const char kJoinHistoryTableSql[] =
// clang-format off
"CREATE TABLE join_history("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"join_time INTEGER NOT NULL,"
"count INTEGER NOT NULL,"
"PRIMARY KEY(owner, name, join_time) "
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kJoinHistoryTableSql)) {
return false;
}
DCHECK(!db.DoesTableExist("bid_history"));
static const char kBidHistoryTableSql[] =
// clang-format off
"CREATE TABLE bid_history("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"bid_time INTEGER NOT NULL,"
"count INTEGER NOT NULL,"
"PRIMARY KEY(owner, name, bid_time) "
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kBidHistoryTableSql)) {
return false;
}
// We can't use the interest group and win time as primary keys since
// auctions on separate pages may occur at the same time.
DCHECK(!db.DoesTableExist("win_history"));
static const char kWinHistoryTableSQL[] =
// clang-format off
"CREATE TABLE win_history("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"win_time INTEGER NOT NULL,"
"ad TEXT NOT NULL,"
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kWinHistoryTableSQL)) {
return false;
}
DCHECK(!db.DoesIndexExist("win_history_index"));
static const char kWinHistoryIndexSQL[] =
// clang-format off
"CREATE INDEX win_history_index "
"ON win_history(owner,name,win_time DESC)";
// clang-format on
if (!db.Execute(kWinHistoryIndexSQL)) {
return false;
}
DCHECK(!db.DoesTableExist("lockout_debugging_only_report"));
static const char kLockoutDebugReportTableSql[] =
// clang-format off
"CREATE TABLE lockout_debugging_only_report("
"id INTEGER NOT NULL,"
"starting_time INTEGER NOT NULL,"
"duration INTEGER NOT NULL,"
"PRIMARY KEY(id))";
// clang-format on
if (!db.Execute(kLockoutDebugReportTableSql)) {
return false;
}
DCHECK(!db.DoesTableExist("cooldown_debugging_only_report"));
// The type field stores an enum mapped to the real TimeDelta cooldown
// duration defined by finch, so that we can easily control duration periods
// through finch without needing to update the database.
static const char kCooldownDebugReportTableSql[] =
// clang-format off
"CREATE TABLE cooldown_debugging_only_report("
"origin TEXT NOT NULL,"
"starting_time INTEGER NOT NULL,"
"type INTEGER NOT NULL,"
"PRIMARY KEY(origin))";
// clang-format on
if (!db.Execute(kCooldownDebugReportTableSql)) {
return false;
}
DCHECK(!db.DoesTableExist("bidding_and_auction_server_keys"));
static const char kBAKeysTableSql[] =
// clang-format off
"CREATE TABLE bidding_and_auction_server_keys("
"coordinator TEXT NOT NULL,"
"keys BLOB NOT NULL,"
"expiration INTEGER NOT NULL,"
"PRIMARY KEY(coordinator))";
// clang-format on
if (!db.Execute(kBAKeysTableSql)) {
return false;
}
DCHECK(!db.DoesTableExist("view_and_click_events"));
static const char kViewAndClickEventsSql[] =
// clang-format off
"CREATE TABLE view_and_click_events("
"provider_origin TEXT NOT NULL,"
"eligible_origin TEXT NOT NULL,"
"uncompacted_view_events BLOB NOT NULL,"
"compacted_view_events BLOB NOT NULL,"
"uncompacted_click_events BLOB NOT NULL,"
"compacted_click_events BLOB NOT NULL,"
"PRIMARY KEY(provider_origin, eligible_origin))";
// clang-format on
if (!db.Execute(kViewAndClickEventsSql)) {
DLOG(ERROR)
<< "view_and_click_events CREATE SQL statement did not compile: "
<< db.GetErrorMessage();
return false;
}
DCHECK(!db.DoesTableExist("cached_k_anonymity_hashes"));
static const char kCachedKAnonymityHashesTableSql[] =
// clang-format off
"CREATE TABLE cached_k_anonymity_hashes("
"key_hash BLOB NOT NULL,"
"is_k_anon INTEGER NOT NULL,"
"fetch_time INTEGER NOT NULL,"
"PRIMARY KEY(key_hash))";
// clang-format on
if (!db.Execute(kCachedKAnonymityHashesTableSql)) {
return false;
}
// Index on fetch_time, needed to delete keys when they expire.
DCHECK(!db.DoesIndexExist("cached_k_anonymity_hashes_expiration_index"));
static const char kCachedKAnonymityHashesExpirationIndexSql[] =
// clang-format off
"CREATE INDEX cached_k_anonymity_hashes_expiration_index"
" ON cached_k_anonymity_hashes(fetch_time)";
// clang-format on
if (!db.Execute(kCachedKAnonymityHashesExpirationIndexSql)) {
return false;
}
return true;
}
bool VacuumDB(sql::Database& db) {
static const char kVacuum[] = "VACUUM";
return db.Execute(kVacuum);
}
bool UpgradeV34SchemaToV35(sql::Database& db, sql::MetaTable& meta_table) {
// Make a new table, `cached_k_anonymity_hashes`.
DCHECK(!db.DoesTableExist("cached_k_anonymity_hashes"));
static const char kCachedKAnonymityHashesTableSql[] =
// clang-format off
"CREATE TABLE cached_k_anonymity_hashes("
"key_hash BLOB NOT NULL,"
"is_k_anon INTEGER NOT NULL,"
"fetch_time INTEGER NOT NULL,"
"PRIMARY KEY(key_hash))";
// clang-format on
if (!db.Execute(kCachedKAnonymityHashesTableSql)) {
DLOG(ERROR) << "cached_k_anonymity_hashes upgrade CREATE SQL "
"statement did not compile: "
<< db.GetErrorMessage();
return false;
}
// Index on fetch_time, needed to delete keys when they expire.
DCHECK(!db.DoesIndexExist("cached_k_anonymity_hashes_expiration_index"));
static const char kCachedKAnonymityHashesExpirationIndexSql[] =
// clang-format off
"CREATE INDEX cached_k_anonymity_hashes_expiration_index"
" ON cached_k_anonymity_hashes(fetch_time)";
// clang-format on
if (!db.Execute(kCachedKAnonymityHashesExpirationIndexSql)) {
return false;
}
return true;
}
bool UpgradeV33SchemaToV34(sql::Database& db, sql::MetaTable& meta_table) {
// Make `view_and_click_events` table.
DCHECK(!db.DoesTableExist("view_and_click_events"));
static const char kViewAndClickEventsSql[] =
// clang-format off
"CREATE TABLE view_and_click_events("
"provider_origin TEXT NOT NULL,"
"eligible_origin TEXT NOT NULL,"
"uncompacted_view_events BLOB NOT NULL,"
"compacted_view_events BLOB NOT NULL,"
"uncompacted_click_events BLOB NOT NULL,"
"compacted_click_events BLOB NOT NULL,"
"PRIMARY KEY(provider_origin, eligible_origin))";
// clang-format on
if (!db.Execute(kViewAndClickEventsSql)) {
DLOG(ERROR) << "view_and_click_counts_providers upgrade CREATE SQL "
"statement did not compile: "
<< db.GetErrorMessage();
return false;
}
return true;
}
bool UpgradeV32SchemaToV33(sql::Database& db, sql::MetaTable& meta_table) {
// Make a table with new column `view_and_click_counts_providers`.
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"trusted_bidding_signals_slot_size_mode INTEGER NOT NULL,"
"max_trusted_bidding_signals_url_length INTEGER NOT NULL,"
"trusted_bidding_signals_coordinator TEXT,"
"view_and_click_counts_providers TEXT,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"aggregation_coordinator_origin TEXT,"
"storage_size INTEGER NOT NULL,"
"last_k_anon_updated_time INTEGER NOT NULL, "
"kanon_keys BLOB NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"trusted_bidding_signals_slot_size_mode,"
"max_trusted_bidding_signals_url_length,"
"trusted_bidding_signals_coordinator,"
"NULL," // view_and_click_counts_providers
"user_bidding_signals,"
"ads_pb,"
"ad_components_pb,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key,"
"aggregation_coordinator_origin,"
"storage_size,"
"last_k_anon_updated_time,"
"kanon_keys "
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV31SchemaToV32(sql::Database& db, sql::MetaTable& meta_table) {
// Adds duration column to the debug report lockout table, and rename its
// last_report_sent_time column to starting_time.
static const char kLockoutTableSql[] =
// clang-format off
"CREATE TABLE new_lockout_debugging_only_report("
"id INTEGER NOT NULL,"
"starting_time INTEGER NOT NULL,"
"duration INTEGER NOT NULL,"
"PRIMARY KEY(id))";
// clang-format on
if (!db.Execute(kLockoutTableSql)) {
return false;
}
// Copy over the existing columns, and set the new duration column's value
// which was always kFledgeDebugReportLockout before.
// clang-format off
sql::Statement copy_lockout_table_sql(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO new_lockout_debugging_only_report "
"SELECT id,"
"last_report_sent_time,"
"? "
"FROM lockout_debugging_only_report"));
// clang-format on
copy_lockout_table_sql.BindTimeDelta(
0, blink::features::kFledgeDebugReportLockout.Get());
if (!copy_lockout_table_sql.Run()) {
return false;
}
static const char kDropLockoutTableSql[] =
"DROP TABLE lockout_debugging_only_report";
if (!db.Execute(kDropLockoutTableSql)) {
return false;
}
static const char kRenameLockoutTableSql[] =
// clang-format off
"ALTER TABLE new_lockout_debugging_only_report "
"RENAME TO lockout_debugging_only_report";
// clang-format on
return db.Execute(kRenameLockoutTableSql);
}
bool UpgradeV29SchemaToV30(sql::Database& db, sql::MetaTable& meta_table) {
// There are no new columns, but the `ads_pb` and `ad_components_pb` columns
// get compressed with Snappy.
// clang-format off
sql::Statement select_prev_groups(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT owner,"
"name,"
"ads_pb,"
"ad_components_pb "
"FROM interest_groups"));
// clang-format on
if (!select_prev_groups.is_valid()) {
return false;
}
// clang-format off
sql::Statement update_group(db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE interest_groups "
"SET ads_pb=?,"
"ad_components_pb=? "
"WHERE owner=? AND name=?"));
// clang-format on
if (!update_group.is_valid()) {
return false;
}
while (select_prev_groups.Step()) {
update_group.Reset(/*clear_bound_vars=*/true);
// Update the `ads_pb` and `ad_components_pb` columns with their contents
// compressed with Snappy.
std::string compressed_ads_pb;
base::span<const uint8_t> ads_pb = select_prev_groups.ColumnBlob(2);
base::TimeTicks start_ads = base::TimeTicks::Now();
snappy::Compress(reinterpret_cast<const char*>(ads_pb.data()),
ads_pb.size(), &compressed_ads_pb);
UMA_HISTOGRAM_TIMES("Storage.InterestGroup.AdProtoCompressionTime",
base::TimeTicks::Now() - start_ads);
update_group.BindBlob(0, base::as_byte_span(compressed_ads_pb));
if (ads_pb.size() > 0u) {
base::UmaHistogramPercentage(
"Storage.InterestGroup.AdProtoCompressionRatio",
compressed_ads_pb.size() * 100 / ads_pb.size());
}
UMA_HISTOGRAM_COUNTS_1M("Storage.InterestGroup.AdProtoSizeUncompressed",
ads_pb.size());
UMA_HISTOGRAM_COUNTS_1M("Storage.InterestGroup.AdProtoSizeCompressed",
compressed_ads_pb.size());
std::string compressed_ad_components_pb;
base::span<const uint8_t> ad_components_pb =
select_prev_groups.ColumnBlob(3);
base::TimeTicks start_ad_components = base::TimeTicks::Now();
snappy::Compress(reinterpret_cast<const char*>(ad_components_pb.data()),
ad_components_pb.size(), &compressed_ad_components_pb);
UMA_HISTOGRAM_TIMES("Storage.InterestGroup.AdProtoCompressionTime",
base::TimeTicks::Now() - start_ad_components);
update_group.BindBlob(1, base::as_byte_span(compressed_ad_components_pb));
if (ad_components_pb.size() > 0u) {
base::UmaHistogramPercentage(
"Storage.InterestGroup.AdProtoCompressionRatio",
compressed_ad_components_pb.size() * 100 / ad_components_pb.size());
}
UMA_HISTOGRAM_COUNTS_1M("Storage.InterestGroup.AdProtoSizeUncompressed",
ad_components_pb.size());
UMA_HISTOGRAM_COUNTS_1M("Storage.InterestGroup.AdProtoSizeCompressed",
compressed_ad_components_pb.size());
update_group.BindString(2, select_prev_groups.ColumnStringView(0));
update_group.BindString(3, select_prev_groups.ColumnStringView(1));
if (!update_group.Run()) {
return false;
}
}
if (!select_prev_groups.Succeeded()) {
return false;
}
return true;
}
bool UpgradeV27SchemaToV28(sql::Database& db, sql::MetaTable& meta_table) {
// Make a table with new columns `trusted_bidding_signals_protocol_version`
// and `trusted_bidding_signals_coordinator.`
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"trusted_bidding_signals_slot_size_mode INTEGER NOT NULL,"
"max_trusted_bidding_signals_url_length INTEGER NOT NULL,"
"trusted_bidding_signals_coordinator TEXT,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"aggregation_coordinator_origin TEXT,"
"storage_size INTEGER NOT NULL,"
"last_k_anon_updated_time INTEGER NOT NULL, "
"kanon_keys BLOB NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"trusted_bidding_signals_slot_size_mode,"
"max_trusted_bidding_signals_url_length,"
"NULL," // trusted_bidding_signals_coordinator
"user_bidding_signals,"
"ads_pb,"
"ad_components_pb,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key,"
"aggregation_coordinator_origin,"
"storage_size,"
"last_k_anon_updated_time,"
"kanon_keys "
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV26SchemaToV27(sql::Database& db, sql::MetaTable& meta_table) {
// Make a table with new columns `last_k_anon_updated_time` and `kanon_keys`.
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"trusted_bidding_signals_slot_size_mode INTEGER NOT NULL,"
"max_trusted_bidding_signals_url_length INTEGER NOT NULL,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"aggregation_coordinator_origin TEXT,"
"storage_size INTEGER NOT NULL,"
"last_k_anon_updated_time INTEGER NOT NULL, "
"kanon_keys BLOB NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
// Copy over the values from the old `interest_groups` before
// populating the new columns.
sql::Statement copy_interest_groups(db.GetCachedStatement(
SQL_FROM_HERE,
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"trusted_bidding_signals_slot_size_mode,"
"max_trusted_bidding_signals_url_length,"
"user_bidding_signals,"
"ads_pb,"
"ad_components_pb,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key,"
"aggregation_coordinator_origin,"
"storage_size,"
"?," // last_k_anon_updated_time
"? " // kanon_keys
"FROM interest_groups"
// clang-format on
));
copy_interest_groups.BindTime(0, base::Time::Min());
std::string kanon_key_protos_str;
if (KAnonKeyProtos().SerializeToString(&kanon_key_protos_str)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
copy_interest_groups.BindBlob(1, kanon_key_protos_str);
if (!copy_interest_groups.Run()) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
if (!CreateInterestGroupIndices(db)) {
return false;
}
static const char kMoveKAnonTimes[] =
// clang-format off
"UPDATE interest_groups "
"SET last_k_anon_updated_time = k.last_k_anon_updated_time "
"FROM "
"(SELECT owner, name, MIN(last_k_anon_updated_time) "
"AS last_k_anon_updated_time FROM k_anon GROUP BY owner, name) "
"AS k "
"WHERE interest_groups.owner = k.owner AND interest_groups.name = k.name";
// clang-format on
if (!db.Execute(kMoveKAnonTimes)) {
return false;
}
// Copy over positive keys to the interest groups table.
sql::Statement get_positive_keys(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT owner, name, hashed_key "
"FROM k_anon WHERE is_k_anon > 0"));
std::map<std::pair<std::string, std::string>, KAnonKeyProtos> positive_keys;
while (get_positive_keys.Step()) {
std::string owner = get_positive_keys.ColumnString(0);
std::string name = get_positive_keys.ColumnString(1);
std::string hashed_key = get_positive_keys.ColumnString(2);
positive_keys[std::make_pair(std::move(owner), std::move(name))].add_keys(
std::move(hashed_key));
}
sql::Statement move_positive_kanon_keys(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE interest_groups "
"SET kanon_keys = ? "
"WHERE owner = ? and name = ?"));
for (auto [owner_name_pair, keys] : positive_keys) {
move_positive_kanon_keys.Reset(true);
std::string keys_str;
if (keys.SerializeToString(&keys_str)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
move_positive_kanon_keys.BindBlob(0, keys_str);
move_positive_kanon_keys.BindString(1, owner_name_pair.first);
move_positive_kanon_keys.BindString(2, owner_name_pair.second);
if (!move_positive_kanon_keys.Run()) {
return false;
}
}
// Make a new `joined_k_anon` table and copy over the last reported
// times.
static const char kCreateInterestGroupKAnonTableSql[] =
"CREATE TABLE joined_k_anon("
"hashed_key BLOB NOT NULL,"
"last_reported_to_anon_server_time INTEGER NOT NULL,"
"PRIMARY KEY(hashed_key))";
// clang-format on
if (!db.Execute(kCreateInterestGroupKAnonTableSql)) {
return false;
}
// The old schema expected all rows with the same hashed_key to have the
// same last_reported_to_anon_server_time so we can use any
// last_reported_to_anon_server_time from any row.
static const char kMoveKanonLastReportedTimes[] =
// clang-format off
"INSERT INTO joined_k_anon "
"SELECT hashed_key, MIN(last_reported_to_anon_server_time) "
"FROM k_anon "
"GROUP BY hashed_key";
// clang-format on
if (!db.Execute(kMoveKanonLastReportedTimes)) {
return false;
}
// Drop the old k-anon table and make new indices.
static const char kDropKAnonTableSql[] = "DROP TABLE k_anon";
if (!db.Execute(kDropKAnonTableSql)) {
return false;
}
return CreateKAnonIndices(db);
}
bool UpgradeV24SchemaToV25(sql::Database& db, sql::MetaTable& meta_table) {
static const char kCreateKAnonTableSql[] =
"CREATE TABLE k_anon_new("
"last_referenced_time INTEGER NOT NULL,"
"hashed_key BLOB NOT NULL,"
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"is_k_anon INTEGER NOT NULL,"
"key_type INTEGER NOT NULL,"
"last_k_anon_updated_time INTEGER NOT NULL,"
"last_reported_to_anon_server_time INTEGER NOT NULL,"
"PRIMARY KEY(owner,name,hashed_key))";
if (!db.Execute(kCreateKAnonTableSql)) {
return false;
}
sql::Statement select_previous_k_anon_values(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT last_referenced_time, owner, name, "
"is_k_anon, last_k_anon_updated_time, "
"last_reported_to_anon_server_time, key FROM k_anon"));
if (!select_previous_k_anon_values.is_valid()) {
return false;
}
// clang-format off
sql::Statement insert_entry(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO k_anon_new("
"last_referenced_time,"
"owner,"
"name,"
"is_k_anon,"
"last_k_anon_updated_time,"
"last_reported_to_anon_server_time,"
"hashed_key,"
"key_type) "
"VALUES(?,?,?,?,?,?,?,?)"));
// clang-format on
if (!insert_entry.is_valid()) {
return false;
}
while (select_previous_k_anon_values.Step()) {
insert_entry.Reset(true);
// Copy over the existing columns.
insert_entry.BindTime(0, select_previous_k_anon_values.ColumnTime(0));
insert_entry.BindString(1,
select_previous_k_anon_values.ColumnStringView(1));
insert_entry.BindString(2,
select_previous_k_anon_values.ColumnStringView(2));
insert_entry.BindBool(3, select_previous_k_anon_values.ColumnBool(3));
insert_entry.BindTime(4, select_previous_k_anon_values.ColumnTime(4));
insert_entry.BindTime(5, select_previous_k_anon_values.ColumnTime(5));
// Create the new columns.
std::string_view unhashed_key =
select_previous_k_anon_values.ColumnStringView(6);
insert_entry.BindBlob(6, crypto::SHA256HashString(unhashed_key));
insert_entry.BindInt(7, GetKAnonType(unhashed_key));
if (!insert_entry.Run()) {
return false;
}
}
if (!select_previous_k_anon_values.Succeeded()) {
return false;
}
static const char kDropKAnonTableSql[] = "DROP TABLE k_anon";
if (!db.Execute(kDropKAnonTableSql)) {
return false;
}
static const char kRenameKAnonTableSql[] =
// clang-format off
"ALTER TABLE k_anon_new "
"RENAME TO k_anon";
// clang-format on
return db.Execute(kRenameKAnonTableSql);
}
bool UpgradeV23SchemaToV24(sql::Database& db, sql::MetaTable& meta_table) {
static const char kBAKeysTableSql[] =
// clang-format off
"CREATE TABLE bidding_and_auction_server_keys("
"coordinator TEXT NOT NULL,"
"keys BLOB NOT NULL,"
"expiration INTEGER NOT NULL,"
"PRIMARY KEY(coordinator))";
// clang-format on
return db.Execute(kBAKeysTableSql);
}
bool UpgradeV22SchemaToV23(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"trusted_bidding_signals_slot_size_mode INTEGER NOT NULL,"
"max_trusted_bidding_signals_url_length INTEGER NOT NULL,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"aggregation_coordinator_origin TEXT,"
"storage_size INTEGER NOT NULL, "
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"trusted_bidding_signals_slot_size_mode,"
"0," // max_trusted_bidding_signals_url_length
"user_bidding_signals,"
"ads_pb,"
"ad_components_pb,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key,"
"aggregation_coordinator_origin,"
"storage_size "
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV21SchemaToV22(sql::Database& db, sql::MetaTable& meta_table) {
// The two changed tables had no data, so no need to copy data over.
static const char kDropLockoutTableTableSql[] =
"DROP TABLE lockout_debugging_only_report";
if (!db.Execute(kDropLockoutTableTableSql)) {
return false;
}
static const char kLockoutDebugReportTableSql[] =
// clang-format off
"CREATE TABLE lockout_debugging_only_report("
"id INTEGER NOT NULL,"
"last_report_sent_time INTEGER NOT NULL,"
"PRIMARY KEY(id))";
// clang-format on
if (!db.Execute(kLockoutDebugReportTableSql)) {
return false;
}
static const char kDropCooldownTableSql[] =
"DROP TABLE cooldown_debugging_only_report";
if (!db.Execute(kDropCooldownTableSql)) {
return false;
}
static const char kCooldownDebugReportTableSql[] =
// clang-format off
"CREATE TABLE cooldown_debugging_only_report("
"origin TEXT NOT NULL,"
"starting_time INTEGER NOT NULL,"
"type INTEGER NOT NULL,"
"PRIMARY KEY(origin))";
// clang-format on
if (!db.Execute(kCooldownDebugReportTableSql)) {
return false;
}
return true;
}
bool UpgradeV20SchemaToV21(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"trusted_bidding_signals_slot_size_mode INTEGER NOT NULL,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"aggregation_coordinator_origin TEXT,"
"storage_size INTEGER NOT NULL, "
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"0," // trusted_bidding_signals_slot_size_mode
"user_bidding_signals,"
"ads_pb,"
"ad_components_pb,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key,"
"aggregation_coordinator_origin,"
"storage_size "
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV19SchemaToV20(sql::Database& db, sql::MetaTable& meta_table) {
// The difference from V19 is V20 adds the following two new tables, used for
// down sampling forDebuggingOnly reports.
static const char kLockoutDebuggingOnlyReportTableSql[] =
// clang-format off
"CREATE TABLE lockout_debugging_only_report("
"date_of_last_report_sent INTEGER NOT NULL)";
// clang-format on
if (!db.Execute(kLockoutDebuggingOnlyReportTableSql)) {
return false;
}
static const char kCooldownDebuggingOnlyReportTableSql[] =
// clang-format off
"CREATE TABLE cooldown_debugging_only_report("
"origin TEXT NOT NULL,"
"starting_date INTEGER NOT NULL,"
"duration INTEGER NOT NULL,"
"PRIMARY KEY(origin))";
// clang-format on
if (!db.Execute(kCooldownDebuggingOnlyReportTableSql)) {
return false;
}
return true;
}
bool UpgradeV18SchemaToV19(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"aggregation_coordinator_origin TEXT,"
"storage_size INTEGER NOT NULL, "
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads_pb,"
"ad_components_pb,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key,"
"NULL," // aggregation_coordinator_origin
"(LENGTH(interest_groups.owner)+"
"LENGTH(interest_groups.joining_origin)+"
"LENGTH(interest_groups.name)+"
"LENGTH(interest_groups.priority_vector)+"
"LENGTH(interest_groups.priority_signals_overrides)+"
"LENGTH(interest_groups.seller_capabilities)+"
"LENGTH(interest_groups.joining_url)+"
"LENGTH(interest_groups.bidding_url)+"
"LENGTH(interest_groups.bidding_wasm_helper_url)+"
"LENGTH(interest_groups.update_url)+"
"LENGTH(interest_groups.trusted_bidding_signals_url)+"
"LENGTH(interest_groups.trusted_bidding_signals_keys)+"
"IFNULL(LENGTH(interest_groups.user_bidding_signals),0)+"
"LENGTH(interest_groups.ads_pb)+"
"LENGTH(interest_groups.ad_components_pb)+"
"LENGTH(interest_groups.ad_sizes)+"
"LENGTH(interest_groups.size_groups)+"
"LENGTH(interest_groups.additional_bid_key)+"
"40) " // storage_size -- start with the old estimated size.
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return CreateInterestGroupIndices(db);
}
bool UpgradeV17SchemaToV18(sql::Database& db, sql::MetaTable& meta_table) {
// The only difference was the addition of the `interest_group_owner_and_type`
// index, which would be removed and recreated in UpgradeV18SchemaToV19.
return true;
}
bool UpgradeV16SchemaToV17(sql::Database& db,
sql::MetaTable& meta_table,
const PassKey& passkey) {
static const char kCreateKAnonTableSql[] =
"CREATE TABLE k_anon_new("
"last_referenced_time INTEGER NOT NULL,"
"key TEXT NOT NULL,"
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"is_k_anon INTEGER NOT NULL,"
"last_k_anon_updated_time INTEGER NOT NULL,"
"last_reported_to_anon_server_time INTEGER NOT NULL,"
"PRIMARY KEY(owner,name,key))";
if (!db.Execute(kCreateKAnonTableSql)) {
return false;
}
// Copy over all existing k-anon values into the new table.
static const char kInsertPreviousKANonValues[] =
// clang-format off
"INSERT INTO k_anon_new("
"last_referenced_time,"
"key,"
"owner,"
"name,"
"is_k_anon,"
"last_k_anon_updated_time,"
"last_reported_to_anon_server_time) "
"SELECT last_referenced_time, key,'','',"
"is_k_anon, last_k_anon_updated_time,"
"last_reported_to_anon_server_time "
"FROM k_anon";
// clang-format on
if (!db.Execute(kInsertPreviousKANonValues)) {
return false;
}
// Make sure all k-anon keys for ads in the interest_groups table are
// represented in the k_anon table.
sql::Statement select_igs_with_ads_and_bidding_url(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT owner, name, ads_pb, ad_components_pb, "
"bidding_url FROM interest_groups"));
base::Time now = base::Time::Now();
while (select_igs_with_ads_and_bidding_url.Step()) {
blink::InterestGroup ig;
ig.owner = DeserializeOrigin(
select_igs_with_ads_and_bidding_url.ColumnStringView(0));
ig.name = select_igs_with_ads_and_bidding_url.ColumnString(1);
ig.ads = DeserializeInterestGroupAdVectorProto(
passkey, select_igs_with_ads_and_bidding_url.ColumnStringView(2));
ig.ad_components = DeserializeInterestGroupAdVectorProto(
passkey, select_igs_with_ads_and_bidding_url.ColumnStringView(3));
ig.bidding_url =
DeserializeURL(select_igs_with_ads_and_bidding_url.ColumnStringView(4));
// Insert k-anon keys for the interest group's ads.
blink::InterestGroupKey interest_group_key(ig.owner, ig.name);
if (ig.ads.has_value() && ig.bidding_url.has_value()) {
for (auto& ad : *ig.ads) {
if (!MaybeCreateKAnonEntryForV17DatabaseUpgrade(
db, interest_group_key,
blink::DEPRECATED_KAnonKeyForAdNameReporting(
ig, ad,
/*selected_buyer_and_seller_reporting_id=*/std::nullopt),
now)) {
return false;
}
if (!MaybeCreateKAnonEntryForV17DatabaseUpgrade(
db, interest_group_key,
blink::DEPRECATED_KAnonKeyForAdBid(ig, ad.render_url()), now)) {
return false;
}
}
}
if (ig.ad_components.has_value()) {
for (auto& ad : *ig.ad_components) {
if (!MaybeCreateKAnonEntryForV17DatabaseUpgrade(
db, interest_group_key,
blink::DEPRECATED_KAnonKeyForAdComponentBid(ad.render_url()),
now)) {
return false;
}
}
}
}
static const char kDropKAnonTableSql[] = "DROP TABLE k_anon";
if (!db.Execute(kDropKAnonTableSql)) {
return false;
}
static const char kRenameKAnonTableSql[] =
// clang-format off
"ALTER TABLE k_anon_new "
"RENAME TO k_anon";
// clang-format on
return db.Execute(kRenameKAnonTableSql);
}
bool UpgradeV15SchemaToV16(sql::Database& db,
sql::MetaTable& meta_table,
const PassKey& passkey) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads_pb BLOB NOT NULL,"
"ad_components_pb BLOB NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
// clang-format off
sql::Statement kCopyInterestGroupTableWithEmptyAdPBsSql(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"?," // ads_pb
"?," // ad_components_pb
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key "
"FROM interest_groups"));
// clang-format on
std::string empty_ad_proto_value;
if (!AdProtos().SerializeToString(&empty_ad_proto_value)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.AdProtos",
InterestGroupStorageProtoSerializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.AdProtos",
InterestGroupStorageProtoSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
kCopyInterestGroupTableWithEmptyAdPBsSql.BindBlob(0, empty_ad_proto_value);
kCopyInterestGroupTableWithEmptyAdPBsSql.BindBlob(1, empty_ad_proto_value);
if (!kCopyInterestGroupTableWithEmptyAdPBsSql.Run()) {
return false;
}
sql::Statement kSelectIGsWithAds(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT owner, name, ads, ad_components from interest_groups"));
while (kSelectIGsWithAds.Step()) {
std::string owner = kSelectIGsWithAds.ColumnString(0);
std::string name = kSelectIGsWithAds.ColumnString(1);
std::optional<std::vector<blink::InterestGroup::Ad>> ads =
DeserializeInterestGroupAdVectorJson(
passkey, kSelectIGsWithAds.ColumnStringView(2),
/*for_components=*/false);
std::optional<std::vector<blink::InterestGroup::Ad>> ad_components =
DeserializeInterestGroupAdVectorJson(
passkey, kSelectIGsWithAds.ColumnStringView(3),
/*for_components=*/true);
std::string serialized_ads = SerializeUncompressed(ads);
std::string serialized_ad_components = SerializeUncompressed(ad_components);
sql::Statement insert_value_into_IG(db.GetCachedStatement(
SQL_FROM_HERE,
"UPDATE new_interest_groups SET ads_pb = ?, ad_components_pb = ? "
"WHERE owner = ? AND name = ?"));
insert_value_into_IG.BindBlob(0, serialized_ads);
insert_value_into_IG.BindBlob(1, serialized_ad_components);
insert_value_into_IG.BindString(2, owner);
insert_value_into_IG.BindString(3, name);
if (!insert_value_into_IG.Run()) {
return false;
}
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return true;
}
bool UpgradeV14SchemaToV15(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"additional_bid_key BLOB NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags, "
"X'' " // additional_bid_key
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return true;
}
bool UpgradeV13SchemaToV14(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"auction_server_request_flags INTEGER NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components,"
"ad_sizes,"
"size_groups,"
"0 " // auction_server_request_flags
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return true;
}
bool UpgradeV12SchemaToV13(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"ad_sizes TEXT NOT NULL,"
"size_groups TEXT NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components,"
"''," // ad_sizes
"''" // size_groups
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return true;
}
bool UpgradeV11SchemaToV12(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"seller_capabilities TEXT NOT NULL,"
"all_sellers_capabilities INTEGER NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"''," // seller_capabilities
"0," // all_sellers_capabilities
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components "
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return true;
}
bool UpgradeV10SchemaToV11(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupTableSql[] =
// clang-format off
"CREATE TABLE new_interest_groups("
"expiration INTEGER NOT NULL,"
"last_updated INTEGER NOT NULL,"
"next_update_after INTEGER NOT NULL,"
"owner TEXT NOT NULL,"
"joining_origin TEXT NOT NULL,"
"exact_join_time INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"priority DOUBLE NOT NULL,"
"enable_bidding_signals_prioritization INTEGER NOT NULL,"
"priority_vector TEXT NOT NULL,"
"priority_signals_overrides TEXT NOT NULL,"
"execution_mode INTEGER NOT NULL,"
"joining_url TEXT NOT NULL,"
"bidding_url TEXT NOT NULL,"
"bidding_wasm_helper_url TEXT NOT NULL,"
"update_url TEXT NOT NULL,"
"trusted_bidding_signals_url TEXT NOT NULL,"
"trusted_bidding_signals_keys TEXT NOT NULL,"
"user_bidding_signals TEXT,"
"ads TEXT NOT NULL,"
"ad_components TEXT NOT NULL,"
"PRIMARY KEY(owner,name))";
// clang-format on
if (!db.Execute(kInterestGroupTableSql)) {
return false;
}
static const char kCopyInterestGroupTableSql[] =
// clang-format off
"INSERT INTO new_interest_groups "
"SELECT expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"last_updated," // exact_join_time
"name,"
"priority,"
"0," // enable_bidding_signals_prioritization
"''," // priority_vector
"''," // priority_signals_overrides
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"user_bidding_signals,"
"ads,"
"ad_components "
"FROM interest_groups";
// clang-format on
if (!db.Execute(kCopyInterestGroupTableSql)) {
return false;
}
static const char kDropInterestGroupTableSql[] = "DROP TABLE interest_groups";
if (!db.Execute(kDropInterestGroupTableSql)) {
return false;
}
static const char kRenameInterestGroupTableSql[] =
// clang-format off
"ALTER TABLE new_interest_groups "
"RENAME TO interest_groups";
// clang-format on
if (!db.Execute(kRenameInterestGroupTableSql)) {
return false;
}
return true;
}
bool UpgradeV9SchemaToV10(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupKAnonTableSql[] =
// clang-format off
"CREATE TABLE k_anon("
"last_referenced_time INTEGER NOT NULL,"
"key TEXT NOT NULL,"
"is_k_anon INTEGER NOT NULL,"
"last_k_anon_updated_time INTEGER NOT NULL,"
"last_reported_to_anon_server_time INTEGER NOT NULL,"
"PRIMARY KEY(key))";
// clang-format on
if (!db.Execute(kInterestGroupKAnonTableSql)) {
return false;
}
static const char kInterestGroupKAnonLastRefIndexSql[] =
// clang-format off
"CREATE INDEX k_anon_last_referenced_time"
" ON k_anon(last_referenced_time DESC)";
// clang-format on
if (!db.Execute(kInterestGroupKAnonLastRefIndexSql)) {
return false;
}
static const char kCopyKAnonTableSql[] =
// clang-format off
"INSERT INTO k_anon "
"SELECT last_referenced_time,"
"key,"
"k_anon_count > 50 AS is_k_anon,"
"last_k_anon_updated_time,"
"last_reported_to_anon_server_time "
"FROM kanon";
// clang-format on
if (!db.Execute(kCopyKAnonTableSql)) {
return false;
}
static const char kDropKanonTableSql[] = "DROP TABLE kanon";
if (!db.Execute(kDropKanonTableSql)) {
return false;
}
return true;
}
bool UpgradeV8SchemaToV9(sql::Database& db, sql::MetaTable& meta_table) {
static const char kJoinHistoryTableSql[] =
// clang-format off
"CREATE TABLE join_history2("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"join_time INTEGER NOT NULL,"
"count INTEGER NOT NULL,"
"PRIMARY KEY(owner, name, join_time) "
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kJoinHistoryTableSql)) {
return false;
}
// Consolidate old join records into a single record that
// counts the number joins in a day (86400000000 microseconds)
static const char kCopyJoinHistoryTableSql[] =
// clang-format off
"INSERT INTO join_history2 "
"SELECT owner,"
"name,"
"(join_time-(join_time%86400000000)) as join_time2,"
"COUNT() as count "
"FROM join_history "
"GROUP BY owner,name,join_time2";
// clang-format on
if (!db.Execute(kCopyJoinHistoryTableSql)) {
return false;
}
static const char kDropJoinHistoryTableSql[] = "DROP TABLE join_history";
if (!db.Execute(kDropJoinHistoryTableSql)) {
return false;
}
static const char kRenameJoinHistoryTableSql[] =
// clang-format off
"ALTER TABLE join_history2 "
"RENAME TO join_history";
// clang-format on
if (!db.Execute(kRenameJoinHistoryTableSql)) {
return false;
}
static const char kBidHistoryTableSql[] =
// clang-format off
"CREATE TABLE bid_history2("
"owner TEXT NOT NULL,"
"name TEXT NOT NULL,"
"bid_time INTEGER NOT NULL,"
"count INTEGER NOT NULL,"
"PRIMARY KEY(owner, name, bid_time) "
"FOREIGN KEY(owner,name) REFERENCES interest_groups)";
// clang-format on
if (!db.Execute(kBidHistoryTableSql)) {
return false;
}
// Consolidate old bid records into a single record that
// counts the number bids in a day (86400000000 microseconds)
static const char kCopyBidHistoryTableSql[] =
// clang-format off
"INSERT INTO bid_history2 "
"SELECT owner,"
"name,"
"(bid_time-(bid_time%86400000000)) as bid_time2,"
"COUNT() as count "
"FROM bid_history "
"GROUP BY owner,name,bid_time2";
// clang-format on
if (!db.Execute(kCopyBidHistoryTableSql)) {
return false;
}
static const char kDropBidHistoryTableSql[] = "DROP TABLE bid_history";
if (!db.Execute(kDropBidHistoryTableSql)) {
return false;
}
static const char kRenameBidHistoryTableSql[] =
// clang-format off
"ALTER TABLE bid_history2 "
"RENAME TO bid_history";
// clang-format on
if (!db.Execute(kRenameBidHistoryTableSql)) {
return false;
}
return true;
}
bool UpgradeV7SchemaToV8(sql::Database& db, sql::MetaTable& meta_table) {
static const char kInterestGroupsAddExecutionModeSql[] =
"ALTER TABLE interest_groups ADD COLUMN execution_mode INTEGER DEFAULT 0";
if (!db.Execute(kInterestGroupsAddExecutionModeSql)) {
return false;
}
return true;
}
bool UpgradeV6SchemaToV7(sql::Database& db, sql::MetaTable& meta_table) {
// Index on group expiration by owner.
DCHECK(db.DoesIndexExist("interest_group_owner"));
static const char kRemoveInterestGroupOwnerIndexSql[] =
// clang-format off
"DROP INDEX interest_group_owner";
// clang-format on
if (!db.Execute(kRemoveInterestGroupOwnerIndexSql)) {
return false;
}
DCHECK(!db.DoesIndexExist("interest_group_owner"));
static const char kInterestGroupOwnerIndexSql[] =
// clang-format off
"CREATE INDEX interest_group_owner"
" ON interest_groups(owner,expiration DESC,next_update_after ASC,name)";
// clang-format on
if (!db.Execute(kInterestGroupOwnerIndexSql)) {
return false;
}
// Update interest_group table.
static const char kInterestGroupsAddPrioritySql[] =
"ALTER TABLE interest_groups ADD COLUMN priority DOUBLE DEFAULT 0";
if (!db.Execute(kInterestGroupsAddPrioritySql)) {
return false;
}
return true;
}
bool UpgradeDB(sql::Database& db,
const int db_version,
sql::MetaTable& meta_table,
const PassKey& pass_key) {
// Whether to vacuum the database after the upgrade. The vacuum must happen
// after the transaction is committed.
bool vacuum_db_post_upgrade = false;
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
switch (db_version) {
case 6:
if (!UpgradeV6SchemaToV7(db, meta_table)) {
return false;
}
[[fallthrough]];
case 7:
if (!UpgradeV7SchemaToV8(db, meta_table)) {
return false;
}
[[fallthrough]];
case 8:
if (!UpgradeV8SchemaToV9(db, meta_table)) {
return false;
}
[[fallthrough]];
case 9:
if (!UpgradeV9SchemaToV10(db, meta_table)) {
return false;
}
[[fallthrough]];
case 10:
if (!UpgradeV10SchemaToV11(db, meta_table)) {
return false;
}
[[fallthrough]];
case 11:
if (!UpgradeV11SchemaToV12(db, meta_table)) {
return false;
}
[[fallthrough]];
case 12:
if (!UpgradeV12SchemaToV13(db, meta_table)) {
return false;
}
[[fallthrough]];
case 13:
if (!UpgradeV13SchemaToV14(db, meta_table)) {
return false;
}
[[fallthrough]];
case 14:
if (!UpgradeV14SchemaToV15(db, meta_table)) {
return false;
}
[[fallthrough]];
case 15:
if (!UpgradeV15SchemaToV16(db, meta_table, pass_key)) {
return false;
}
[[fallthrough]];
case 16:
if (!UpgradeV16SchemaToV17(db, meta_table, pass_key)) {
return false;
}
[[fallthrough]];
case 17:
if (!UpgradeV17SchemaToV18(db, meta_table)) {
return false;
}
[[fallthrough]];
case 18:
if (!UpgradeV18SchemaToV19(db, meta_table)) {
return false;
}
[[fallthrough]];
case 19:
if (!UpgradeV19SchemaToV20(db, meta_table)) {
return false;
}
[[fallthrough]];
case 20:
if (!UpgradeV20SchemaToV21(db, meta_table)) {
return false;
}
[[fallthrough]];
case 21:
if (!UpgradeV21SchemaToV22(db, meta_table)) {
return false;
}
[[fallthrough]];
case 22:
if (!UpgradeV22SchemaToV23(db, meta_table)) {
return false;
}
[[fallthrough]];
case 23:
if (!UpgradeV23SchemaToV24(db, meta_table)) {
return false;
}
[[fallthrough]];
case 24:
if (!UpgradeV24SchemaToV25(db, meta_table)) {
return false;
}
[[fallthrough]];
case 25:
vacuum_db_post_upgrade = true;
[[fallthrough]];
case 26:
vacuum_db_post_upgrade = true;
if (!UpgradeV26SchemaToV27(db, meta_table)) {
return false;
}
[[fallthrough]];
case 27:
if (!UpgradeV27SchemaToV28(db, meta_table)) {
return false;
}
[[fallthrough]];
case 28:
// v29 adds a new field in the IG.ads structure, and so doesn't require
// any changes to the InterestGroup table. Existing data is
// forwards-compatible because `FromInterestGroupAdValue` correctly
// handles the lack of a value for
// `selectable_buyer_and_seller_reporting_ids`.
[[fallthrough]];
case 29:
vacuum_db_post_upgrade = true;
if (!UpgradeV29SchemaToV30(db, meta_table)) {
return false;
}
[[fallthrough]];
case 30:
// Conversion is a no-op, just bookkeeping for a proto change.
[[fallthrough]];
case 31:
if (!UpgradeV31SchemaToV32(db, meta_table)) {
return false;
}
[[fallthrough]];
case 32:
if (!UpgradeV32SchemaToV33(db, meta_table)) {
return false;
}
[[fallthrough]];
case 33:
if (!UpgradeV33SchemaToV34(db, meta_table)) {
return false;
}
[[fallthrough]];
case 34:
if (!UpgradeV34SchemaToV35(db, meta_table)) {
return false;
}
if (!meta_table.SetVersionNumber(kCurrentVersionNumber)) {
return false;
}
}
bool committed = transaction.Commit();
if (!committed) {
return false;
}
if (vacuum_db_post_upgrade) {
const bool vacuum_result = VacuumDB(db);
if (vacuum_result) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.VacuumResult",
InterestGroupStorageVacuumResult::kSucceeded);
} else {
DLOG(ERROR) << "Failed to vacuum: " << db.GetErrorMessage();
base::UmaHistogramEnumeration("Storage.InterestGroup.VacuumResult",
InterestGroupStorageVacuumResult::kFailed);
}
}
return true;
}
bool RemoveJoinHistory(sql::Database& db,
const blink::InterestGroupKey& group_key) {
sql::Statement remove_join_history(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM join_history "
"WHERE owner=? AND name=?"));
if (!remove_join_history.is_valid()) {
return false;
}
remove_join_history.Reset(true);
remove_join_history.BindString(0, Serialize(group_key.owner));
remove_join_history.BindString(1, group_key.name);
return remove_join_history.Run();
}
bool RemoveBidHistory(sql::Database& db,
const blink::InterestGroupKey& group_key) {
sql::Statement remove_bid_history(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM bid_history "
"WHERE owner=? AND name=?"));
if (!remove_bid_history.is_valid()) {
return false;
}
remove_bid_history.Reset(true);
remove_bid_history.BindString(0, Serialize(group_key.owner));
remove_bid_history.BindString(1, group_key.name);
return remove_bid_history.Run();
}
bool RemoveWinHistory(sql::Database& db,
const blink::InterestGroupKey& group_key) {
sql::Statement remove_win_history(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM win_history "
"WHERE owner=? AND name=?"));
if (!remove_win_history.is_valid()) {
return false;
}
remove_win_history.Reset(true);
remove_win_history.BindString(0, Serialize(group_key.owner));
remove_win_history.BindString(1, group_key.name);
return remove_win_history.Run();
}
bool DoRemoveInterestGroup(sql::Database& db,
const blink::InterestGroupKey& group_key) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
// These tables have foreign keys that reference the interest group table.
if (!RemoveJoinHistory(db, group_key)) {
return false;
}
if (!RemoveBidHistory(db, group_key)) {
return false;
}
if (!RemoveWinHistory(db, group_key)) {
return false;
}
sql::Statement remove_group(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM interest_groups "
"WHERE owner=? AND name=?"));
if (!remove_group.is_valid()) {
return false;
}
remove_group.Reset(true);
remove_group.BindString(0, Serialize(group_key.owner));
remove_group.BindString(1, group_key.name);
return remove_group.Run() && transaction.Commit();
}
bool DoClearClusteredBiddingGroups(sql::Database& db,
const url::Origin& owner,
const url::Origin& main_frame) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
// clang-format off
sql::Statement same_cluster_groups(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name "
"FROM interest_groups "
"WHERE owner = ? AND joining_origin = ? AND execution_mode = ?"));
// clang-format on
if (!same_cluster_groups.is_valid()) {
return false;
}
same_cluster_groups.Reset(true);
same_cluster_groups.BindString(0, Serialize(owner));
same_cluster_groups.BindString(1, Serialize(main_frame));
same_cluster_groups.BindInt(
2, static_cast<int>(
blink::InterestGroup::ExecutionMode::kGroupedByOriginMode));
while (same_cluster_groups.Step()) {
if (!DoRemoveInterestGroup(
db, blink::InterestGroupKey(owner,
same_cluster_groups.ColumnString(0)))) {
return false;
}
}
return transaction.Commit();
}
// Leaves all the interest groups joined on `joining_origin` except
// `interest_groups_to_keep`. Returns std::nullopt on error, and a (possibly
// empty) list of left interest groups on success.
std::optional<std::vector<std::string>> DoClearOriginJoinedInterestGroups(
sql::Database& db,
const url::Origin& owner,
const std::set<std::string>& interest_groups_to_keep,
const url::Origin& joining_origin) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return std::nullopt;
}
// Have to select interest groups and then use DoRemoveInterestGroup() in
// order to remove data in other tables about any removed groups. Can't just
// do a single delete.
// clang-format off
sql::Statement same_cluster_groups(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name "
"FROM interest_groups "
"WHERE owner = ? AND joining_origin = ?"));
// clang-format on
if (!same_cluster_groups.is_valid()) {
return std::nullopt;
}
same_cluster_groups.Reset(true);
same_cluster_groups.BindString(0, Serialize(owner));
same_cluster_groups.BindString(1, Serialize(joining_origin));
std::vector<std::string> cleared_interest_groups;
while (same_cluster_groups.Step()) {
std::string name = same_cluster_groups.ColumnString(0);
if (interest_groups_to_keep.contains(name)) {
continue;
}
if (!DoRemoveInterestGroup(db, blink::InterestGroupKey(owner, name))) {
return std::nullopt;
}
cleared_interest_groups.emplace_back(std::move(name));
}
if (!transaction.Commit()) {
return std::nullopt;
}
return cleared_interest_groups;
}
#define COMMON_INTEREST_GROUPS_QUERY_FIELDS \
"expiration," \
"joining_origin," \
"exact_join_time," \
"last_updated," \
"next_update_after," \
"priority," \
"enable_bidding_signals_prioritization," \
"priority_vector," \
"priority_signals_overrides," \
"seller_capabilities," \
"all_sellers_capabilities," \
"execution_mode," \
"bidding_url," \
"bidding_wasm_helper_url," \
"update_url," \
"trusted_bidding_signals_url," \
"trusted_bidding_signals_keys," \
"trusted_bidding_signals_slot_size_mode," \
"max_trusted_bidding_signals_url_length," \
"trusted_bidding_signals_coordinator," \
"view_and_click_counts_providers," \
"user_bidding_signals," /* opaque data */ \
"ads_pb," \
"ad_components_pb," \
"ad_sizes," \
"size_groups," \
"auction_server_request_flags," \
"additional_bid_key," \
"aggregation_coordinator_origin," \
"last_k_anon_updated_time," \
"kanon_keys"
// Populate `group` with the current `load` outcome. Prerequisite:
// `load` is an interest group query with the initial fields being
// `COMMON_INTEREST_GROUPS_QUERY_FIELDS`, and there is a row of data returned.
void PopulateInterestGroupFromQueryResult(sql::Statement& load,
const PassKey& passkey,
StorageInterestGroup& group) {
group.interest_group.expiry = load.ColumnTime(0);
group.joining_origin = DeserializeOrigin(load.ColumnStringView(1));
group.join_time = load.ColumnTime(2);
group.last_updated = load.ColumnTime(3);
group.next_update_after = load.ColumnTime(4);
group.interest_group.priority = load.ColumnDouble(5);
group.interest_group.enable_bidding_signals_prioritization =
load.ColumnBool(6);
group.interest_group.priority_vector =
DeserializeStringDoubleMap(load.ColumnStringView(7));
group.interest_group.priority_signals_overrides =
DeserializeStringDoubleMap(load.ColumnStringView(8));
group.interest_group.seller_capabilities =
DeserializeSellerCapabilitiesMap(load.ColumnStringView(9));
group.interest_group.all_sellers_capabilities =
DeserializeSellerCapabilities(load.ColumnInt64(10));
group.interest_group.execution_mode =
static_cast<blink::InterestGroup::ExecutionMode>(load.ColumnInt(11));
group.interest_group.bidding_url = DeserializeURL(load.ColumnStringView(12));
group.interest_group.bidding_wasm_helper_url =
DeserializeURL(load.ColumnStringView(13));
group.interest_group.update_url = DeserializeURL(load.ColumnStringView(14));
group.interest_group.trusted_bidding_signals_url =
DeserializeURL(load.ColumnStringView(15));
group.interest_group.trusted_bidding_signals_keys =
DeserializeStringVector(load.ColumnStringView(16));
group.interest_group.trusted_bidding_signals_slot_size_mode =
static_cast<blink::InterestGroup::TrustedBiddingSignalsSlotSizeMode>(
load.ColumnInt(17));
group.interest_group.max_trusted_bidding_signals_url_length =
load.ColumnInt(18);
if (load.GetColumnType(19) != sql::ColumnType::kNull) {
group.interest_group.trusted_bidding_signals_coordinator =
DeserializeOrigin(load.ColumnStringView(19));
}
if (load.GetColumnType(20) != sql::ColumnType::kNull) {
group.interest_group.view_and_click_counts_providers =
DeserializeOriginVector(load.ColumnStringView(20));
}
if (load.GetColumnType(21) != sql::ColumnType::kNull) {
group.interest_group.user_bidding_signals = load.ColumnString(21);
}
group.interest_group.ads = DecompressAndDeserializeInterestGroupAdVectorProto(
passkey, load.ColumnStringView(22));
group.interest_group.ad_components =
DecompressAndDeserializeInterestGroupAdVectorProto(
passkey, load.ColumnStringView(23));
group.interest_group.ad_sizes =
DeserializeStringSizeMap(load.ColumnStringView(24));
group.interest_group.size_groups =
DeserializeStringStringVectorMap(load.ColumnStringView(25));
group.interest_group.auction_server_request_flags =
DeserializeAuctionServerRequestFlags(load.ColumnInt64(26));
group.interest_group.additional_bid_key =
DeserializeAdditionalBidKey(load.ColumnBlob(27));
if (load.GetColumnType(28) != sql::ColumnType::kNull) {
group.interest_group.aggregation_coordinator_origin =
DeserializeOrigin(load.ColumnStringView(28));
}
group.last_k_anon_updated = load.ColumnTime(29);
KAnonKeyProtos keys_proto;
if (keys_proto.ParseFromString(load.ColumnStringView(30))) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.KAnonKeyProtos",
InterestGroupStorageProtoDeserializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.KAnonKeyProtos",
InterestGroupStorageProtoDeserializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
group.hashed_kanon_keys =
std::vector(keys_proto.keys().begin(), keys_proto.keys().end());
}
bool DoLoadInterestGroup(sql::Database& db,
const PassKey& passkey,
const blink::InterestGroupKey& group_key,
StorageInterestGroup& group) {
// clang-format off
sql::Statement load(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT " COMMON_INTEREST_GROUPS_QUERY_FIELDS " "
"FROM interest_groups "
"WHERE owner = ? AND name = ? "));
// clang-format on
if (!load.is_valid()) {
return false;
}
load.Reset(true);
load.BindString(0, Serialize(group_key.owner));
load.BindString(1, group_key.name);
if (!load.Step() || !load.Succeeded()) {
return false;
}
group.interest_group.owner = group_key.owner;
group.interest_group.name = group_key.name;
PopulateInterestGroupFromQueryResult(load, passkey, group);
return true;
}
bool DoRecordInterestGroupJoin(sql::Database& db,
const url::Origin& owner,
const std::string& name,
base::Time join_time) {
// This flow basically emulates SQLite's UPSERT feature which is disabled in
// Chrome. Although there are two statements executed, we don't need to
// enclose them in a transaction since only one will actually modify the
// database.
// NOTE: Join and bid history can expire up to a few hours before the interest
// group, since join and bid history are floored to UTC days, but the
// interest group doesn't necessarily expire at UTC midnight.
//
// The reason for this is to reduce the number of join / bid rows stored.
//
// For joins, this can result in a join count of 0.
int64_t join_day = join_time.ToDeltaSinceWindowsEpoch()
.FloorToMultiple(base::Days(1))
.InMicroseconds();
// clang-format off
sql::Statement insert_join_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT OR IGNORE INTO join_history(owner,name,join_time,count) "
"VALUES(?,?,?,1)"));
// clang-format on
if (!insert_join_hist.is_valid()) {
return false;
}
insert_join_hist.Reset(true);
insert_join_hist.BindString(0, Serialize(owner));
insert_join_hist.BindString(1, name);
insert_join_hist.BindInt64(2, join_day);
if (!insert_join_hist.Run()) {
return false;
}
// If the insert changed the database return early.
if (db.GetLastChangeCount() > 0) {
return true;
}
// clang-format off
sql::Statement update_join_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE join_history "
"SET count=count+1 "
"WHERE owner=? AND name=? AND join_time=?"));
// clang-format on
if (!update_join_hist.is_valid()) {
return false;
}
update_join_hist.Reset(true);
update_join_hist.BindString(0, Serialize(owner));
update_join_hist.BindString(1, name);
update_join_hist.BindInt64(2, join_day);
return update_join_hist.Run();
}
std::optional<InterestGroupKanonUpdateParameter> DoJoinInterestGroup(
sql::Database& db,
const PassKey& passkey,
const blink::InterestGroup& data,
const GURL& joining_url,
base::Time exact_join_time,
base::Time last_updated,
base::Time next_update_after) {
DCHECK(data.IsValid() && data.IsValidForJoinAndUpdate());
url::Origin joining_origin = url::Origin::Create(joining_url);
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return std::nullopt;
}
StorageInterestGroup old_group;
base::Time last_k_anon_updated = base::Time::Min();
base::flat_set<std::string> positive_kanon_keys;
base::flat_set<std::string> all_old_kanon_keys;
blink::InterestGroupKey interest_group_key(data.owner, data.name);
if (DoLoadInterestGroup(db, passkey, interest_group_key, old_group)) {
if (old_group.interest_group.expiry <= base::Time::Now()) {
// If there's a matching old interest group that's expired but that
// hasn't yet been cleaned up, delete it. This removes its associated
// tables, which should expire at the same time as the old interest
// group.
if (!DoRemoveInterestGroup(db, interest_group_key)) {
return std::nullopt;
}
} else if (old_group.interest_group.execution_mode ==
blink::InterestGroup::ExecutionMode::kGroupedByOriginMode &&
joining_origin != old_group.joining_origin) {
// Clear all interest groups with same owner and mode
// GroupedByOriginMode and same `old_group.joining_origin`.
if (!DoClearClusteredBiddingGroups(db, data.owner,
old_group.joining_origin)) {
return std::nullopt;
}
} else {
last_k_anon_updated = old_group.last_k_anon_updated;
positive_kanon_keys = std::move(old_group.hashed_kanon_keys);
all_old_kanon_keys = old_group.interest_group.GetAllKAnonKeys();
}
}
InterestGroupKanonUpdateParameter kanon_update(last_k_anon_updated);
base::flat_set<std::string> all_new_kanon_keys = data.GetAllKAnonKeys();
std::set_difference(all_new_kanon_keys.begin(), all_new_kanon_keys.end(),
all_old_kanon_keys.begin(), all_old_kanon_keys.end(),
std::back_inserter(kanon_update.newly_added_hashed_keys));
positive_kanon_keys.erase(
std::remove_if(positive_kanon_keys.begin(), positive_kanon_keys.end(),
[&](const std::string& key) -> bool {
return !all_new_kanon_keys.contains(key);
}),
positive_kanon_keys.end());
kanon_update.hashed_keys.insert(kanon_update.hashed_keys.end(),
all_new_kanon_keys.begin(),
all_new_kanon_keys.end());
// clang-format off
sql::Statement join_group(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT OR REPLACE INTO interest_groups("
"expiration,"
"last_updated,"
"next_update_after,"
"owner,"
"joining_origin,"
"exact_join_time,"
"name,"
"priority,"
"enable_bidding_signals_prioritization,"
"priority_vector,"
"priority_signals_overrides,"
"seller_capabilities,"
"all_sellers_capabilities,"
"execution_mode,"
"joining_url,"
"bidding_url,"
"bidding_wasm_helper_url,"
"update_url,"
"trusted_bidding_signals_url,"
"trusted_bidding_signals_keys,"
"trusted_bidding_signals_slot_size_mode,"
"max_trusted_bidding_signals_url_length,"
"trusted_bidding_signals_coordinator,"
"view_and_click_counts_providers,"
"user_bidding_signals," // opaque data
"ads_pb,"
"ad_components_pb,"
"ad_sizes,"
"size_groups,"
"auction_server_request_flags,"
"additional_bid_key,"
"aggregation_coordinator_origin,"
"storage_size,"
"last_k_anon_updated_time,"
"kanon_keys) "
"VALUES("
"?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
));
// clang-format on
if (!join_group.is_valid()) {
return std::nullopt;
}
join_group.Reset(true);
join_group.BindTime(0, data.expiry);
join_group.BindTime(1, last_updated);
join_group.BindTime(2, next_update_after);
join_group.BindString(3, Serialize(data.owner));
join_group.BindString(4, Serialize(joining_origin));
join_group.BindTime(5, exact_join_time);
join_group.BindString(6, data.name);
join_group.BindDouble(7, data.priority);
join_group.BindBool(8, data.enable_bidding_signals_prioritization);
join_group.BindString(9, Serialize(data.priority_vector));
join_group.BindString(10, Serialize(data.priority_signals_overrides));
join_group.BindString(11, Serialize(data.seller_capabilities));
join_group.BindInt64(12, Serialize(data.all_sellers_capabilities));
join_group.BindInt(13, static_cast<int>(data.execution_mode));
join_group.BindString(14, Serialize(joining_url));
join_group.BindString(15, Serialize(data.bidding_url));
join_group.BindString(16, Serialize(data.bidding_wasm_helper_url));
join_group.BindString(17, Serialize(data.update_url));
join_group.BindString(18, Serialize(data.trusted_bidding_signals_url));
join_group.BindString(19, Serialize(data.trusted_bidding_signals_keys));
join_group.BindInt(
20, static_cast<int>(data.trusted_bidding_signals_slot_size_mode));
join_group.BindInt(21, data.max_trusted_bidding_signals_url_length);
if (data.trusted_bidding_signals_coordinator) {
join_group.BindString(22,
Serialize(*data.trusted_bidding_signals_coordinator));
} else {
join_group.BindNull(22);
}
if (data.view_and_click_counts_providers) {
join_group.BindString(23, Serialize(*data.view_and_click_counts_providers));
} else {
join_group.BindNull(23);
}
if (data.user_bidding_signals) {
join_group.BindString(24, data.user_bidding_signals.value());
} else {
join_group.BindNull(24);
}
join_group.BindBlob(25, Serialize(data.ads));
join_group.BindBlob(26, Serialize(data.ad_components));
join_group.BindString(27, Serialize(data.ad_sizes));
join_group.BindString(28, Serialize(data.size_groups));
join_group.BindInt64(29, Serialize(data.auction_server_request_flags));
join_group.BindBlob(30, Serialize(data.additional_bid_key));
if (data.aggregation_coordinator_origin) {
join_group.BindString(31, Serialize(*data.aggregation_coordinator_origin));
} else {
join_group.BindNull(31);
}
join_group.BindInt64(32, data.EstimateSize());
join_group.BindTime(33, last_k_anon_updated);
KAnonKeyProtos key_proto;
*key_proto.mutable_keys() = {positive_kanon_keys.begin(),
positive_kanon_keys.end()};
std::string key_proto_str;
if (key_proto.SerializeToString(&key_proto_str)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
join_group.BindBlob(34, key_proto_str);
if (!join_group.Run()) {
return std::nullopt;
}
if (!DoRecordInterestGroupJoin(db, data.owner, data.name, last_updated)) {
return std::nullopt;
}
if (!transaction.Commit()) {
return std::nullopt;
}
if (data.ads) {
base::UmaHistogramCounts1000(
"Storage.InterestGroup.PerInterestGroup.NumAds", data.ads->size());
for (blink::InterestGroup::Ad ad : *data.ads) {
base::UmaHistogramCounts10000("Storage.InterestGroup.AdRenderURLSize",
ad.render_url().size());
}
}
if (data.ad_components) {
base::UmaHistogramCounts1000(
"Storage.InterestGroup.PerInterestGroup.NumAdComponents",
data.ad_components->size());
for (blink::InterestGroup::Ad ad_component : *data.ad_components) {
base::UmaHistogramCounts10000(
"Storage.InterestGroup.AdComponentRenderURLSize",
ad_component.render_url().size());
}
}
return std::move(kanon_update);
}
bool DoStoreInterestGroupUpdate(
sql::Database& db,
const blink::InterestGroup& group,
base::Time now,
base::flat_set<std::string>& positive_kanon_keys) {
// clang-format off
sql::Statement store_group(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE interest_groups "
"SET last_updated=?,"
"next_update_after=?,"
"priority=?,"
"enable_bidding_signals_prioritization=?,"
"priority_vector=?,"
"priority_signals_overrides=?,"
"seller_capabilities=?,"
"all_sellers_capabilities=?,"
"execution_mode=?,"
"bidding_url=?,"
"bidding_wasm_helper_url=?,"
"update_url=?,"
"trusted_bidding_signals_url=?,"
"trusted_bidding_signals_keys=?,"
"trusted_bidding_signals_slot_size_mode=?,"
"max_trusted_bidding_signals_url_length=?,"
"trusted_bidding_signals_coordinator=?,"
"view_and_click_counts_providers=?,"
"user_bidding_signals=?,"
"ads_pb=?,"
"ad_components_pb=?,"
"ad_sizes=?,"
"size_groups=?,"
"auction_server_request_flags=?,"
"additional_bid_key=?,"
"aggregation_coordinator_origin=?,"
"storage_size=?,"
"kanon_keys=? "
"WHERE owner=? AND name=?"));
// clang-format on
if (!store_group.is_valid()) {
return false;
}
store_group.Reset(true);
store_group.BindTime(0, now);
store_group.BindTime(
1, now + InterestGroupStorage::kUpdateSucceededBackoffPeriod);
store_group.BindDouble(2, group.priority);
store_group.BindBool(3, group.enable_bidding_signals_prioritization);
store_group.BindString(4, Serialize(group.priority_vector));
store_group.BindString(5, Serialize(group.priority_signals_overrides));
store_group.BindString(6, Serialize(group.seller_capabilities));
store_group.BindInt64(7, Serialize(group.all_sellers_capabilities));
store_group.BindInt(8, static_cast<int>(group.execution_mode));
store_group.BindString(9, Serialize(group.bidding_url));
store_group.BindString(10, Serialize(group.bidding_wasm_helper_url));
store_group.BindString(11, Serialize(group.update_url));
store_group.BindString(12, Serialize(group.trusted_bidding_signals_url));
store_group.BindString(13, Serialize(group.trusted_bidding_signals_keys));
store_group.BindInt(
14, static_cast<int>(group.trusted_bidding_signals_slot_size_mode));
store_group.BindInt(15, group.max_trusted_bidding_signals_url_length);
if (group.trusted_bidding_signals_coordinator) {
store_group.BindString(
16, Serialize(*group.trusted_bidding_signals_coordinator));
} else {
store_group.BindNull(16);
}
if (group.view_and_click_counts_providers) {
store_group.BindString(17,
Serialize(*group.view_and_click_counts_providers));
} else {
store_group.BindNull(17);
}
if (group.user_bidding_signals) {
store_group.BindString(18, group.user_bidding_signals.value());
} else {
store_group.BindNull(18);
}
store_group.BindBlob(19, Serialize(group.ads));
store_group.BindBlob(20, Serialize(group.ad_components));
store_group.BindString(21, Serialize(group.ad_sizes));
store_group.BindString(22, Serialize(group.size_groups));
store_group.BindInt64(23, Serialize(group.auction_server_request_flags));
store_group.BindBlob(24, Serialize(group.additional_bid_key));
if (group.aggregation_coordinator_origin) {
store_group.BindString(25,
Serialize(*group.aggregation_coordinator_origin));
} else {
store_group.BindNull(25);
}
store_group.BindInt64(26, group.EstimateSize());
KAnonKeyProtos key_proto;
*key_proto.mutable_keys() = {positive_kanon_keys.begin(),
positive_kanon_keys.end()};
std::string key_proto_str;
if (key_proto.SerializeToString(&key_proto_str)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
store_group.BindBlob(27, key_proto_str);
store_group.BindString(28, Serialize(group.owner));
store_group.BindString(29, group.name);
return store_group.Run();
}
std::optional<InterestGroupKanonUpdateParameter> DoUpdateInterestGroup(
sql::Database& db,
const PassKey& passkey,
const blink::InterestGroupKey& group_key,
InterestGroupUpdate update,
base::Time now) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return std::nullopt;
}
// Unlike Join() operations, for Update() operations, values that aren't
// specified in the JSON returned by servers (Serialize()'d below as empty
// strings) aren't modified in the database -- in this sense, new data is
// merged with old data.
//
// Since we need to verify this results in a valid interest group, we have
// to first read the interest group from the DB, apply the changes and then
// verify the interest group is valid before writing it to the database.
StorageInterestGroup storage_interest_group;
if (!DoLoadInterestGroup(db, passkey, group_key, storage_interest_group)) {
return std::nullopt;
}
blink::InterestGroup& updated_group = storage_interest_group.interest_group;
base::flat_set<std::string> pre_existing_k_anon_keys =
updated_group.GetAllKAnonKeys();
base::flat_set<std::string> positive_kanon_keys =
std::move(storage_interest_group.hashed_kanon_keys);
bool updated_kanon_keys = false;
// (Optimization) Don't do anything for expired interest groups.
if (updated_group.expiry <= now) {
return std::nullopt;
}
if (update.priority) {
updated_group.priority = *update.priority;
}
if (update.enable_bidding_signals_prioritization) {
updated_group.enable_bidding_signals_prioritization =
*update.enable_bidding_signals_prioritization;
}
if (update.priority_vector) {
updated_group.priority_vector = update.priority_vector;
}
if (update.priority_signals_overrides) {
MergePrioritySignalsOverrides(*update.priority_signals_overrides,
updated_group.priority_signals_overrides);
}
if (update.seller_capabilities) {
updated_group.seller_capabilities = update.seller_capabilities;
}
if (update.all_sellers_capabilities) {
updated_group.all_sellers_capabilities = *update.all_sellers_capabilities;
}
if (update.execution_mode) {
updated_group.execution_mode = *update.execution_mode;
}
if (update.daily_update_url) {
updated_group.update_url = std::move(update.daily_update_url);
}
if (update.bidding_url) {
// The bidding URL is part of k-anon keys.
updated_kanon_keys = true;
updated_group.bidding_url = std::move(update.bidding_url);
}
if (update.bidding_wasm_helper_url) {
updated_group.bidding_wasm_helper_url =
std::move(update.bidding_wasm_helper_url);
}
if (update.trusted_bidding_signals_url) {
updated_group.trusted_bidding_signals_url =
std::move(update.trusted_bidding_signals_url);
}
if (update.trusted_bidding_signals_keys) {
updated_group.trusted_bidding_signals_keys =
std::move(update.trusted_bidding_signals_keys);
}
if (update.trusted_bidding_signals_slot_size_mode) {
updated_group.trusted_bidding_signals_slot_size_mode =
*update.trusted_bidding_signals_slot_size_mode;
}
if (update.max_trusted_bidding_signals_url_length) {
updated_group.max_trusted_bidding_signals_url_length =
*update.max_trusted_bidding_signals_url_length;
}
if (update.trusted_bidding_signals_coordinator.has_value()) {
updated_group.trusted_bidding_signals_coordinator =
std::move(update.trusted_bidding_signals_coordinator.value());
}
if (update.view_and_click_counts_providers.has_value()) {
updated_group.view_and_click_counts_providers =
std::move(update.view_and_click_counts_providers.value());
}
if (update.user_bidding_signals) {
updated_group.user_bidding_signals = std::move(update.user_bidding_signals);
}
if (update.ads) {
updated_kanon_keys = true;
updated_group.ads = std::move(update.ads);
}
if (update.ad_components) {
updated_kanon_keys = true;
updated_group.ad_components = std::move(update.ad_components);
}
if (update.ad_sizes) {
updated_group.ad_sizes = std::move(update.ad_sizes);
}
if (update.size_groups) {
updated_group.size_groups = std::move(update.size_groups);
}
if (update.auction_server_request_flags) {
updated_group.auction_server_request_flags =
*update.auction_server_request_flags;
}
if (update.aggregation_coordinator_origin) {
updated_group.aggregation_coordinator_origin =
std::move(update.aggregation_coordinator_origin);
}
if (!updated_group.IsValid() || !updated_group.IsValidForJoinAndUpdate()) {
// TODO(behamilton): Report errors to devtools.
return std::nullopt;
}
InterestGroupKanonUpdateParameter kanon_update(
storage_interest_group.last_k_anon_updated);
if (updated_kanon_keys) {
base::flat_set<std::string> new_keys = updated_group.GetAllKAnonKeys();
positive_kanon_keys.erase(
std::remove_if(positive_kanon_keys.begin(), positive_kanon_keys.end(),
[&](const std::string& key) -> bool {
return !new_keys.contains(key);
}),
positive_kanon_keys.end());
kanon_update.hashed_keys.insert(kanon_update.hashed_keys.end(),
new_keys.begin(), new_keys.end());
std::set_difference(
kanon_update.hashed_keys.begin(), kanon_update.hashed_keys.end(),
pre_existing_k_anon_keys.begin(), pre_existing_k_anon_keys.end(),
std::back_inserter(kanon_update.newly_added_hashed_keys));
} else {
kanon_update.hashed_keys.insert(kanon_update.hashed_keys.end(),
pre_existing_k_anon_keys.begin(),
pre_existing_k_anon_keys.end());
}
if (!DoStoreInterestGroupUpdate(db, updated_group, now,
positive_kanon_keys) ||
!transaction.Commit()) {
return std::nullopt;
}
return std::move(kanon_update);
}
bool DoAllowUpdateIfOlderThan(sql::Database& db,
const PassKey& passkey,
const blink::InterestGroupKey& group_key,
base::TimeDelta update_if_older_than,
base::Time now) {
sql::Statement allow_update_if_older_than(
db.GetCachedStatement(SQL_FROM_HERE, R"(
UPDATE interest_groups SET
next_update_after=?
WHERE owner=? AND name=? AND ? - last_updated >= ?)"));
if (!allow_update_if_older_than.is_valid()) {
return false;
}
allow_update_if_older_than.Reset(true);
allow_update_if_older_than.BindTime(0, now);
allow_update_if_older_than.BindString(1, Serialize(group_key.owner));
allow_update_if_older_than.BindString(2, group_key.name);
allow_update_if_older_than.BindTime(3, now);
allow_update_if_older_than.BindTimeDelta(4, update_if_older_than);
return allow_update_if_older_than.Run();
}
bool DoReportUpdateFailed(sql::Database& db,
const blink::InterestGroupKey& group_key,
bool parse_failure,
base::Time now) {
sql::Statement update_group(db.GetCachedStatement(SQL_FROM_HERE, R"(
UPDATE interest_groups SET
next_update_after=?
WHERE owner=? AND name=?)"));
if (!update_group.is_valid()) {
return false;
}
update_group.Reset(true);
if (parse_failure) {
// Non-network failures delay the same amount of time as successful
// updates.
update_group.BindTime(
0, now + InterestGroupStorage::kUpdateSucceededBackoffPeriod);
} else {
update_group.BindTime(
0, now + InterestGroupStorage::kUpdateFailedBackoffPeriod);
}
update_group.BindString(1, Serialize(group_key.owner));
update_group.BindString(2, group_key.name);
return update_group.Run();
}
bool DoRecordInterestGroupBid(sql::Database& db,
const blink::InterestGroupKey& group_key,
base::Time bid_time) {
// This flow basically emulates SQLite's UPSERT feature which is disabled in
// Chrome. Although there are two statements executed, we don't need to
// enclose them in a transaction since only one will actually modify the
// database.
// NOTE: Join and bid history can expire up to a few hours before the interest
// group, since join and bid history are floored to UTC days, but the
// interest group doesn't necessarily expire at UTC midnight.
//
// The reason for this is to reduce the number of join / bid rows stored.
int64_t bid_day = bid_time.ToDeltaSinceWindowsEpoch()
.FloorToMultiple(base::Days(1))
.InMicroseconds();
// clang-format off
sql::Statement insert_bid_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT OR IGNORE INTO bid_history(owner,name,bid_time,count) "
"VALUES(?,?,?,1)"));
// clang-format on
if (!insert_bid_hist.is_valid()) {
return false;
}
insert_bid_hist.Reset(true);
insert_bid_hist.BindString(0, Serialize(group_key.owner));
insert_bid_hist.BindString(1, group_key.name);
insert_bid_hist.BindInt64(2, bid_day);
if (!insert_bid_hist.Run()) {
return false;
}
// If the insert changed the database return early.
if (db.GetLastChangeCount() > 0) {
return true;
}
// clang-format off
sql::Statement update_bid_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE bid_history "
"SET count=count+1 "
"WHERE owner=? AND name=? AND bid_time=?"));
// clang-format on
if (!update_bid_hist.is_valid()) {
return false;
}
update_bid_hist.Reset(true);
update_bid_hist.BindString(0, Serialize(group_key.owner));
update_bid_hist.BindString(1, group_key.name);
update_bid_hist.BindInt64(2, bid_day);
return update_bid_hist.Run();
}
bool DoRecordInterestGroupBids(sql::Database& db,
const blink::InterestGroupSet& group_keys,
base::Time bid_time) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
for (const auto& group_key : group_keys) {
if (!DoRecordInterestGroupBid(db, group_key, bid_time)) {
return false;
}
}
return transaction.Commit();
}
bool DoRecordInterestGroupWin(sql::Database& db,
const blink::InterestGroupKey& group_key,
const std::string& ad_json,
base::Time win_time) {
// Record the win. It should be unique since auctions should be serialized.
// If it is not unique we should just keep the first one.
// clang-format off
sql::Statement win_hist(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO win_history(owner,name,win_time,ad) "
"VALUES(?,?,?,?)"));
// clang-format on
if (!win_hist.is_valid()) {
return false;
}
win_hist.Reset(true);
win_hist.BindString(0, Serialize(group_key.owner));
win_hist.BindString(1, group_key.name);
win_hist.BindTime(2, win_time);
win_hist.BindString(3, ad_json);
return win_hist.Run();
}
bool DoRecordDebugReportLockout(sql::Database& db,
base::Time starting_time,
base::TimeDelta duration) {
sql::Statement debug_lockout(db.GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR REPLACE "
"INTO lockout_debugging_only_report(id, starting_time, duration) "
"VALUES(1, ?, ?)"));
if (!debug_lockout.is_valid()) {
return false;
}
debug_lockout.Reset(true);
// Ceil to nearest hour to be stored in DB.
debug_lockout.BindInt64(0, starting_time.ToDeltaSinceWindowsEpoch()
.CeilToMultiple(base::Hours(1))
.InMicroseconds());
debug_lockout.BindTimeDelta(1, duration);
return debug_lockout.Run();
}
std::optional<base::Time> DoGetMostDistantInterestGroupExpiration(
sql::Database& db,
base::Time now) {
base::Time result;
sql::Statement get_expiration(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT expiration "
"FROM interest_groups "
"WHERE expiration>? "
"ORDER BY expiration DESC "
"LIMIT 1"));
if (!get_expiration.is_valid()) {
DLOG(ERROR) << "GetMostDistantInterestGroupExpiration SQL statement did "
"not compile: "
<< db.GetErrorMessage();
return std::nullopt;
}
get_expiration.Reset(true);
get_expiration.BindTime(0, now);
if (!get_expiration.Step()) {
return std::nullopt;
}
return result = get_expiration.ColumnTime(0);
}
bool DoSetDebugReportLockoutUntilIGExpires(sql::Database& db, base::Time now) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
std::optional<base::Time> maybe_expiration =
DoGetMostDistantInterestGroupExpiration(db, now);
// If all interest groups joined before now already expired, then no need to
// lockout.
if (!maybe_expiration.has_value()) {
sql::Statement clear_lockout(db.GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM lockout_debugging_only_report"));
return clear_lockout.Run() && transaction.Commit();
}
sql::Statement debug_lockout(db.GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR REPLACE "
"INTO lockout_debugging_only_report(id, starting_time, duration) "
"VALUES(1, ?, ?)"));
if (!debug_lockout.is_valid()) {
return false;
}
int64_t starting_time_nearest_next_hour = now.ToDeltaSinceWindowsEpoch()
.CeilToMultiple(base::Hours(1))
.InMicroseconds();
int64_t duration =
maybe_expiration->ToDeltaSinceWindowsEpoch().InMicroseconds() -
starting_time_nearest_next_hour;
if (duration < 0) {
duration = 0;
}
debug_lockout.Reset(true);
// Ceil to nearest hour to be stored in DB.
debug_lockout.BindInt64(0, starting_time_nearest_next_hour);
debug_lockout.BindInt64(1, duration);
return debug_lockout.Run() && transaction.Commit();
}
bool DoRecordDebugReportCooldown(sql::Database& db,
const url::Origin& origin,
base::Time cooldown_start,
DebugReportCooldownType cooldown_type) {
sql::Statement debug_cooldown(db.GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR REPLACE "
"INTO cooldown_debugging_only_report(origin, starting_time, type) "
"VALUES(?, ?, ?)"));
if (!debug_cooldown.is_valid()) {
return false;
}
debug_cooldown.Reset(true);
debug_cooldown.BindString(0, Serialize(origin));
// Ceil to nearest hour to be stored in DB.
debug_cooldown.BindInt64(1, cooldown_start.ToDeltaSinceWindowsEpoch()
.CeilToMultiple(base::Hours(1))
.InMicroseconds());
debug_cooldown.BindInt(2, static_cast<int>(cooldown_type));
return debug_cooldown.Run();
}
bool DoUpdateKAnonymity(sql::Database& db,
const blink::InterestGroupKey& interest_group_key,
const std::vector<std::string>& positive_hashed_keys,
const base::Time update_time,
bool replace_existing_values,
base::Time now) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
KAnonKeyProtos keys_to_insert;
base::Time update_time_to_insert = update_time;
std::set<std::string> existing_keys;
if (!replace_existing_values) {
sql::Statement get_existing_keys(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT last_k_anon_updated_time, kanon_keys FROM interest_groups "
"WHERE owner = ? AND name = ? "));
get_existing_keys.BindString(0, Serialize(interest_group_key.owner));
get_existing_keys.BindString(1, interest_group_key.name);
if (!get_existing_keys.Step()) {
return false;
}
update_time_to_insert = get_existing_keys.ColumnTime(0);
if (update_time_to_insert > update_time) {
// DO NOT perform an update if the existing keys are newer.
return false;
}
if (keys_to_insert.ParseFromString(get_existing_keys.ColumnStringView(1))) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.KAnonKeyProtos",
InterestGroupStorageProtoDeserializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.KAnonKeyProtos",
InterestGroupStorageProtoDeserializationResult::kFailed);
return false;
}
existing_keys.insert(keys_to_insert.keys().begin(),
keys_to_insert.keys().end());
}
for (const std::string& new_key : positive_hashed_keys) {
if (!existing_keys.contains(new_key)) {
keys_to_insert.add_keys(new_key);
}
}
sql::Statement set_values(db.GetCachedStatement(
SQL_FROM_HERE,
"UPDATE interest_groups "
"SET last_k_anon_updated_time=?, kanon_keys=? "
"WHERE owner=? AND name=? AND last_k_anon_updated_time<=?"));
set_values.Reset(true);
set_values.BindTime(0, update_time_to_insert);
std::string keys_to_insert_str;
if (keys_to_insert.SerializeToString(&keys_to_insert_str)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kSucceeded);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.KAnonKeyProtos",
InterestGroupStorageProtoSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
}
set_values.BindBlob(1, keys_to_insert_str);
set_values.BindString(2, Serialize(interest_group_key.owner));
set_values.BindString(3, interest_group_key.name);
set_values.BindTime(4, update_time);
if (!set_values.Run()) {
return false;
}
return transaction.Commit();
}
std::optional<base::Time> DoGetLastKAnonymityReported(
sql::Database& db,
const std::string& hashed_key) {
const base::Time distant_past = base::Time::Min();
sql::Statement get_reported(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT last_reported_to_anon_server_time FROM "
"joined_k_anon WHERE hashed_key=?"));
if (!get_reported.is_valid()) {
DLOG(ERROR) << "GetLastKAnonymityReported SQL statement did not compile: "
<< db.GetErrorMessage();
return std::nullopt;
}
get_reported.Reset(true);
get_reported.BindBlob(0, hashed_key);
if (!get_reported.Step()) {
return distant_past;
}
if (!get_reported.Succeeded()) {
return std::nullopt;
}
return get_reported.ColumnTime(0);
}
void DoUpdateLastKAnonymityReported(sql::Database& db,
const std::string& hashed_key,
base::Time now) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return;
}
// clang-format off
sql::Statement set_reported(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT OR REPLACE INTO joined_k_anon("
"hashed_key,"
"last_reported_to_anon_server_time) "
"VALUES(?,?)"));
// clang-format on
if (!set_reported.is_valid()) {
return;
}
set_reported.Reset(true);
set_reported.BindBlob(0, hashed_key);
set_reported.BindTime(1, now);
if (!set_reported.Run()) {
return;
}
transaction.Commit();
}
// Loads the view and click data for `provider_origin`, `eligible_origin`.
// Returns data if available, or reason if there isn't otherwise.
base::expected<ViewClickCountsForProviderAndEligible, MissingReason>
DoGetViewClickCountsForProviderAndEligible(sql::Database& db,
const url::Origin& provider_origin,
const url::Origin& eligible_origin) {
sql::Statement get_counts(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT uncompacted_view_events,"
"compacted_view_events,"
"uncompacted_click_events,"
"compacted_click_events "
"FROM view_and_click_events "
"WHERE provider_origin=? AND eligible_origin=?"));
if (!get_counts.is_valid()) {
return base::unexpected(MissingReason::kDbError);
}
get_counts.Reset(true);
get_counts.BindString(0, provider_origin.Serialize());
get_counts.BindString(1, eligible_origin.Serialize());
if (!get_counts.Step()) {
// No counts stored.
return base::unexpected(MissingReason::kNotInDb);
}
if (!get_counts.Succeeded()) {
return base::unexpected(MissingReason::kDbError);
}
ViewClickCountsForProviderAndEligible result;
if (!result.uncompacted_view_events.ParseFromString(
get_counts.ColumnStringView(0)) ||
!result.compacted_view_events.ParseFromString(
get_counts.ColumnStringView(1)) ||
!result.uncompacted_click_events.ParseFromString(
get_counts.ColumnStringView(2)) ||
!result.compacted_click_events.ParseFromString(
get_counts.ColumnStringView(3))) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kFailed);
return base::unexpected(MissingReason::kDecodeError);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kSucceeded);
}
return result;
}
void DoIncrementViewClickCounts(base::Time now,
ViewOrClickCountsPtr& view_or_click_counts,
base::TimeDelta max_window,
int64_t int_timestamp,
int32_t count) {
base::TimeDelta event_age = now - base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(int_timestamp));
if (event_age > max_window) {
return;
}
if (event_age <= base::Hours(1)) {
view_or_click_counts->past_hour += count;
}
if (event_age <= base::Days(1)) {
view_or_click_counts->past_day += count;
}
if (event_age <= base::Days(7)) {
view_or_click_counts->past_week += count;
}
if (event_age <= base::Days(30)) {
view_or_click_counts->past_30_days += count;
}
if (event_age <= base::Days(90)) {
view_or_click_counts->past_90_days += count;
}
// Older expired events may exist -- maintenance will eventually remove them.
}
// Reads in view and click counts from database, and converts them to an
// aggregated form, just including counts for each window and category,
// rather than the stored time info.
//
// If successful, the value will not be null; entries not in database
// are represented as unexpected(kNotInDb) instead.
[[nodiscard]] base::expected<ViewAndClickCountsPtr, MissingReason>
DoGetViewAndClickCountsSummarized(sql::Database& db,
base::Time now,
const url::Origin& provider_origin,
const url::Origin& eligible_origin) {
const base::TimeDelta max_window = blink::MaxInterestGroupLifetime();
base::expected<ViewClickCountsForProviderAndEligible, MissingReason>
raw_counts = DoGetViewClickCountsForProviderAndEligible(
/*db=*/db,
/*provider_origin=*/provider_origin,
/*eligible_origin=*/eligible_origin);
if (!raw_counts.has_value()) {
return base::unexpected(raw_counts.error());
}
ViewAndClickCountsPtr view_and_click_counts =
blink::mojom::ViewAndClickCounts::New(
/*view_counts=*/blink::mojom::ViewOrClickCounts::New(),
/*click_counts=*/blink::mojom::ViewOrClickCounts::New());
for (int64_t timestamp : raw_counts->uncompacted_view_events.timestamps()) {
DoIncrementViewClickCounts(
/*now=*/now,
/*view_or_click_counts=*/view_and_click_counts->view_counts,
/*max_window=*/max_window,
/*int_timestamp=*/timestamp, /*count=*/1);
}
for (ListOfTimestampAndCounts_Entry timestamp_and_count :
raw_counts->compacted_view_events.timestamp_and_counts()) {
DoIncrementViewClickCounts(
/*now=*/now,
/*view_or_click_counts=*/view_and_click_counts->view_counts,
/*max_window=*/max_window,
/*int_timestamp=*/timestamp_and_count.timestamp(),
/*count=*/timestamp_and_count.count());
}
for (int64_t timestamp : raw_counts->uncompacted_click_events.timestamps()) {
DoIncrementViewClickCounts(
/*now=*/now,
/*view_or_click_counts=*/view_and_click_counts->click_counts,
/*max_window=*/max_window,
/*int_timestamp=*/timestamp, /*count=*/1);
}
for (ListOfTimestampAndCounts_Entry timestamp_and_count :
raw_counts->compacted_click_events.timestamp_and_counts()) {
DoIncrementViewClickCounts(
/*now=*/now,
/*view_or_click_counts=*/view_and_click_counts->click_counts,
/*max_window=*/max_window,
/*int_timestamp=*/timestamp_and_count.timestamp(),
/*count=*/timestamp_and_count.count());
}
return view_and_click_counts;
}
void AggregateViewOrClickCounts(const blink::mojom::ViewOrClickCounts& in,
blink::mojom::ViewOrClickCounts& out) {
out.past_hour += in.past_hour;
out.past_day += in.past_day;
out.past_week += in.past_week;
out.past_30_days += in.past_30_days;
out.past_90_days += in.past_90_days;
}
void AggregateViewAndClickCounts(const blink::mojom::ViewAndClickCounts& in,
blink::mojom::ViewAndClickCounts& out) {
AggregateViewOrClickCounts(*in.view_counts, *out.view_counts);
AggregateViewOrClickCounts(*in.click_counts, *out.click_counts);
}
// Mutates browser signals for each entry in `interest_groups`, filling
// in loaded view and click counts.
//
// Returns true on success, and false on failure.
[[nodiscard]] bool DoGetViewAndClickCountsForGroups(
sql::Database& db,
base::Time now,
const url::Origin& owner,
InterestGroupsByName& interest_groups_by_name) {
std::vector<url::Origin> default_providers = {owner};
// Figure out which click tables we need.
std::set<url::Origin> clickiness_providers;
for (auto& [unused_name, storage_group] : interest_groups_by_name) {
if (storage_group.interest_group.IsNegativeInterestGroup()) {
continue;
}
if (!storage_group.interest_group.view_and_click_counts_providers ||
storage_group.interest_group.view_and_click_counts_providers->empty()) {
clickiness_providers.insert(owner);
continue;
}
for (const url::Origin& provider_origin :
*storage_group.interest_group.view_and_click_counts_providers) {
clickiness_providers.insert(provider_origin);
}
}
// Read all the needed tables into memory.
std::map<url::Origin, ViewAndClickCountsPtr> clickiness_summaries;
for (auto& provider_origin : clickiness_providers) {
base::expected<ViewAndClickCountsPtr, MissingReason> summary =
DoGetViewAndClickCountsSummarized(db, now,
/*provider_origin=*/provider_origin,
/*eligible_origin=*/owner);
if (!summary.has_value()) {
switch (summary.error()) {
case MissingReason::kNotInDb:
continue;
case MissingReason::kDbError:
case MissingReason::kDecodeError:
return false;
}
}
clickiness_summaries[provider_origin] = std::move(*summary);
}
// Now compose the info for each group.
for (const auto& [unused_name, storage_group] : interest_groups_by_name) {
storage_group.bidding_browser_signals->view_and_click_counts =
blink::mojom::ViewAndClickCounts::New(
/*view_counts=*/blink::mojom::ViewOrClickCounts::New(),
/*click_counts=*/blink::mojom::ViewOrClickCounts::New());
if (storage_group.interest_group.IsNegativeInterestGroup()) {
continue;
}
for (const url::Origin& provider_origin :
storage_group.interest_group.view_and_click_counts_providers &&
!storage_group.interest_group.view_and_click_counts_providers
->empty()
? *storage_group.interest_group.view_and_click_counts_providers
: default_providers) {
auto it = clickiness_summaries.find(provider_origin);
if (it == clickiness_summaries.end()) {
continue;
}
AggregateViewAndClickCounts(
*(it->second),
*storage_group.bidding_browser_signals->view_and_click_counts);
}
}
return true;
}
// Returns true if the rate limit (10 events in the last 20 seconds) has been
// exceeded.
bool IsClickinessRateLimited(base::Time now,
const ListOfTimestamps& uncompacted_events) {
constexpr int kMaxEvents = 10;
constexpr base::TimeDelta kRateLimitWindow = base::Seconds(20);
int event_count = 0;
// Iterate backwards -- the newest events are at the end.
for (int64_t timestamp : base::Reversed(uncompacted_events.timestamps())) {
base::TimeDelta event_age = now - base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(timestamp));
if (event_age > kRateLimitWindow) {
return false;
}
if (++event_count >= kMaxEvents) {
return true;
}
}
return false;
}
void DoRecordViewClick(sql::Database& db,
const network::AdAuctionEventRecord& record,
base::Time now) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return;
}
const std::vector<url::Origin>* eligible_origins = nullptr;
std::vector<url::Origin> default_eligible_origin;
if (record.eligible_origins.empty()) {
default_eligible_origin.emplace_back(record.providing_origin);
eligible_origins = &default_eligible_origin;
} else {
eligible_origins = &record.eligible_origins;
}
for (const url::Origin& eligible_origin : *eligible_origins) {
sql::Statement get_counts(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT uncompacted_view_events,"
"uncompacted_click_events "
"FROM view_and_click_events "
"WHERE provider_origin=? AND eligible_origin=?"));
if (!get_counts.is_valid()) {
DLOG(ERROR) << "DoRecordViewClick SELECT SQL statement did not compile: "
<< db.GetErrorMessage();
return;
}
get_counts.Reset(true);
get_counts.BindString(0, record.providing_origin.Serialize());
get_counts.BindString(1, eligible_origin.Serialize());
bool row_exists = false;
ListOfTimestamps uncompacted_view_events;
ListOfTimestamps uncompacted_click_events;
if (get_counts.Step()) {
row_exists = true;
if (!uncompacted_view_events.ParseFromString(
get_counts.ColumnStringView(0)) ||
!uncompacted_click_events.ParseFromString(
get_counts.ColumnStringView(1))) {
// TODO(crbug.com/355010821): Consider bubbling out the failure.
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kFailed);
return;
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kSucceeded);
}
} else if (!get_counts.Succeeded()) {
return;
}
const int64_t int_now = now.ToDeltaSinceWindowsEpoch().InMicroseconds();
switch (record.type) {
case AdAuctionEventRecord::Type::kUninitialized:
NOTREACHED();
case AdAuctionEventRecord::Type::kView:
if (IsClickinessRateLimited(now, uncompacted_view_events)) {
return;
}
uncompacted_view_events.add_timestamps(int_now);
break;
case AdAuctionEventRecord::Type::kClick:
if (IsClickinessRateLimited(now, uncompacted_click_events)) {
return;
}
uncompacted_click_events.add_timestamps(int_now);
break;
}
std::string uncompacted_view_events_str;
std::string uncompacted_click_events_str;
if (!uncompacted_view_events.SerializeToString(
&uncompacted_view_events_str) ||
!uncompacted_click_events.SerializeToString(
&uncompacted_click_events_str)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kFailed);
// TODO(crbug.com/355010821): Consider bubbling out the failure.
return;
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kSucceeded);
}
if (!row_exists) {
// clang-format off
sql::Statement insert_counts(
db.GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO view_and_click_events("
"provider_origin,"
"eligible_origin,"
"uncompacted_view_events,"
"compacted_view_events,"
"uncompacted_click_events,"
"compacted_click_events) "
"VALUES(?,?,?,?,?,?)"));
// clang-format on
if (!insert_counts.is_valid()) {
DLOG(ERROR)
<< "DoRecordViewClick INSERT SQL statement did not compile: "
<< db.GetErrorMessage();
return;
}
insert_counts.Reset(true);
insert_counts.BindString(0, record.providing_origin.Serialize());
insert_counts.BindString(1, eligible_origin.Serialize());
insert_counts.BindString(2, uncompacted_view_events_str);
insert_counts.BindString(3, "");
insert_counts.BindString(4, uncompacted_click_events_str);
insert_counts.BindString(5, "");
if (!insert_counts.Run()) {
return;
}
} else {
// clang-format off
sql::Statement update_counts(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE view_and_click_events "
"SET uncompacted_view_events=?,"
"uncompacted_click_events=? "
"WHERE provider_origin=? AND eligible_origin=?"));
// clang-format on
if (!update_counts.is_valid()) {
DLOG(ERROR)
<< "DoRecordViewClick UPDATE SQL statement did not compile: "
<< db.GetErrorMessage();
return;
}
update_counts.Reset(true);
update_counts.BindString(0, uncompacted_view_events_str);
update_counts.BindString(1, uncompacted_click_events_str);
update_counts.BindString(2, record.providing_origin.Serialize());
update_counts.BindString(3, eligible_origin.Serialize());
if (!update_counts.Run()) {
return;
}
}
}
transaction.Commit();
}
std::optional<std::vector<url::Origin>> DoGetAllInterestGroupOwners(
sql::Database& db,
base::Time expiring_after) {
std::vector<url::Origin> result;
sql::Statement load(db.GetCachedStatement(SQL_FROM_HERE,
"SELECT DISTINCT owner "
"FROM interest_groups "
"WHERE expiration>? "
"ORDER BY expiration DESC"));
if (!load.is_valid()) {
DLOG(ERROR) << "LoadAllInterestGroups SQL statement did not compile: "
<< db.GetErrorMessage();
return std::nullopt;
}
load.Reset(true);
load.BindTime(0, expiring_after);
while (load.Step()) {
result.push_back(DeserializeOrigin(load.ColumnStringView(0)));
}
if (!load.Succeeded()) {
return std::nullopt;
}
return result;
}
std::optional<std::vector<url::Origin>> DoGetAllInterestGroupJoiningOrigins(
sql::Database& db,
base::Time expiring_after) {
std::vector<url::Origin> result;
sql::Statement load(db.GetCachedStatement(SQL_FROM_HERE,
"SELECT DISTINCT joining_origin "
"FROM interest_groups "
"WHERE expiration>?"));
if (!load.is_valid()) {
DLOG(ERROR) << "LoadAllInterestGroupJoiningOrigins SQL statement did not "
"compile: "
<< db.GetErrorMessage();
return std::nullopt;
}
load.Reset(true);
load.BindTime(0, expiring_after);
while (load.Step()) {
result.push_back(DeserializeOrigin(load.ColumnStringView(0)));
}
if (!load.Succeeded()) {
return std::nullopt;
}
return result;
}
bool DoRemoveInterestGroupsMatchingOwnerAndJoiner(
sql::Database& db,
const url::Origin& owner,
const url::Origin& joining_origin,
base::Time expiring_after) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
std::vector<std::string> owner_joiner_names;
sql::Statement load(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT name "
"FROM interest_groups "
"WHERE owner=? AND joining_origin=? AND expiration>?"));
if (!load.is_valid()) {
return false;
}
load.Reset(true);
load.BindString(0, owner.Serialize());
load.BindString(1, joining_origin.Serialize());
load.BindTime(2, expiring_after);
while (load.Step()) {
owner_joiner_names.emplace_back(load.ColumnString(0));
}
for (auto& name : owner_joiner_names) {
if (!DoRemoveInterestGroup(
db, blink::InterestGroupKey{owner, std::move(name)})) {
return false;
}
}
return transaction.Commit();
}
std::optional<std::vector<std::pair<url::Origin, url::Origin>>>
DoGetAllInterestGroupOwnerJoinerPairs(sql::Database& db,
base::Time expiring_after) {
std::vector<std::pair<url::Origin, url::Origin>> result;
sql::Statement load(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT DISTINCT owner,joining_origin "
"FROM interest_groups "
"WHERE expiration>?"));
if (!load.is_valid()) {
DLOG(ERROR) << "LoadAllInterestGroupOwnerJoinerPairs SQL statement did not "
"compile: "
<< db.GetErrorMessage();
return std::nullopt;
}
load.Reset(true);
load.BindTime(0, expiring_after);
while (load.Step()) {
result.emplace_back(DeserializeOrigin(load.ColumnStringView(0)),
DeserializeOrigin(load.ColumnStringView(1)));
}
if (!load.Succeeded()) {
return std::nullopt;
}
return result;
}
bool GetPreviousWins(sql::Database& db,
const blink::InterestGroupKey& group_key,
base::Time win_time_after,
BiddingBrowserSignalsPtr& output) {
// clang-format off
sql::Statement prev_wins(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT win_time, ad "
"FROM win_history "
"WHERE owner = ? AND name = ? AND win_time >= ? "
"ORDER BY win_time DESC"));
// clang-format on
if (!prev_wins.is_valid()) {
DLOG(ERROR) << "GetInterestGroupsForOwner win_history SQL statement did "
"not compile: "
<< db.GetErrorMessage();
return false;
}
prev_wins.Reset(true);
prev_wins.BindString(0, Serialize(group_key.owner));
prev_wins.BindString(1, group_key.name);
prev_wins.BindTime(2, win_time_after);
while (prev_wins.Step()) {
PreviousWinPtr prev_win = blink::mojom::PreviousWin::New(
/*time=*/prev_wins.ColumnTime(0),
/*ad_json=*/prev_wins.ColumnString(1));
output->prev_wins.push_back(std::move(prev_win));
}
bool succeeded = prev_wins.Succeeded();
if (succeeded) {
UMA_HISTOGRAM_COUNTS_100000("Storage.InterestGroup.PrevWinsNumEntries",
output->prev_wins.size());
}
return succeeded;
}
// NOTE: Join and bid history can expire up to a few hours before the interest
// group, since join and bid history are floored to UTC days, but the
// interest group doesn't necessarily expire at UTC midnight.
//
// The reason for this is to reduce the number of join / bid rows stored.
//
// For joins, this can result in a join count of 0.
bool GetJoinCount(sql::Database& db,
const blink::InterestGroupKey& group_key,
base::Time joined_after,
BiddingBrowserSignalsPtr& output) {
// clang-format off
sql::Statement join_count(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT SUM(count) "
"FROM join_history "
"WHERE owner = ? AND name = ? AND join_time >=?"));
// clang-format on
if (!join_count.is_valid()) {
DLOG(ERROR) << "GetJoinCount SQL statement did not compile: "
<< db.GetErrorMessage();
return false;
}
join_count.Reset(true);
join_count.BindString(0, Serialize(group_key.owner));
join_count.BindString(1, group_key.name);
join_count.BindTime(2, joined_after);
while (join_count.Step()) {
output->join_count = join_count.ColumnInt64(0);
}
return join_count.Succeeded();
}
// NOTE: Join and bid history can expire up to a few hours before the interest
// group, since join and bid history are floored to UTC days, but the
// interest group doesn't necessarily expire at UTC midnight.
//
// The reason for this is to reduce the number of join / bid rows stored.
bool GetBidCount(sql::Database& db,
const blink::InterestGroupKey& group_key,
base::Time now,
BiddingBrowserSignalsPtr& output) {
// clang-format off
sql::Statement bid_count(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT SUM(count) "
"FROM bid_history "
"WHERE owner = ? AND name = ? AND bid_time >= ?"));
// clang-format on
if (!bid_count.is_valid()) {
DLOG(ERROR) << "GetBidCount SQL statement did not compile: "
<< db.GetErrorMessage();
return false;
}
bid_count.Reset(true);
bid_count.BindString(0, Serialize(group_key.owner));
bid_count.BindString(1, group_key.name);
bid_count.BindTime(2, now - blink::MaxInterestGroupLifetimeForMetadata());
while (bid_count.Step()) {
output->bid_count = bid_count.ColumnInt64(0);
}
return bid_count.Succeeded();
}
std::optional<DebugReportLockout> DoGetDebugReportLockout(
sql::Database& db,
std::optional<base::Time> ignore_before) {
sql::Statement lockout(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT starting_time, duration "
"FROM lockout_debugging_only_report "
"WHERE starting_time > ?"));
if (!lockout.is_valid()) {
DLOG(ERROR) << "GetDebugReportLockout SQL statement did not compile: "
<< db.GetErrorMessage();
return std::nullopt;
}
lockout.BindTime(0, ignore_before.value_or(base::Time::Min()));
if (lockout.Step()) {
return DebugReportLockout(lockout.ColumnTime(0),
lockout.ColumnTimeDelta(1));
}
if (!lockout.Succeeded()) {
// When reading the table fails, treat it as there is an unexpired lockout,
// to cautiously avoid sending more fDO reports than allowed in case there
// is a lockout in the table.
return DebugReportLockout(base::Time::Now(), base::Days(365));
}
// Reading the table succeeded but there was no row.
return std::nullopt;
}
void DoGetAllDebugReportCooldowns(
sql::Database& db,
std::optional<base::Time> ignore_before,
DebugReportLockoutAndCooldowns& debug_report_lockout_and_cooldowns) {
sql::Statement cooldowns(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT origin, starting_time, type "
"FROM cooldown_debugging_only_report "
"WHERE starting_time > ?"));
if (!cooldowns.is_valid()) {
DLOG(ERROR) << "GetAllDebugReportCooldowns SQL statement did not compile: "
<< db.GetErrorMessage();
return;
}
cooldowns.BindTime(0, ignore_before.value_or(base::Time::Min()));
while (cooldowns.Step()) {
url::Origin origin = DeserializeOrigin(cooldowns.ColumnStringView(0));
debug_report_lockout_and_cooldowns
.debug_report_cooldown_map[std::move(origin)] = DebugReportCooldown(
cooldowns.ColumnTime(1),
static_cast<DebugReportCooldownType>(cooldowns.ColumnInt(2)));
}
// TODO(qingxinwu): When reading the table fails, treat it as there is an
// unexpired cooldown.
}
std::optional<DebugReportCooldown> DoGetDebugReportCooldownForOrigin(
sql::Database& db,
const url::Origin& origin,
std::optional<base::Time> ignore_before) {
sql::Statement cooldown_debugging_only_report(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT starting_time, type "
"FROM cooldown_debugging_only_report "
"WHERE origin = ? AND starting_time > ?"));
if (!cooldown_debugging_only_report.is_valid()) {
DLOG(ERROR) << "GetDebugReportCooldown SQL statement did not compile: "
<< db.GetErrorMessage();
return std::nullopt;
}
cooldown_debugging_only_report.BindString(0, Serialize(origin));
cooldown_debugging_only_report.BindTime(
1, ignore_before.value_or(base::Time::Min()));
if (cooldown_debugging_only_report.Step()) {
return DebugReportCooldown(
cooldown_debugging_only_report.ColumnTime(0),
static_cast<DebugReportCooldownType>(
cooldown_debugging_only_report.ColumnInt(1)));
}
if (!cooldown_debugging_only_report.Succeeded()) {
// When reading the table fails, treat it as there is an unexpired cooldown,
// to cautiously avoid sending more fDO reports than allowed in case there
// is a cooldown in the table.
return DebugReportCooldown(base::Time::Now(),
DebugReportCooldownType::kRestrictedCooldown);
}
// Reading the table succeeded but there was no row.
return std::nullopt;
}
void DoGetDebugReportCooldowns(
sql::Database& db,
const base::flat_set<url::Origin>& origins,
std::optional<base::Time> ignore_before,
DebugReportLockoutAndCooldowns& debug_report_lockout_and_cooldowns) {
for (const url::Origin& origin : origins) {
std::optional<DebugReportCooldown> cooldown =
DoGetDebugReportCooldownForOrigin(db, origin, ignore_before);
if (cooldown.has_value()) {
debug_report_lockout_and_cooldowns.debug_report_cooldown_map[origin] =
*cooldown;
}
}
}
std::optional<std::vector<std::string>> DoGetInterestGroupNamesForOwner(
sql::Database& db,
const url::Origin& owner,
base::Time now) {
// clang-format off
sql::Statement get_names(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name "
"FROM interest_groups "
"WHERE owner=? AND expiration>? "
"ORDER BY expiration DESC"));
// clang-format on
if (!get_names.is_valid()) {
return std::nullopt;
}
get_names.Reset(true);
get_names.BindString(0, Serialize(owner));
get_names.BindTime(1, now);
std::vector<std::string> result;
while (get_names.Step()) {
result.push_back(get_names.ColumnString(0));
}
if (!get_names.Succeeded()) {
return std::nullopt;
}
return result;
}
std::optional<std::vector<std::string>>
DoGetAllRegularInterestGroupNamesForOwner(sql::Database& db,
const url::Origin& owner) {
// clang-format off
sql::Statement get_names(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name "
"FROM interest_groups "
"WHERE LENGTH(additional_bid_key) == 0 AND owner=? "
"ORDER BY expiration DESC"));
// clang-format on
if (!get_names.is_valid()) {
return std::nullopt;
}
get_names.Reset(true);
get_names.BindString(0, Serialize(owner));
std::vector<std::string> result;
while (get_names.Step()) {
result.push_back(get_names.ColumnString(0));
}
if (!get_names.Succeeded()) {
return std::nullopt;
}
return result;
}
std::optional<std::vector<std::string>>
DoGetAllNegativeInterestGroupNamesForOwner(sql::Database& db,
const url::Origin& owner) {
// clang-format off
sql::Statement get_names(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name "
"FROM interest_groups "
"WHERE NOT LENGTH(additional_bid_key) == 0 AND owner=? "
"ORDER BY expiration DESC"));
// clang-format on
if (!get_names.is_valid()) {
return std::nullopt;
}
get_names.Reset(true);
get_names.BindString(0, Serialize(owner));
std::vector<std::string> result;
while (get_names.Step()) {
result.push_back(get_names.ColumnString(0));
}
if (!get_names.Succeeded()) {
return std::nullopt;
}
return result;
}
std::optional<StorageInterestGroup> DoGetStoredInterestGroup(
sql::Database& db,
const PassKey& passkey,
const blink::InterestGroupKey& group_key,
base::Time now) {
// We need an InterestGroupsByName for DoGetViewAndClickCountsForGroups;
// but that doesn't actually look at names.
InterestGroupsByName interest_groups_by_name;
StorageInterestGroup& db_interest_group = interest_groups_by_name["0"];
if (!DoLoadInterestGroup(db, passkey, group_key, db_interest_group)) {
return std::nullopt;
}
db_interest_group.bidding_browser_signals =
blink::mojom::BiddingBrowserSignals::New();
if (!GetJoinCount(db, group_key,
now - blink::MaxInterestGroupLifetimeForMetadata(),
db_interest_group.bidding_browser_signals)) {
return std::nullopt;
}
if (!GetBidCount(db, group_key,
now - blink::MaxInterestGroupLifetimeForMetadata(),
db_interest_group.bidding_browser_signals)) {
return std::nullopt;
}
if (!GetPreviousWins(db, group_key,
now - blink::MaxInterestGroupLifetimeForMetadata(),
db_interest_group.bidding_browser_signals)) {
return std::nullopt;
}
if (!DoGetViewAndClickCountsForGroups(db, now,
db_interest_group.interest_group.owner,
interest_groups_by_name)) {
return std::nullopt;
}
return std::move(db_interest_group);
}
std::optional<std::vector<InterestGroupUpdateParameter>>
DoGetInterestGroupsForUpdate(sql::Database& db,
const url::Origin& owner,
base::Time now,
size_t groups_limit) {
std::vector<InterestGroupUpdateParameter> result;
// To maximize the chance of reusing open sockets from a previous batch, sort
// the storage groups by joining origin.
//
// To mitigate potential side-channel vulnerabilities and prevent updates from
// occurring in a predictable sequence, such as the order of expiration, data
// is shuffled when reading from the database.
sql::Statement get_interest_group_update_parameters(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT name, update_url, joining_origin "
"FROM interest_groups "
"WHERE owner=? AND expiration>? AND ?>=next_update_after "
"ORDER BY joining_origin, RANDOM() "
"LIMIT ?"));
if (!get_interest_group_update_parameters.is_valid()) {
return std::nullopt;
}
get_interest_group_update_parameters.Reset(true);
get_interest_group_update_parameters.BindString(0, Serialize(owner));
get_interest_group_update_parameters.BindTime(1, now);
get_interest_group_update_parameters.BindTime(2, now);
get_interest_group_update_parameters.BindInt64(3, groups_limit);
while (get_interest_group_update_parameters.Step()) {
std::optional<GURL> update_url = DeserializeURL(
get_interest_group_update_parameters.ColumnStringView(1));
if (!update_url.has_value()) {
continue;
}
result.emplace_back(
blink::InterestGroupKey(
owner, get_interest_group_update_parameters.ColumnString(0)),
update_url.value(),
DeserializeOrigin(
get_interest_group_update_parameters.ColumnStringView(2)));
}
if (!get_interest_group_update_parameters.Succeeded()) {
return std::nullopt;
}
return result;
}
std::optional<std::vector<StorageInterestGroup>> DoGetInterestGroupsForOwner(
sql::Database& db,
const PassKey& passkey,
const url::Origin& owner,
base::Time now) {
TRACE_EVENT("fledge", "DoGetInterestGroupsForOwner");
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return std::nullopt;
}
InterestGroupsByName interest_group_by_name;
{
TRACE_EVENT("fledge", "load_from_interest_groups_table");
// clang-format off
sql::Statement load(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT " COMMON_INTEREST_GROUPS_QUERY_FIELDS ","
"name "
"FROM interest_groups "
"WHERE owner=? AND expiration>?"));
// clang-format on
if (!load.is_valid()) {
return std::nullopt;
}
load.BindString(0, Serialize(owner));
load.BindTime(1, now);
while (load.Step()) {
std::string name = load.ColumnString(31);
StorageInterestGroup& db_interest_group = interest_group_by_name[name];
db_interest_group.bidding_browser_signals =
blink::mojom::BiddingBrowserSignals::New();
db_interest_group.interest_group.owner = owner;
db_interest_group.interest_group.name = std::move(name);
PopulateInterestGroupFromQueryResult(load, passkey, db_interest_group);
}
if (!load.Succeeded()) {
return std::nullopt;
}
}
{
TRACE_EVENT("fledge", "load_from_join_history_table");
// clang-format off
sql::Statement join_count(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name, SUM(count) "
"FROM join_history "
"WHERE owner=? AND join_time>=? "
"GROUP BY name"));
// clang-format on
join_count.BindString(0, Serialize(owner));
join_count.BindTime(1, now - blink::MaxInterestGroupLifetimeForMetadata());
while (join_count.Step()) {
auto it = interest_group_by_name.find(join_count.ColumnStringView(0));
if (it == interest_group_by_name.end()) {
// TODO(yaoxia): Return std::nullopt?
continue;
}
StorageInterestGroup& db_interest_group = it->second;
db_interest_group.bidding_browser_signals->join_count =
join_count.ColumnInt64(1);
}
if (!join_count.Succeeded()) {
return std::nullopt;
}
}
{
TRACE_EVENT("fledge", "load_from_bid_history_table");
// clang-format off
sql::Statement bid_count(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name, SUM(count) "
"FROM bid_history "
"WHERE owner=? AND bid_time>=? "
"GROUP BY name"));
// clang-format on
bid_count.BindString(0, Serialize(owner));
bid_count.BindTime(1, now - blink::MaxInterestGroupLifetimeForMetadata());
while (bid_count.Step()) {
auto it = interest_group_by_name.find(bid_count.ColumnStringView(0));
if (it == interest_group_by_name.end()) {
// TODO(yaoxia): Return std::nullopt?
continue;
}
StorageInterestGroup& db_interest_group = it->second;
db_interest_group.bidding_browser_signals->bid_count =
bid_count.ColumnInt64(1);
}
if (!bid_count.Succeeded()) {
return std::nullopt;
}
}
{
TRACE_EVENT("fledge", "load_from_prev_wins_table");
// clang-format off
sql::Statement prev_wins(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT name, win_time, ad "
"FROM win_history "
"WHERE owner=? AND win_time>=? "
"ORDER BY name, win_time DESC "));
// clang-format on
prev_wins.BindString(0, Serialize(owner));
prev_wins.BindTime(1, now - blink::MaxInterestGroupLifetimeForMetadata());
while (prev_wins.Step()) {
auto it = interest_group_by_name.find(prev_wins.ColumnStringView(0));
if (it == interest_group_by_name.end()) {
// TODO(yaoxia): Return std::nullopt?
continue;
}
StorageInterestGroup& db_interest_group = it->second;
PreviousWinPtr prev_win = blink::mojom::PreviousWin::New(
/*time=*/prev_wins.ColumnTime(1),
/*ad_json=*/prev_wins.ColumnString(2));
db_interest_group.bidding_browser_signals->prev_wins.push_back(
std::move(prev_win));
}
if (!prev_wins.Succeeded()) {
return std::nullopt;
}
}
{
TRACE_EVENT("fledge", "load_from_clicks_views_table");
if (!DoGetViewAndClickCountsForGroups(db, now, owner,
interest_group_by_name)) {
return std::nullopt;
}
}
if (!transaction.Commit()) {
return std::nullopt;
}
std::vector<StorageInterestGroup> result;
for (auto& [key, value] : interest_group_by_name) {
result.push_back(std::move(value));
}
// Sort `result` by decreasing expiration time.
std::sort(result.begin(), result.end(),
[](const StorageInterestGroup& a, const StorageInterestGroup& b) {
return a.interest_group.expiry > b.interest_group.expiry;
});
return result;
}
std::optional<std::vector<blink::InterestGroupKey>>
DoGetInterestGroupNamesForJoiningOrigin(sql::Database& db,
const url::Origin& joining_origin,
base::Time now) {
std::vector<blink::InterestGroupKey> result;
// clang-format off
sql::Statement load(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT owner,name "
"FROM interest_groups "
"WHERE joining_origin=? AND expiration>?"));
// clang-format on
if (!load.is_valid()) {
DLOG(ERROR) << "GetInterestGroupNamesForJoiningOrigin SQL statement did "
"not compile: "
<< db.GetErrorMessage();
return std::nullopt;
}
load.Reset(true);
load.BindString(0, Serialize(joining_origin));
load.BindTime(1, now);
while (load.Step()) {
result.emplace_back(DeserializeOrigin(load.ColumnStringView(0)),
load.ColumnString(1));
}
if (!load.Succeeded()) {
return std::nullopt;
}
return result;
}
bool DoDeleteViewClickCounts(sql::Database& db) {
sql::Statement remove_view_clicks(db.GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM view_and_click_events"));
if (!remove_view_clicks.is_valid()) {
return false;
}
remove_view_clicks.Reset(true);
return remove_view_clicks.Run();
}
bool DoDeleteViewClickCountsForProvider(
sql::Database& db,
const StoragePartition::StorageKeyMatcherFunction& storage_key_matcher) {
std::vector<url::Origin> providers_to_delete;
sql::Statement load(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT DISTINCT provider_origin FROM view_and_click_events"));
if (!load.is_valid()) {
return false;
}
load.Reset(true);
while (load.Step()) {
url::Origin origin = DeserializeOrigin(load.ColumnStringView(0));
if (storage_key_matcher.Run(blink::StorageKey::CreateFirstParty(origin))) {
providers_to_delete.push_back(std::move(origin));
}
}
sql::Statement del(db.GetCachedStatement(
SQL_FROM_HERE,
"DELETE FROM view_and_click_events WHERE provider_origin=?"));
if (!del.is_valid()) {
return false;
}
for (const url::Origin& origin : providers_to_delete) {
del.Reset(true);
del.BindString(0, origin.Serialize());
if (!del.Run()) {
return false;
}
}
return true;
}
bool DoDeleteInterestGroupData(
sql::Database& db,
StoragePartition::StorageKeyMatcherFunction storage_key_matcher,
bool user_initiated_deletion) {
const base::Time distant_past = base::Time::Min();
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
// For view & click events, we generally delete everything even when the user
// asked for a subset of sites, since we do not know what top-level site the
// events are associated with, so we have to be conservative to make sure to
// match everything that may be expected. Doing this for Clear-Site-Data,
// however, would let sites hostilely delete clickiness data of others,
// so if `user_initiated_deletion` is false, only things provided by that
// origin are deleted.
if (user_initiated_deletion || storage_key_matcher.is_null()) {
if (!DoDeleteViewClickCounts(db)) {
return false;
}
} else {
if (!DoDeleteViewClickCountsForProvider(db, storage_key_matcher)) {
return false;
}
}
std::vector<url::Origin> affected_origins;
std::optional<std::vector<url::Origin>> maybe_all_origins =
DoGetAllInterestGroupOwners(db, distant_past);
if (!maybe_all_origins) {
return false;
}
for (const url::Origin& origin : maybe_all_origins.value()) {
if (storage_key_matcher.is_null() ||
storage_key_matcher.Run(blink::StorageKey::CreateFirstParty(origin))) {
affected_origins.push_back(origin);
}
}
for (const auto& affected_origin : affected_origins) {
std::optional<std::vector<std::string>> maybe_group_names =
DoGetInterestGroupNamesForOwner(db, affected_origin, distant_past);
if (!maybe_group_names) {
return false;
}
for (const auto& group_name : maybe_group_names.value()) {
if (!DoRemoveInterestGroup(
db, blink::InterestGroupKey(affected_origin, group_name))) {
return false;
}
}
}
affected_origins.clear();
maybe_all_origins = DoGetAllInterestGroupJoiningOrigins(db, distant_past);
if (!maybe_all_origins) {
return false;
}
for (const url::Origin& origin : maybe_all_origins.value()) {
if (storage_key_matcher.is_null() ||
storage_key_matcher.Run(blink::StorageKey::CreateFirstParty(origin))) {
affected_origins.push_back(origin);
}
}
for (const auto& affected_origin : affected_origins) {
std::optional<std::vector<blink::InterestGroupKey>> maybe_group_names =
DoGetInterestGroupNamesForJoiningOrigin(db, affected_origin,
distant_past);
if (!maybe_group_names) {
return false;
}
for (const auto& interest_group_key : maybe_group_names.value()) {
if (!DoRemoveInterestGroup(db, interest_group_key)) {
return false;
}
}
}
return transaction.Commit();
}
bool DoSetInterestGroupPriority(sql::Database& db,
const blink::InterestGroupKey& group_key,
double priority) {
// clang-format off
sql::Statement set_priority_sql(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE interest_groups "
"SET priority=? "
"WHERE owner=? AND name=?"));
// clang-format on
if (!set_priority_sql.is_valid()) {
DLOG(ERROR) << "SetPriority SQL statement did not compile.";
return false;
}
set_priority_sql.Reset(true);
set_priority_sql.BindDouble(0, priority);
set_priority_sql.BindString(1, Serialize(group_key.owner));
set_priority_sql.BindString(2, group_key.name);
return set_priority_sql.Run();
}
bool DoSetInterestGroupPrioritySignalsOverrides(
sql::Database& db,
const blink::InterestGroupKey& group_key,
const std::optional<base::flat_map<std::string, double>>&
priority_signals_overrides) {
// clang-format off
sql::Statement update_priority_signals_overrides_sql(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE interest_groups "
"SET priority_signals_overrides=? "
"WHERE owner=? AND name=?"));
// clang-format on
if (!update_priority_signals_overrides_sql.is_valid()) {
DLOG(ERROR) << "SetPrioritySignalsOverrides SQL statement did not compile.";
return false;
}
update_priority_signals_overrides_sql.Reset(true);
update_priority_signals_overrides_sql.BindString(
0, Serialize(priority_signals_overrides));
update_priority_signals_overrides_sql.BindString(1,
Serialize(group_key.owner));
update_priority_signals_overrides_sql.BindString(2, group_key.name);
return update_priority_signals_overrides_sql.Run();
}
bool DeleteOldJoins(sql::Database& db, base::Time cutoff) {
sql::Statement del_join_history(db.GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM join_history WHERE join_time <= ?"));
if (!del_join_history.is_valid()) {
DLOG(ERROR) << "DeleteOldJoins SQL statement did not compile.";
return false;
}
del_join_history.Reset(true);
del_join_history.BindTime(0, cutoff);
if (!del_join_history.Run()) {
DLOG(ERROR) << "Could not delete old join_history.";
return false;
}
return true;
}
bool DeleteOldBids(sql::Database& db, base::Time cutoff) {
sql::Statement del_bid_history(db.GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM bid_history WHERE bid_time <= ?"));
if (!del_bid_history.is_valid()) {
DLOG(ERROR) << "DeleteOldBids SQL statement did not compile.";
return false;
}
del_bid_history.Reset(true);
del_bid_history.BindTime(0, cutoff);
if (!del_bid_history.Run()) {
DLOG(ERROR) << "Could not delete old bid_history.";
return false;
}
return true;
}
bool DeleteOldWins(sql::Database& db, base::Time cutoff) {
sql::Statement del_win_history(db.GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM win_history WHERE win_time <= ?"));
if (!del_win_history.is_valid()) {
DLOG(ERROR) << "DeleteOldWins SQL statement did not compile.";
return false;
}
del_win_history.Reset(true);
del_win_history.BindTime(0, cutoff);
if (!del_win_history.Run()) {
DLOG(ERROR) << "Could not delete old win_history.";
return false;
}
return true;
}
bool DoClearExcessInterestGroups(
sql::Database& db,
const url::Origin& affected_origin,
const std::optional<std::vector<std::string>> maybe_interest_groups,
size_t max_owner_interest_groups) {
if (!maybe_interest_groups) {
return false;
}
for (size_t group_idx = max_owner_interest_groups;
group_idx < maybe_interest_groups.value().size(); group_idx++) {
if (!DoRemoveInterestGroup(
db,
blink::InterestGroupKey(
affected_origin, maybe_interest_groups.value()[group_idx]))) {
return false;
}
}
return true;
}
bool ClearExcessInterestGroups(sql::Database& db,
size_t max_owners,
size_t max_owner_regular_interest_groups,
size_t max_owner_negative_interest_groups) {
const base::Time distant_past = base::Time::Min();
const std::optional<std::vector<url::Origin>> maybe_all_origins =
DoGetAllInterestGroupOwners(db, distant_past);
if (!maybe_all_origins) {
return false;
}
for (size_t owner_idx = 0; owner_idx < maybe_all_origins.value().size();
owner_idx++) {
const url::Origin& affected_origin = maybe_all_origins.value()[owner_idx];
if (!DoClearExcessInterestGroups(
db, affected_origin,
DoGetAllRegularInterestGroupNamesForOwner(db, affected_origin),
owner_idx < max_owners ? max_owner_regular_interest_groups : 0)) {
return false;
}
if (!DoClearExcessInterestGroups(
db, affected_origin,
DoGetAllNegativeInterestGroupNamesForOwner(db, affected_origin),
owner_idx < max_owners ? max_owner_negative_interest_groups : 0)) {
return false;
}
}
return true;
}
bool ClearExpiredInterestGroups(sql::Database& db,
base::Time expiration_before) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
sql::Statement expired_interest_group(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT owner, name "
"FROM interest_groups "
"WHERE expiration<=?"));
if (!expired_interest_group.is_valid()) {
DLOG(ERROR) << "ClearExpiredInterestGroups SQL statement did not compile.";
return false;
}
expired_interest_group.Reset(true);
expired_interest_group.BindTime(0, expiration_before);
std::vector<blink::InterestGroupKey> expired_groups;
while (expired_interest_group.Step()) {
expired_groups.emplace_back(
DeserializeOrigin(expired_interest_group.ColumnStringView(0)),
expired_interest_group.ColumnString(1));
}
if (!expired_interest_group.Succeeded()) {
DLOG(ERROR) << "ClearExpiredInterestGroups could not get expired groups.";
// Keep going so we can clear any groups that we did get.
}
for (const auto& interest_group : expired_groups) {
if (!DoRemoveInterestGroup(db, interest_group)) {
return false;
}
}
return transaction.Commit();
}
// Removes interest groups so that per-owner limit is respected.
bool ClearExcessiveStorage(sql::Database& db, size_t max_owner_storage_size) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
// We go through groups for each owner, starting with the ones that expire
// later, accumulating the stored size. If the accumulated size is too much,
// we start marking groups for deletion. This means that the interest groups
// expiring soonest will be deleted if we need to free up space.
// clang-format off
sql::Statement excessive_storage_groups(db.GetCachedStatement(
SQL_FROM_HERE,
"SELECT owner, name, storage_size "
"FROM interest_groups "
"ORDER BY owner, expiration DESC"
));
// clang-format on
if (!excessive_storage_groups.is_valid()) {
return false;
}
excessive_storage_groups.Reset(true);
std::vector<blink::InterestGroupKey> groups_to_remove;
std::optional<url::Origin> previous;
size_t cum_size;
while (excessive_storage_groups.Step()) {
url::Origin group_owner =
DeserializeOrigin(excessive_storage_groups.ColumnStringView(0));
std::string group_name = excessive_storage_groups.ColumnString(1);
size_t group_size = excessive_storage_groups.ColumnInt64(2);
if (!previous || *previous != group_owner) {
previous = group_owner;
cum_size = 0;
}
if (cum_size + group_size > max_owner_storage_size) {
groups_to_remove.emplace_back(std::move(group_owner),
std::move(group_name));
} else {
cum_size += group_size;
}
}
if (!excessive_storage_groups.Succeeded()) {
DLOG(ERROR) << "ClearExcessiveStorage could not get expired groups.";
// Keep going so we can clear any groups that we did get.
}
for (const auto& interest_group : groups_to_remove) {
if (!DoRemoveInterestGroup(db, interest_group)) {
return false;
}
}
return transaction.Commit();
}
bool ClearExpiredKAnon(sql::Database& db, base::Time cutoff) {
// clang-format off
sql::Statement expired_k_anon(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM joined_k_anon "
"WHERE last_reported_to_anon_server_time < ?"));
// clang-format on
if (!expired_k_anon.is_valid()) {
DLOG(ERROR) << "ClearExpiredKAnon SQL statement did not compile.";
return false;
}
expired_k_anon.Reset(true);
expired_k_anon.BindTime(0, cutoff);
return expired_k_anon.Run();
}
bool DeleteExpiredDebugReportCooldown(sql::Database& db, base::Time now) {
// clang-format off
sql::Statement delete_cooldown(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM cooldown_debugging_only_report "
"WHERE (type==? AND starting_time<?) OR "
"(type==? AND starting_time<?)"));
// clang-format on
if (!delete_cooldown.is_valid()) {
DLOG(ERROR)
<< "DeleteExpiredDebugReportCooldown SQL statement did not compile.";
return false;
}
delete_cooldown.Reset(true);
std::optional<base::TimeDelta> short_duration =
ConvertDebugReportCooldownTypeToDuration(
DebugReportCooldownType::kShortCooldown);
std::optional<base::TimeDelta> restricted_duration =
ConvertDebugReportCooldownTypeToDuration(
DebugReportCooldownType::kRestrictedCooldown);
CHECK(short_duration.has_value());
CHECK(restricted_duration.has_value());
delete_cooldown.BindInt(
0, static_cast<int>(DebugReportCooldownType::kShortCooldown));
delete_cooldown.BindTime(1, now - *short_duration);
delete_cooldown.BindInt(
2, static_cast<int>(DebugReportCooldownType::kRestrictedCooldown));
delete_cooldown.BindTime(3, now - *restricted_duration);
if (!delete_cooldown.Run()) {
DLOG(ERROR) << "Could not delete old debug report cooldown.";
return false;
}
return true;
}
bool DoDeleteAllDebugReportCooldowns(sql::Database& db) {
sql::Statement clear_cooldown(db.GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM cooldown_debugging_only_report"));
return clear_cooldown.Run();
}
bool ClearExpiredBiddingAndAuctionKeys(sql::Database& db, base::Time now) {
sql::Statement clear_expired_keys(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM bidding_and_auction_server_keys "
"WHERE expiration<?"));
clear_expired_keys.BindTime(0, now);
return clear_expired_keys.Run();
}
bool ClearExpiredCachedKAnonymityHashes(sql::Database& db, base::Time now) {
const base::TimeDelta ttl = features::kFledgeCacheKAnonHashedKeysTtl.Get();
sql::Statement clear_expired_cache_entries(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM cached_k_anonymity_hashes "
"WHERE fetch_time<?"));
if (!clear_expired_cache_entries.is_valid()) {
DLOG(ERROR)
<< "ClearExpiredCachedKAnonymityHashes SQL statement did not compile.";
return {};
}
clear_expired_cache_entries.BindTime(0, now - ttl);
return clear_expired_cache_entries.Run();
}
// Used by CompactClickiness(). The number of events that occurred at the
// base::Time in CompactionMap.
using ClickinessCount = int64_t;
// Used by CompactClickiness(). The raw integer timestamp stored in the database
// for clickiness events. Stored as microseconds since the Windows epoch.
using ClickinessIntTimestamp = int64_t;
// Used by CompactClickiness(). Temporary storage used during the compaction
// process.
using CompactionMap = std::map<base::Time, ClickinessCount>;
// A helper function for ClickinessCompactionRawToMap(), which is itself a
// helper for CompactClickiness(). For a given raw `int_timestamp` loaded from
// the database (which is in microseconds since the Windows epoch) and `count`
// of events at that timestamp, update the count at the compacted time.
//
// Events older than 1 hour (determined using `now`) will be grouped by hour,
// and events older than 1 day will be grouped by day.
void ClickinessCompactionAddTimestampToMap(base::Time now,
ClickinessIntTimestamp int_timestamp,
ClickinessCount count,
CompactionMap& map) {
base::Time timestamp =
base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(int_timestamp));
base::TimeDelta event_age = now - timestamp;
if (event_age <= base::Hours(1)) {
// Count can be > 1 here if the clock is rolled back.
map[timestamp] += count;
} else if (event_age <= base::Days(1)) {
map[TruncateToHour(timestamp)] += count;
} else if (event_age <= base::Days(90)) {
map[TruncateToDay(timestamp)] += count;
}
}
// A helper function for CompactClickiness(). For each compacted and uncompacted
// events protobuf loaded from the database (`raw`), compact those events into
// the returned map.
//
// Events older than 1 hour (determined using `now`) will be grouped by hour,
// and events older than 1 day will be grouped by day.
//
// ClickinessCompactionMapToRaw() will then convert the returned map back into
// raw protobuf form that can be written to the database, splitting events into
// compacted and uncompacted events, with events younger than an hour going into
// uncompacted events.
//
// Returns std::nullopt on error.
std::optional<CompactionMap> ClickinessCompactionRawToMap(
base::Time now,
const InterestGroupStorage::ClickinessCompactionRawEvents& raw) {
CompactionMap map;
{
// Write raw uncompacted events.
ListOfTimestamps uncompacted_events;
if (!uncompacted_events.ParseFromString(raw.uncompacted_events)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kFailed);
return std::nullopt;
}
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kSucceeded);
for (ClickinessIntTimestamp int_timestamp :
uncompacted_events.timestamps()) {
ClickinessCompactionAddTimestampToMap(now, int_timestamp, /*count=*/1,
map);
}
}
{
// Add raw compacted events.
ListOfTimestampAndCounts compacted_events;
if (!compacted_events.ParseFromString(raw.compacted_events)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kFailed);
return std::nullopt;
}
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoDeserializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kSucceeded);
for (ListOfTimestampAndCounts_Entry entry :
compacted_events.timestamp_and_counts()) {
ClickinessCompactionAddTimestampToMap(now, entry.timestamp(),
/*count=*/entry.count(), map);
}
}
return map;
}
// A helper function for CompactClickiness(). Converts `map` (produced by
// ClickinessCompactionRawToMap()) back to the raw protobuf form that can be
// written to the database, splitting events into compacted and uncompacted
// events, with events younger than an hour going into
// uncompacted events.
//
// Returns std::nullopt on error.
std::optional<InterestGroupStorage::ClickinessCompactionRawEvents>
ClickinessCompactionMapToRaw(base::Time now, const CompactionMap& map) {
InterestGroupStorage::ClickinessCompactionRawEvents raw;
ListOfTimestamps uncompacted_events;
ListOfTimestampAndCounts compacted_events;
// Populate proto objects.
for (const auto& [timestamp, count] : map) {
base::TimeDelta event_age = now - timestamp;
if (event_age <= base::Hours(1)) {
for (int i = 0; i < count; i++) {
// While (possibly) rare, it's possible for more than one of the same
// event to be recorded at the same time.
uncompacted_events.add_timestamps(
timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
}
} else {
ListOfTimestampAndCounts_Entry* entry =
compacted_events.add_timestamp_and_counts();
entry->set_timestamp(
timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
entry->set_count(count);
}
}
// Serialize proto objects to raw strings.
if (!uncompacted_events.SerializeToString(&raw.uncompacted_events) ||
!compacted_events.SerializeToString(&raw.compacted_events)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kFailed);
return std::nullopt;
}
base::UmaHistogramEnumeration(
"Storage.InterestGroup.ProtoSerializationResult.ListOfTimestamps",
InterestGroupStorageProtoSerializationResult::kSucceeded);
return raw;
}
// Computation portion of compaction --- takes in raw protobufs from database,
// and returns compacted portion, unless it fails.
std::optional<InterestGroupStorage::ClickinessCompactionRawEvents>
ComputeCompactClickiness(
base::Time now,
const InterestGroupStorage::ClickinessCompactionRawEvents& raw) {
// Perform the compaction in a std::map. This avoids n^2 performance of
// in-place insertions. The raw -> map and map -> raw phases are separate for
// better structuring. The map -> raw phase will write events back
// to the uncompacted list that are less than an hour old.
std::optional<CompactionMap> map = ClickinessCompactionRawToMap(now, raw);
if (!map) {
return std::nullopt;
}
return ClickinessCompactionMapToRaw(now, *map);
}
// To reduce storage utilization for view and click events, compact events older
// than an hour to store only a per-hour count, and events older than a day to
// store only a per-day count.
bool CompactClickiness(sql::Database& db, base::Time now) {
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
// For each primary key in view_and_click_events (which is the
// (provider_origin, eligible_origin) tuple), we load all uncompacted and
// compacted view and click events, compact them in memory, then write the
// results back to that (provider_origin, eligible_origin).
sql::Statement get_all_rows_sql(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT provider_origin,"
"eligible_origin,"
"uncompacted_view_events,"
"compacted_view_events,"
"uncompacted_click_events,"
"compacted_click_events "
"FROM view_and_click_events"));
if (!get_all_rows_sql.is_valid()) {
return false;
}
sql::Statement update_row(
db.GetCachedStatement(SQL_FROM_HERE,
"UPDATE view_and_click_events "
"SET uncompacted_view_events=?,"
"compacted_view_events=?,"
"uncompacted_click_events=?,"
"compacted_click_events=? "
"WHERE provider_origin=? AND eligible_origin=?"));
if (!update_row.is_valid()) {
return false;
}
sql::Statement delete_row(
db.GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM view_and_click_events "
"WHERE provider_origin=? AND eligible_origin=?"));
if (!delete_row.is_valid()) {
return false;
}
// For each (provider_origin, eligible_origin) in view_and_click_events,
// compact that (provider_origin, eligible_origin), and write the updated
// results:
while (get_all_rows_sql.Step()) {
update_row.Reset(/*clear_bound_vars=*/true);
// Read raw compacted and uncompacted events into `raw`, and compact them
// for both view and clicks, clearing memory we don't need anymore as
// we go to reduce peak memory consumption, avoiding keeping excess copies
// of all events in memory.
std::string provider_origin(get_all_rows_sql.ColumnStringView(0));
std::string eligible_origin(get_all_rows_sql.ColumnStringView(1));
// Views
std::optional<InterestGroupStorage::ClickinessCompactionRawEvents>
raw_views;
{
InterestGroupStorage::ClickinessCompactionRawEvents raw;
raw.uncompacted_events = get_all_rows_sql.ColumnStringView(2);
raw.compacted_events = get_all_rows_sql.ColumnStringView(3);
raw_views = ComputeCompactClickiness(now, raw);
}
if (!raw_views) {
return false;
}
// Clicks
std::optional<InterestGroupStorage::ClickinessCompactionRawEvents>
raw_clicks;
{
InterestGroupStorage::ClickinessCompactionRawEvents raw;
raw.uncompacted_events = get_all_rows_sql.ColumnStringView(4);
raw.compacted_events = get_all_rows_sql.ColumnStringView(5);
raw_clicks = ComputeCompactClickiness(now, raw);
}
if (!raw_clicks) {
return false;
}
// Done compacting this (provider_origin, eligible_origin) row.
// If something is left, write the results back to the database,
// otherwise delete it.
if (raw_views->uncompacted_events.empty() &&
raw_views->compacted_events.empty() &&
raw_clicks->uncompacted_events.empty() &&
raw_clicks->compacted_events.empty()) {
delete_row.Reset(/*clear_bound_vars=*/true);
delete_row.BindString(0, provider_origin);
delete_row.BindString(1, eligible_origin);
if (!delete_row.Run()) {
return false;
}
} else {
update_row.BindString(0, raw_views->uncompacted_events);
update_row.BindString(1, raw_views->compacted_events);
update_row.BindString(2, raw_clicks->uncompacted_events);
update_row.BindString(3, raw_clicks->compacted_events);
update_row.BindString(4, provider_origin);
update_row.BindString(5, eligible_origin);
if (!update_row.Run()) {
return false;
}
}
}
if (!get_all_rows_sql.Succeeded()) {
return false;
}
return transaction.Commit();
}
bool DoSetBiddingAndAuctionServerKeys(sql::Database& db,
const url::Origin& coordinator,
std::string serialized_keys,
base::Time expiration) {
sql::Statement insert_keys_statement(db.GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR REPLACE INTO "
"bidding_and_auction_server_keys(coordinator,keys,expiration) "
"VALUES (?,?,?)"));
if (!insert_keys_statement.is_valid()) {
DLOG(ERROR)
<< "DoSetBiddingAndAuctionServerKeys SQL statement did not compile.";
return false;
}
insert_keys_statement.Reset(true);
insert_keys_statement.BindString(0, Serialize(coordinator));
insert_keys_statement.BindBlob(1, std::move(serialized_keys));
insert_keys_statement.BindTime(2, expiration);
return insert_keys_statement.Run();
}
std::pair<base::Time, std::string> DoGetBiddingAndAuctionServerKeys(
sql::Database& db,
const url::Origin& coordinator) {
sql::Statement keys_statement(
db.GetCachedStatement(SQL_FROM_HERE,
"SELECT expiration, keys "
"FROM bidding_and_auction_server_keys "
"WHERE coordinator = ? AND expiration>?"));
if (!keys_statement.is_valid()) {
DLOG(ERROR)
<< "DoGetBiddingAndAuctionServerKeys SQL statement did not compile.";
return {};
}
keys_statement.Reset(true);
keys_statement.BindString(0, Serialize(coordinator));
keys_statement.BindTime(1, base::Time::Now());
if (keys_statement.Step()) {
base::Time expiration = keys_statement.ColumnTime(0);
std::string key_blob = keys_statement.ColumnString(1);
return {expiration, std::move(key_blob)};
}
return {base::Time::Min(), {}};
}
bool DoWriteHashedKAnonymityKeysToCache(
sql::Database& db,
const std::vector<std::string>& positive_hashed_keys,
const std::vector<std::string>& negative_hashed_keys,
base::Time fetch_time) {
sql::Statement insert_keys_statement(db.GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR REPLACE INTO "
"cached_k_anonymity_hashes(key_hash,is_k_anon,fetch_time) "
"VALUES (?,?,?)"));
if (!insert_keys_statement.is_valid()) {
DLOG(ERROR)
<< "DoWriteHashedKAnonymityKeysToCache SQL statement did not compile.";
return {};
}
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
for (const std::string& key : positive_hashed_keys) {
insert_keys_statement.Reset(true);
insert_keys_statement.BindBlob(0, key);
insert_keys_statement.BindBool(1, true);
insert_keys_statement.BindTime(2, fetch_time);
if (!insert_keys_statement.Run()) {
return false;
}
}
for (const std::string& key : negative_hashed_keys) {
insert_keys_statement.Reset(true);
insert_keys_statement.BindBlob(0, key);
insert_keys_statement.BindBool(1, false);
insert_keys_statement.BindTime(2, fetch_time);
if (!insert_keys_statement.Run()) {
return false;
}
}
return transaction.Commit();
}
bool AppendKAnonCacheQueryStatement(
sql::Database& db,
sql::StatementID statement_id,
size_t number_of_keys,
std::vector<std::pair<size_t, std::unique_ptr<sql::Statement>>>* levels) {
if (number_of_keys < 1) {
return false;
}
std::string query_string =
"SELECT key_hash, is_k_anon "
"FROM cached_k_anonymity_hashes "
"WHERE fetch_time >= ? AND key_hash ";
if (number_of_keys > 1) {
base::StrAppend(&query_string, {"IN (?"});
base::StrAppend(&query_string,
std::vector<std::string>(number_of_keys - 1, ",?"));
base::StrAppend(&query_string, {")"});
} else {
base::StrAppend(&query_string, {"= ?"});
}
std::unique_ptr<sql::Statement> statement = std::make_unique<sql::Statement>(
db.GetCachedStatement(statement_id, query_string));
if (!statement->is_valid()) {
DLOG(ERROR) << "CreateKAnonCacheQueryStatement "
<< "for number_of_keys=" << number_of_keys
<< " SQL statement did not compile.";
return false;
}
levels->emplace_back(number_of_keys, std::move(statement));
return true;
}
InterestGroupStorage::KAnonymityCacheResponse
DoLoadPositiveHashedKAnonymityKeysFromCache(
sql::Database& db,
const std::vector<std::string>& keys,
const base::Time fetch_time) {
const base::TimeDelta ttl = features::kFledgeCacheKAnonHashedKeysTtl.Get();
const base::Time min_valid_time = fetch_time - ttl;
// These should always be in descending order of number_of_keys, and the
// last one should always be number_of_keys = 1.
std::vector<std::pair<size_t, std::unique_ptr<sql::Statement>>> levels;
if (!AppendKAnonCacheQueryStatement(db, SQL_FROM_HERE, 100, &levels)) {
return {{}, keys};
}
if (!AppendKAnonCacheQueryStatement(db, SQL_FROM_HERE, 10, &levels)) {
return {{}, keys};
}
if (!AppendKAnonCacheQueryStatement(db, SQL_FROM_HERE, 1, &levels)) {
return {{}, keys};
}
base::flat_map<std::string, bool> lookup_results;
size_t key_index = 0;
while (key_index < keys.size()) {
for (auto& level : levels) {
const size_t number_of_keys = level.first;
if (keys.size() - key_index >= number_of_keys) {
sql::Statement* statement = level.second.get();
statement->Reset(true);
statement->BindTime(0, min_valid_time);
for (size_t param_index = 1; param_index <= number_of_keys;
++param_index) {
statement->BindBlob(param_index, keys[key_index++]);
}
while (statement->Step()) {
lookup_results.emplace(statement->ColumnString(0),
statement->ColumnBool(1));
}
if (!statement->Succeeded()) {
DLOG(ERROR) << "DoLoadPositiveHashedKAnonymityKeysFromCache "
<< "Encountered error while stepping through results";
return {{}, keys};
}
break;
}
}
}
std::vector<std::string> positive_hashed_keys;
std::vector<std::string> keys_to_fetch;
for (const std::string& key : keys) {
auto is_k_anon_iterator = lookup_results.find(key);
if (is_k_anon_iterator == lookup_results.end()) {
keys_to_fetch.emplace_back(key);
} else if (is_k_anon_iterator->second) {
positive_hashed_keys.emplace_back(key);
}
}
base::UmaHistogramPercentage(
"Storage.InterestGroup.KAnonymityKeysCacheHitRate",
(keys.size() - keys_to_fetch.size()) * 100 / keys.size());
return {positive_hashed_keys, keys_to_fetch};
}
bool DoPerformDatabaseMaintenance(sql::Database& db,
base::Time now,
size_t max_owners,
size_t max_owner_storage_size,
size_t max_owner_regular_interest_groups,
size_t max_owner_negative_interest_groups) {
SCOPED_UMA_HISTOGRAM_TIMER_MICROS("Storage.InterestGroup.DBMaintenanceTime");
sql::Transaction transaction(&db);
if (!transaction.Begin()) {
return false;
}
if (!ClearExcessInterestGroups(db, max_owners,
max_owner_regular_interest_groups,
max_owner_negative_interest_groups)) {
return false;
}
if (!ClearExpiredInterestGroups(db, now)) {
return false;
}
if (!ClearExcessiveStorage(db, max_owner_storage_size)) {
return false;
}
if (!DeleteOldJoins(db, now - blink::MaxInterestGroupLifetimeForMetadata())) {
return false;
}
if (!DeleteOldBids(db, now - blink::MaxInterestGroupLifetimeForMetadata())) {
return false;
}
if (!DeleteOldWins(db, now - blink::MaxInterestGroupLifetimeForMetadata())) {
return false;
}
if (!ClearExpiredKAnon(
db, now - InterestGroupStorage::kAdditionalKAnonStoragePeriod)) {
return false;
}
if (!DeleteExpiredDebugReportCooldown(db, now)) {
return false;
}
if (!ClearExpiredBiddingAndAuctionKeys(db, now)) {
return false;
}
if (!ClearExpiredCachedKAnonymityHashes(db, now)) {
return false;
}
if (!CompactClickiness(db, now)) {
return false;
}
return transaction.Commit();
}
base::FilePath DBPath(const base::FilePath& base) {
if (base.empty()) {
return base;
}
return base.Append(kDatabasePath);
}
sql::DatabaseOptions GetDatabaseOptions() {
return sql::DatabaseOptions().set_wal_mode(base::FeatureList::IsEnabled(
features::kFledgeEnableWALForInterestGroupStorage));
}
void ReportCreateSchemaResult(
bool create_schema_result,
sql::RazeIfIncompatibleResult raze_if_incompatible_result,
bool missing_metatable_razed) {
if (create_schema_result) {
if (raze_if_incompatible_result ==
sql::RazeIfIncompatibleResult::kRazedSuccessfully) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::
kSuccessCreateSchemaAfterIncompatibleRaze);
} else if (missing_metatable_razed) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::
kSuccessCreateSchemaAfterNoMetaTableRaze);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kSuccessCreateSchema);
}
} else {
if (raze_if_incompatible_result ==
sql::RazeIfIncompatibleResult::kRazedSuccessfully) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::
kFailedCreateSchemaAfterIncompatibleRaze);
} else if (missing_metatable_razed) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::
kFailedCreateSchemaAfterNoMetaTableRaze);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kFailedCreateSchema);
}
}
}
void ReportUpgradeDBResult(bool upgrade_succeeded, int db_version) {
if (upgrade_succeeded) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kSuccessUpgraded);
static_assert(kCurrentVersionNumber <= 100,
"UmaHistogramExactLinear() only supports 100 buckets -- a "
"new histogram is needed for larger versions.");
base::UmaHistogramExactLinear(
"Storage.InterestGroup.UpgradeSucceededStartVersion", db_version,
/*exclusive_max=*/101);
base::UmaHistogramExactLinear(
"Storage.InterestGroup.UpgradeSucceededEndVersion",
kCurrentVersionNumber,
/*exclusive_max=*/101);
} else {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kFailedUpgradeDB);
base::UmaHistogramExactLinear(
"Storage.InterestGroup.UpgradeFailedStartVersion", db_version,
/*exclusive_max=*/101);
base::UmaHistogramExactLinear(
"Storage.InterestGroup.UpgradeFailedEndVersion", kCurrentVersionNumber,
/*exclusive_max=*/101);
}
}
} // namespace
constexpr base::TimeDelta InterestGroupStorage::kMaintenanceInterval;
constexpr base::TimeDelta InterestGroupStorage::kDefaultIdlePeriod;
constexpr base::TimeDelta InterestGroupStorage::kUpdateSucceededBackoffPeriod;
constexpr base::TimeDelta InterestGroupStorage::kUpdateFailedBackoffPeriod;
InterestGroupStorage::InterestGroupStorage(const base::FilePath& path)
: InterestGroupStorage(std::move(path),
/*idle_period=*/kDefaultIdlePeriod) {}
InterestGroupStorage::InterestGroupStorage(const base::FilePath& path,
base::TimeDelta idle_period)
: path_to_database_(DBPath(path)),
max_owners_(network::features::kInterestGroupStorageMaxOwners.Get()),
max_owner_regular_interest_groups_(MaxOwnerRegularInterestGroups()),
max_owner_negative_interest_groups_(MaxOwnerNegativeInterestGroups()),
max_owner_storage_size_(MaxOwnerStorageSize()),
max_ops_before_maintenance_(
network::features::kInterestGroupStorageMaxOpsBeforeMaintenance
.Get()),
db_maintenance_timer_(FROM_HERE,
idle_period,
this,
&InterestGroupStorage::PerformDBMaintenance) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
InterestGroupStorage::~InterestGroupStorage() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
bool InterestGroupStorage::EnsureDBInitialized() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::Time now = base::Time::Now();
if (now > last_maintenance_time_ + kMaintenanceInterval) {
// Schedule maintenance for next idle period. If maintenance already
// scheduled this delays it further (we're not idle).
db_maintenance_timer_.Reset();
}
// Force maintenance even if we're busy if the database may have changed too
// much.
if (ops_since_last_maintenance_++ > max_ops_before_maintenance_) {
PerformDBMaintenance();
}
last_access_time_ = now;
if (db_ && db_->is_open()) {
return true;
}
return InitializeDB();
}
bool InterestGroupStorage::InitializeDB() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
db_ = std::make_unique<sql::Database>(GetDatabaseOptions(),
sql::Database::Tag("InterestGroups"));
db_->set_error_callback(base::BindRepeating(
&InterestGroupStorage::DatabaseErrorCallback, base::Unretained(this)));
if (path_to_database_.empty()) {
if (!db_->OpenInMemory()) {
DLOG(ERROR) << "Failed to create in-memory interest group database: "
<< db_->GetErrorMessage();
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kFailedCreateInMemory);
return false;
}
} else {
const base::FilePath dir = path_to_database_.DirName();
if (!base::CreateDirectory(dir)) {
DLOG(ERROR) << "Failed to create directory for interest group database";
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kFailedCreateDirectory);
return false;
}
if (db_->Open(path_to_database_) == false) {
DLOG(ERROR) << "Failed to open interest group database: "
<< db_->GetErrorMessage();
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kFailedCreateFile);
return false;
}
}
if (!InitializeSchema()) {
db_->Close();
return false;
}
DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
DCHECK(db_->DoesTableExist("interest_groups"));
DCHECK(db_->DoesTableExist("join_history"));
DCHECK(db_->DoesTableExist("bid_history"));
DCHECK(db_->DoesTableExist("win_history"));
DCHECK(db_->DoesTableExist("joined_k_anon"));
DCHECK(db_->DoesTableExist("lockout_debugging_only_report"));
DCHECK(db_->DoesTableExist("cooldown_debugging_only_report"));
return true;
}
bool InterestGroupStorage::InitializeSchema() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!db_) {
return false;
}
sql::RazeIfIncompatibleResult raze_if_incompatible_result =
sql::MetaTable::RazeIfIncompatible(
db_.get(), /*lowest_supported_version=*/kDeprecatedVersionNumber + 1,
kCurrentVersionNumber);
if (raze_if_incompatible_result == sql::RazeIfIncompatibleResult::kFailed) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kFailedToRazeIncompatible);
return false;
}
sql::MetaTable meta_table;
bool missing_metatable_razed = false;
bool has_metatable = meta_table.DoesTableExist(db_.get());
if (!has_metatable && db_->DoesTableExist("interest_groups")) {
// Existing DB with no meta table. We have no idea what version the schema
// is so we should remove it and start fresh.
missing_metatable_razed = true;
// If the incompatible version raze happened and succeeded, it should have
// removed the interest_groups table.
CHECK_NE(raze_if_incompatible_result,
sql::RazeIfIncompatibleResult::kRazedSuccessfully);
if (!db_->Raze()) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kFailedToRazeNoMetaTable);
return false;
}
}
const bool new_db = !has_metatable;
if (!meta_table.Init(db_.get(), kCurrentVersionNumber,
kCompatibleVersionNumber)) {
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kFailedMetaTableInit);
return false;
}
if (new_db) {
bool create_schema_result = CreateCurrentSchema(*db_);
ReportCreateSchemaResult(
/*create_schema_result=*/create_schema_result,
/*raze_if_incompatible_result=*/raze_if_incompatible_result,
/*missing_metatable_razed=*/missing_metatable_razed);
return create_schema_result;
}
const int db_version = meta_table.GetVersionNumber();
if (db_version >= kCurrentVersionNumber) {
// Getting past RazeIfIncompatible implies that
// kCurrentVersionNumber >= meta_table.GetCompatibleVersionNumber
// So DB is either the current database version or a future version that is
// back-compatible with this version of Chrome.
base::UmaHistogramEnumeration(
"Storage.InterestGroup.InitializationResult",
InterestGroupStorageInitializationResult::kSuccessAlreadyCurrent);
return true;
}
// Older versions - should be migrated.
// db_version < kCurrentVersionNumber
// db_version > kDeprecatedVersionNumber
bool upgrade_succeeded = UpgradeDB(*db_, db_version, meta_table, PassKey());
ReportUpgradeDBResult(/*upgrade_succeeded=*/upgrade_succeeded,
/*db_version=*/db_version);
return upgrade_succeeded;
}
std::optional<InterestGroupKanonUpdateParameter>
InterestGroupStorage::JoinInterestGroup(const blink::InterestGroup& group,
const GURL& main_frame_joining_url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return std::nullopt;
}
base::Time now = base::Time::Now();
std::optional<InterestGroupKanonUpdateParameter> kanon_update =
DoJoinInterestGroup(*db_, PassKey(), group, main_frame_joining_url,
/*exact_join_time=*/now,
/*last_updated=*/now,
/*next_update_after=*/base::Time::Min());
if (!kanon_update) {
DLOG(ERROR) << "Could not join interest group: " << db_->GetErrorMessage();
}
return kanon_update;
}
void InterestGroupStorage::LeaveInterestGroup(
const blink::InterestGroupKey& group_key,
const url::Origin& main_frame) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
StorageInterestGroup old_group;
if (DoLoadInterestGroup(*db_, PassKey(), group_key, old_group) &&
old_group.interest_group.execution_mode ==
blink::InterestGroup::ExecutionMode::kGroupedByOriginMode &&
main_frame != old_group.joining_origin) {
// Clear all interest groups with same owner and mode GroupedByOriginMode
// and same old_group.joining_origin.
if (!DoClearClusteredBiddingGroups(*db_, group_key.owner,
old_group.joining_origin)) {
DLOG(ERROR) << "Could not leave interest group: "
<< db_->GetErrorMessage();
}
return;
}
if (!DoRemoveInterestGroup(*db_, group_key)) {
DLOG(ERROR) << "Could not leave interest group: " << db_->GetErrorMessage();
}
}
std::vector<std::string> InterestGroupStorage::ClearOriginJoinedInterestGroups(
const url::Origin& owner,
const std::set<std::string>& interest_groups_to_keep,
const url::Origin& main_frame_origin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return std::vector<std::string>();
}
std::optional<std::vector<std::string>> left_interest_groups =
DoClearOriginJoinedInterestGroups(*db_, owner, interest_groups_to_keep,
main_frame_origin);
if (!left_interest_groups) {
DLOG(ERROR) << "Could not leave interest group: " << db_->GetErrorMessage();
return std::vector<std::string>();
}
return std::move(left_interest_groups.value());
}
std::optional<DebugReportLockoutAndCooldowns>
InterestGroupStorage::GetDebugReportLockoutAndCooldowns(
const base::flat_set<url::Origin>& origins) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return std::nullopt;
}
DebugReportLockoutAndCooldowns debug_report_lockout_and_cooldowns;
// Ignore lockout and cooldowns whose start time is before
// kFledgeEnableFilteringDebugReportStartingFrom.
std::optional<base::Time> ignore_before = GetSampleDebugReportStartingFrom();
debug_report_lockout_and_cooldowns.lockout =
DoGetDebugReportLockout(*db_, ignore_before);
DoGetDebugReportCooldowns(*db_, origins, ignore_before,
debug_report_lockout_and_cooldowns);
return debug_report_lockout_and_cooldowns;
}
std::optional<DebugReportLockoutAndCooldowns>
InterestGroupStorage::GetDebugReportLockoutAndAllCooldowns() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return std::nullopt;
}
DebugReportLockoutAndCooldowns debug_report_lockout_and_cooldowns;
// Ignore lockout and cooldowns whose start time is before
// kFledgeEnableFilteringDebugReportStartingFrom.
std::optional<base::Time> ignore_before = GetSampleDebugReportStartingFrom();
debug_report_lockout_and_cooldowns.lockout =
DoGetDebugReportLockout(*db_, ignore_before);
DoGetAllDebugReportCooldowns(*db_, ignore_before,
debug_report_lockout_and_cooldowns);
return debug_report_lockout_and_cooldowns;
}
std::optional<InterestGroupKanonUpdateParameter>
InterestGroupStorage::UpdateInterestGroup(
const blink::InterestGroupKey& group_key,
InterestGroupUpdate update) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return std::nullopt;
}
std::optional<InterestGroupKanonUpdateParameter> kanon_update =
DoUpdateInterestGroup(*db_, PassKey(), group_key, update,
base::Time::Now());
if (!kanon_update) {
DLOG(ERROR) << "Could not update interest group: "
<< db_->GetErrorMessage();
}
return kanon_update;
}
void InterestGroupStorage::AllowUpdateIfOlderThan(
blink::InterestGroupKey group_key,
base::TimeDelta update_if_older_than) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
bool success = DoAllowUpdateIfOlderThan(
*db_, PassKey(), group_key, update_if_older_than, base::Time::Now());
if (!success) {
DLOG(ERROR) << "Could not process update_if_older_than: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::ReportUpdateFailed(
const blink::InterestGroupKey& group_key,
bool parse_failure) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
NOTREACHED(); // We already fetched interest groups to update...
}
if (!DoReportUpdateFailed(*db_, group_key, parse_failure,
base::Time::Now())) {
DLOG(ERROR) << "Couldn't update next_update_after: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::RecordInterestGroupBids(
const blink::InterestGroupSet& group_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoRecordInterestGroupBids(*db_, group_keys, base::Time::Now())) {
DLOG(ERROR) << "Could not record win for interest group: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::RecordInterestGroupWin(
const blink::InterestGroupKey& group_key,
const std::string& ad_json) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoRecordInterestGroupWin(*db_, group_key, ad_json, base::Time::Now())) {
DLOG(ERROR) << "Could not record bid for interest group: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::RecordDebugReportLockout(base::Time starting_time,
base::TimeDelta duration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoRecordDebugReportLockout(*db_, starting_time, duration)) {
DLOG(ERROR) << "Could not record debugging only report lockout: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::RecordDebugReportCooldown(
const url::Origin& origin,
base::Time cooldown_start,
DebugReportCooldownType cooldown_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoRecordDebugReportCooldown(*db_, origin, cooldown_start,
cooldown_type)) {
DLOG(ERROR) << "Could not record debugging only report cooldown: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::DeleteAllDebugReportCooldowns() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoDeleteAllDebugReportCooldowns(*db_)) {
DLOG(ERROR) << "Could not delete all debug report cooldowns: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::UpdateKAnonymity(
const blink::InterestGroupKey& interest_group_key,
const std::vector<std::string>& positive_hashed_keys,
const base::Time update_time,
bool replace_existing_values) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoUpdateKAnonymity(*db_, interest_group_key, positive_hashed_keys,
update_time, replace_existing_values,
base::Time::Now())) {
DLOG(ERROR) << "Could not update k-anonymity: " << db_->GetErrorMessage();
}
}
std::optional<base::Time> InterestGroupStorage::GetLastKAnonymityReported(
const std::string& hashed_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
return DoGetLastKAnonymityReported(*db_, hashed_key);
}
void InterestGroupStorage::UpdateLastKAnonymityReported(
const std::string& hashed_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
DoUpdateLastKAnonymityReported(*db_, hashed_key, base::Time::Now());
}
void InterestGroupStorage::RecordViewClick(
const network::AdAuctionEventRecord& record) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
DoRecordViewClick(*db_, record, base::Time::Now());
}
std::optional<StorageInterestGroup> InterestGroupStorage::GetInterestGroup(
const blink::InterestGroupKey& group_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return std::nullopt;
}
return DoGetStoredInterestGroup(*db_, PassKey(), group_key,
base::Time::Now());
}
std::vector<url::Origin> InterestGroupStorage::GetAllInterestGroupOwners() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
std::optional<std::vector<url::Origin>> maybe_result =
DoGetAllInterestGroupOwners(*db_, base::Time::Now());
if (!maybe_result) {
return {};
}
return std::move(maybe_result.value());
}
std::vector<StorageInterestGroup>
InterestGroupStorage::GetInterestGroupsForOwner(const url::Origin& owner) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
std::optional<std::vector<StorageInterestGroup>> maybe_result =
DoGetInterestGroupsForOwner(*db_, PassKey(), owner, base::Time::Now());
if (!maybe_result) {
return {};
}
base::UmaHistogramCounts1000("Storage.InterestGroup.PerSiteCount",
maybe_result->size());
return std::move(maybe_result.value());
}
std::vector<InterestGroupUpdateParameter>
InterestGroupStorage::GetInterestGroupsForUpdate(const url::Origin& owner,
size_t groups_limit) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
std::optional<std::vector<InterestGroupUpdateParameter>> maybe_result =
DoGetInterestGroupsForUpdate(*db_, owner, base::Time::Now(),
groups_limit);
if (!maybe_result) {
return {};
}
return std::move(maybe_result.value());
}
std::vector<url::Origin>
InterestGroupStorage::GetAllInterestGroupJoiningOrigins() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
std::optional<std::vector<url::Origin>> maybe_result =
DoGetAllInterestGroupJoiningOrigins(*db_, base::Time::Now());
if (!maybe_result) {
return {};
}
return std::move(maybe_result.value());
}
std::vector<std::pair<url::Origin, url::Origin>>
InterestGroupStorage::GetAllInterestGroupOwnerJoinerPairs() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
std::optional<std::vector<std::pair<url::Origin, url::Origin>>> maybe_result =
DoGetAllInterestGroupOwnerJoinerPairs(*db_, base::Time::Now());
if (!maybe_result) {
return {};
}
return std::move(maybe_result.value());
}
void InterestGroupStorage::SetDebugReportLockoutUntilIGExpires() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoSetDebugReportLockoutUntilIGExpires(*db_, base::Time::Now())) {
DLOG(ERROR)
<< "Could not set debug report lockout until interest groups expire: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::RemoveInterestGroupsMatchingOwnerAndJoiner(
const url::Origin& owner,
const url::Origin& joining_origin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoRemoveInterestGroupsMatchingOwnerAndJoiner(*db_, owner, joining_origin,
base::Time::Now())) {
DLOG(ERROR)
<< "Could not remove interest groups matching owner and joiner: "
<< db_->GetErrorMessage();
}
return;
}
void InterestGroupStorage::DeleteInterestGroupData(
StoragePartition::StorageKeyMatcherFunction storage_key_matcher,
bool user_initiated_deletion) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoDeleteInterestGroupData(*db_, std::move(storage_key_matcher),
user_initiated_deletion)) {
DLOG(ERROR) << "Could not delete interest group data: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::DeleteAllInterestGroupData() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
db_->RazeAndPoison();
db_.reset();
}
void InterestGroupStorage::SetInterestGroupPriority(
const blink::InterestGroupKey& group_key,
double priority) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
if (!DoSetInterestGroupPriority(*db_, group_key, priority)) {
DLOG(ERROR) << "Could not set interest group priority: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::UpdateInterestGroupPriorityOverrides(
const blink::InterestGroupKey& group_key,
base::flat_map<std::string,
auction_worklet::mojom::PrioritySignalsDoublePtr>
update_priority_signals_overrides) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
StorageInterestGroup group;
if (!DoLoadInterestGroup(*db_, PassKey(), group_key, group)) {
return;
}
MergePrioritySignalsOverrides(
update_priority_signals_overrides,
group.interest_group.priority_signals_overrides);
if (!group.interest_group.IsValid()) {
// TODO(mmenke): Report errors to devtools.
return;
}
if (!DoSetInterestGroupPrioritySignalsOverrides(
*db_, group_key, group.interest_group.priority_signals_overrides)) {
DLOG(ERROR) << "Could not set interest group priority signals overrides: "
<< db_->GetErrorMessage();
}
}
void InterestGroupStorage::PerformDBMaintenance() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
last_maintenance_time_ = base::Time::Now();
ops_since_last_maintenance_ = 0;
std::optional<int64_t> db_size = base::GetFileSize(path_to_database_);
if (db_size.has_value()) {
UMA_HISTOGRAM_MEMORY_KB("Storage.InterestGroup.DBSize",
db_size.value() / 1024);
}
if (EnsureDBInitialized()) {
DoPerformDatabaseMaintenance(
*db_, last_maintenance_time_, /*max_owners=*/max_owners_,
/*max_owner_storage_size=*/max_owner_storage_size_,
/*max_owner_regular_interest_groups=*/
max_owner_regular_interest_groups_,
/*max_owner_negative_interest_groups=*/
max_owner_negative_interest_groups_);
}
}
std::vector<StorageInterestGroup>
InterestGroupStorage::GetAllInterestGroupsUnfilteredForTesting() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {};
}
const base::Time distant_past = base::Time::Min();
std::vector<StorageInterestGroup> result;
std::optional<std::vector<url::Origin>> maybe_owners =
DoGetAllInterestGroupOwners(*db_, distant_past);
if (!maybe_owners) {
return {};
}
for (const auto& owner : *maybe_owners) {
std::optional<std::vector<StorageInterestGroup>> maybe_owner_results =
DoGetInterestGroupsForOwner(*db_, PassKey(), owner, distant_past);
DCHECK(maybe_owner_results) << owner;
std::move(maybe_owner_results->begin(), maybe_owner_results->end(),
std::back_inserter(result));
}
return result;
}
void InterestGroupStorage::SetBiddingAndAuctionServerKeys(
const url::Origin& coordinator,
std::string serialized_keys,
base::Time expiration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return;
}
DoSetBiddingAndAuctionServerKeys(*db_, coordinator,
std::move(serialized_keys), expiration);
}
std::pair<base::Time, std::string>
InterestGroupStorage::GetBiddingAndAuctionServerKeys(
const url::Origin& coordinator) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {base::Time::Min(), {}};
}
return DoGetBiddingAndAuctionServerKeys(*db_, coordinator);
}
bool InterestGroupStorage::WriteHashedKAnonymityKeysToCache(
const std::vector<std::string>& positive_hashed_keys,
const std::vector<std::string>& negative_hashed_keys,
base::Time fetch_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return false;
}
return DoWriteHashedKAnonymityKeysToCache(*db_, positive_hashed_keys,
negative_hashed_keys, fetch_time);
}
InterestGroupStorage::KAnonymityCacheResponse::KAnonymityCacheResponse(
std::vector<std::string> _positive_hashed_keys_from_cache,
std::vector<std::string> _ids_to_query_from_server)
: positive_hashed_keys_from_cache(
std::move(_positive_hashed_keys_from_cache)),
ids_to_query_from_server(std::move(_ids_to_query_from_server)) {}
InterestGroupStorage::KAnonymityCacheResponse::KAnonymityCacheResponse(
const KAnonymityCacheResponse& other) = default;
InterestGroupStorage::KAnonymityCacheResponse&
InterestGroupStorage::KAnonymityCacheResponse::operator=(
const KAnonymityCacheResponse& other) = default;
InterestGroupStorage::KAnonymityCacheResponse::~KAnonymityCacheResponse() =
default;
InterestGroupStorage::KAnonymityCacheResponse
InterestGroupStorage::LoadPositiveHashedKAnonymityKeysFromCache(
const std::vector<std::string>& keys,
base::Time check_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return {{}, keys};
}
return DoLoadPositiveHashedKAnonymityKeysFromCache(*db_, keys, check_time);
}
// static
size_t InterestGroupStorage::MaxOwnerRegularInterestGroups() {
return network::features::kInterestGroupStorageMaxGroupsPerOwner.Get();
}
// static
size_t InterestGroupStorage::MaxOwnerNegativeInterestGroups() {
return network::features::kInterestGroupStorageMaxNegativeGroupsPerOwner
.Get();
}
// static
size_t InterestGroupStorage::MaxOwnerStorageSize() {
return network::features::kInterestGroupStorageMaxStoragePerOwner.Get();
}
base::Time InterestGroupStorage::GetLastMaintenanceTimeForTesting() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return last_maintenance_time_;
}
/* static */ int InterestGroupStorage::GetCurrentVersionNumberForTesting() {
return kCurrentVersionNumber;
}
/*static */ std::unique_ptr<InterestGroupStorage>
InterestGroupStorage::CreateWithIdlePeriodForTesting(
const base::FilePath& path,
base::TimeDelta idle_period) {
return base::WrapUnique(new InterestGroupStorage(path, idle_period));
}
void InterestGroupStorage::ResetIdleTimerForTesting() {
EnsureDBInitialized();
}
// static
std::optional<InterestGroupStorage::ClickinessCompactionRawEvents>
InterestGroupStorage::ComputeCompactClickinessForTesting(
base::Time now,
const InterestGroupStorage::ClickinessCompactionRawEvents& raw) {
return ComputeCompactClickiness(now, raw);
}
std::optional<bool>
InterestGroupStorage::CheckViewClickCountsForProviderAndEligibleInDbForTesting(
const url::Origin& provider_origin,
const url::Origin& eligible_origin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!EnsureDBInitialized()) {
return std::nullopt;
}
auto status = DoGetViewClickCountsForProviderAndEligible(
*db_, provider_origin, eligible_origin);
if (status.has_value()) {
return true;
}
switch (status.error()) {
case MissingReason::kNotInDb:
return false;
case MissingReason::kDbError:
case MissingReason::kDecodeError:
return std::nullopt;
}
}
void InterestGroupStorage::DatabaseErrorCallback(int extended_error,
sql::Statement* stmt) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Only save the basic error code (not extended) to UMA.
base::UmaHistogramExactLinear("Storage.InterestGroup.DBErrors",
extended_error & 0xFF,
/*sqlite error max+1*/ SQLITE_WARNING + 1);
if (sql::IsErrorCatastrophic(extended_error)) {
// Normally this will poison the database, causing any subsequent operations
// to silently fail without any side effects. However, if RazeAndPoison() is
// called from the error callback in response to an error raised from within
// sql::Database::Open, opening the now-razed database will be retried.
db_->RazeAndPoison();
return;
}
// The default handling is to assert on debug and to ignore on release.
if (!sql::Database::IsExpectedSqliteError(extended_error)) {
DLOG(FATAL) << db_->GetErrorMessage();
}
}
} // namespace content