#include "content/browser/interest_group/bidding_and_auction_serializer.h"
#include <stdint.h>
#include <algorithm>
#include <array>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/containers/span_writer.h"
#include "base/feature_list.h"
#include "base/json/json_reader.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/checked_math.h"
#include "base/rand_util.h"
#include "base/time/time.h"
#include "components/cbor/diagnostic_writer.h"
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
#include "content/browser/interest_group/for_debugging_only_report_util.h"
#include "content/browser/interest_group/interest_group_auction.h"
#include "content/browser/interest_group/interest_group_caching_storage.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/storage_interest_group.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "third_party/abseil-cpp/absl/numeric/bits.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/interest_group/interest_group.h"
#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
#include "third_party/zlib/google/compression_utils.h"
namespace content {
namespace {
const size_t kFramingHeaderSize = 5;
const size_t kOhttpEncIdSize = 7;
const size_t kOhttpSharedSecretSize = 48;
const size_t kOhttpHeaderSize = kOhttpEncIdSize + kOhttpSharedSecretSize;
const uint8_t kRequestVersion = 0;
const uint8_t kRequestVersionBitOffset = 5;
const uint8_t kGzipCompression = 2;
const uint8_t kCompressionBitOffset = 0;
const std::array<uint32_t, 7> kBinSizes = {
{0, 5 * 1024, 10 * 1024, 20 * 1024, 30 * 1024, 40 * 1024, 55 * 1024}};
struct ValueAndSize {
cbor::Value value;
base::CheckedNumeric<size_t> size;
};
struct ValueAndSizeAndPrevWinsSize {
cbor::Value value;
base::CheckedNumeric<size_t> size;
base::CheckedNumeric<size_t> prev_wins_array_size;
};
struct CompressedInterestGroups {
std::string data;
std::vector<std::string> group_names;
size_t uncompressed_size;
size_t num_groups;
base::flat_map<blink::InterestGroupKey, url::Origin> group_pagg_coordinators;
std::vector<size_t> prev_wins_array_sizes;
};
struct SerializedBiddersMap {
cbor::Value::MapValue bidders;
base::flat_map<url::Origin, std::vector<std::string>> group_names;
size_t num_groups;
base::CheckedNumeric<size_t> bidders_elements_size;
base::flat_map<blink::InterestGroupKey, url::Origin> group_pagg_coordinators;
};
bool KAnonIsEnabled() {
if (base::FeatureList::IsEnabled(
features::kCookieDeprecationFacilitatedTesting)) {
return false;
}
return base::FeatureList::IsEnabled(
blink::features::kFledgeConsiderKAnonymity) &&
base::FeatureList::IsEnabled(
blink::features::kFledgeEnforceKAnonymity);
}
constexpr std::size_t constexpr_strlen(const char* s) {
return std::char_traits<char>::length(s);
}
constexpr size_t LengthOfLength(uint64_t length) {
if (length < 24) {
return 0;
}
if (length <= 0xFF) {
return 1;
}
if (length <= 0xFFFF) {
return 2;
}
if (length <= 0xFFFFFFFF) {
return 4;
}
return 8;
}
size_t MaxLengthOfTaggedData(uint64_t length) {
DCHECK_GT(length, 0u);
size_t lol_x = 0;
if (length <= 23 + 1) {
lol_x = 0;
} else if (length <= 0xFF + 1 + 1) {
lol_x = 1;
} else if (length <= 0xFFFF + 1 + 2) {
lol_x = 2;
} else if (length <= static_cast<uint64_t>(0xFFFFFFFF) + 1 + 4) {
lol_x = 4;
} else {
lol_x = 8;
}
DCHECK_LE(LengthOfLength(length - 1 - lol_x), lol_x)
<< " with length = " << length;
return lol_x;
}
constexpr base::CheckedNumeric<size_t> TaggedStringLength(size_t length) {
return 1 + LengthOfLength(length) + length;
}
constexpr base::CheckedNumeric<size_t> TaggedUIntLength(uint64_t value) {
return 1 + LengthOfLength(value);
}
constexpr base::CheckedNumeric<size_t> TaggedSIntLength(int64_t value) {
if (value < 0) {
return TaggedUIntLength(-value - 1);
}
return TaggedUIntLength(value);
}
base::CheckedNumeric<size_t> TaggedArrayLength(
const cbor::Value::ArrayValue& array,
base::CheckedNumeric<size_t> elements_size) {
return 1 + LengthOfLength(array.size()) + elements_size;
}
base::CheckedNumeric<size_t> TaggedMapLength(
const cbor::Value::MapValue& map,
base::CheckedNumeric<size_t> elements_size) {
return 1 + LengthOfLength(2 * map.size()) + elements_size;
}
size_t GetFramingSize() {
return kFramingHeaderSize + kOhttpHeaderSize + 1;
}
ValueAndSize SerializeAds(const std::vector<blink::InterestGroup::Ad>& ads,
bool include_full_ads) {
cbor::Value::ArrayValue result;
base::CheckedNumeric<size_t> array_elements_size = 0;
for (const auto& ad : ads) {
if (include_full_ads) {
cbor::Value::MapValue obj;
base::CheckedNumeric<size_t> map_elements_size = 0;
obj[cbor::Value("renderURL")] = cbor::Value(ad.render_url());
map_elements_size += TaggedStringLength(constexpr_strlen("renderURL")) +
TaggedStringLength(ad.render_url().size());
if (ad.metadata) {
obj[cbor::Value("metadata")] = cbor::Value(ad.metadata.value());
map_elements_size += TaggedStringLength(constexpr_strlen("metadata")) +
TaggedStringLength(ad.metadata->size());
}
if (ad.size_group) {
obj[cbor::Value("sizeGroup")] = cbor::Value(ad.size_group.value());
map_elements_size += TaggedStringLength(constexpr_strlen("sizeGroup")) +
TaggedStringLength(ad.size_group->size());
}
if (ad.ad_render_id) {
obj[cbor::Value("adRenderId")] = cbor::Value(ad.ad_render_id.value());
map_elements_size +=
TaggedStringLength(constexpr_strlen("adRenderId")) +
TaggedStringLength(ad.ad_render_id->size());
}
array_elements_size += TaggedMapLength(obj, map_elements_size);
result.emplace_back(std::move(obj));
} else {
if (ad.ad_render_id) {
result.emplace_back(ad.ad_render_id.value());
array_elements_size += TaggedStringLength(ad.ad_render_id->size());
}
}
}
base::CheckedNumeric<size_t> total_size =
TaggedArrayLength(result, array_elements_size);
return {cbor::Value(std::move(result)), total_size};
}
std::pair<cbor::Value::ArrayValue, size_t> BuildClickinessArray(
const blink::mojom::ViewOrClickCounts& counts) {
cbor::Value::ArrayValue result;
base::CheckedNumeric<size_t> serialized_size = 1;
result.reserve(5);
result.emplace_back(counts.past_hour);
serialized_size += TaggedSIntLength(counts.past_hour);
result.emplace_back(counts.past_day);
serialized_size += TaggedSIntLength(counts.past_day);
result.emplace_back(counts.past_week);
serialized_size += TaggedSIntLength(counts.past_week);
result.emplace_back(counts.past_30_days);
serialized_size += TaggedSIntLength(counts.past_30_days);
result.emplace_back(counts.past_90_days);
serialized_size += TaggedSIntLength(counts.past_90_days);
return std::pair<cbor::Value::ArrayValue, size_t>(
std::move(result), serialized_size.ValueOrDie());
}
ValueAndSizeAndPrevWinsSize SerializeInterestGroup(
base::Time start_time,
const SingleStorageInterestGroup& group,
bool in_cooldown_or_lockout) {
cbor::Value::MapValue group_obj;
base::CheckedNumeric<size_t> group_elements_size = 0;
group_obj[cbor::Value("name")] = cbor::Value(group->interest_group.name);
group_elements_size += TaggedStringLength(constexpr_strlen("name")) +
TaggedStringLength(group->interest_group.name.size());
if (group->interest_group.trusted_bidding_signals_keys) {
cbor::Value::ArrayValue bidding_signal_keys;
base::CheckedNumeric<size_t> array_elements_size = 0;
bidding_signal_keys.reserve(
group->interest_group.trusted_bidding_signals_keys->size());
for (const auto& key :
*group->interest_group.trusted_bidding_signals_keys) {
bidding_signal_keys.emplace_back(key);
array_elements_size += TaggedStringLength(key.size());
}
group_elements_size +=
TaggedStringLength(constexpr_strlen("biddingSignalsKeys")) +
TaggedArrayLength(bidding_signal_keys, array_elements_size);
group_obj[cbor::Value("biddingSignalsKeys")] =
cbor::Value(std::move(bidding_signal_keys));
}
if (!group->interest_group.auction_server_request_flags.Has(
blink::AuctionServerRequestFlagsEnum::kOmitUserBiddingSignals) &&
group->interest_group.user_bidding_signals) {
group_obj[cbor::Value("userBiddingSignals")] =
cbor::Value(*group->interest_group.user_bidding_signals);
group_elements_size +=
TaggedStringLength(constexpr_strlen("userBiddingSignals")) +
TaggedStringLength(group->interest_group.user_bidding_signals->size());
}
if (!group->interest_group.auction_server_request_flags.Has(
blink::AuctionServerRequestFlagsEnum::kOmitAds)) {
if (group->interest_group.ads) {
ValueAndSize ads = SerializeAds(
*group->interest_group.ads,
group->interest_group.auction_server_request_flags.Has(
blink::AuctionServerRequestFlagsEnum::kIncludeFullAds));
group_obj[cbor::Value("ads")] = std::move(ads.value);
group_elements_size +=
TaggedStringLength(constexpr_strlen("ads")) + ads.size;
}
if (group->interest_group.ad_components) {
ValueAndSize components = SerializeAds(
*group->interest_group.ad_components,
group->interest_group.auction_server_request_flags.Has(
blink::AuctionServerRequestFlagsEnum::kIncludeFullAds));
group_obj[cbor::Value("components")] = std::move(components.value);
group_elements_size +=
TaggedStringLength(constexpr_strlen("components")) + components.size;
}
}
cbor::Value::MapValue browser_signals;
base::CheckedNumeric<size_t> browser_signals_elements_size = 0;
browser_signals[cbor::Value("bidCount")] =
cbor::Value(group->bidding_browser_signals->bid_count);
browser_signals_elements_size +=
TaggedStringLength(constexpr_strlen("bidCount")) +
TaggedSIntLength(group->bidding_browser_signals->bid_count);
browser_signals[cbor::Value("joinCount")] =
cbor::Value(group->bidding_browser_signals->join_count);
browser_signals_elements_size +=
TaggedStringLength(constexpr_strlen("joinCount")) +
TaggedSIntLength(group->bidding_browser_signals->join_count);
int64_t recency = (start_time - group->join_time).InMilliseconds();
if (recency < 0) {
recency = 0;
}
browser_signals[cbor::Value("recencyMs")] = cbor::Value(recency);
browser_signals_elements_size +=
TaggedStringLength(constexpr_strlen("recencyMs")) +
TaggedSIntLength(recency);
if (group->bidding_browser_signals->view_and_click_counts &&
base::FeatureList::IsEnabled(features::kEnableBandAClickiness)) {
if (group->bidding_browser_signals->view_and_click_counts->view_counts) {
std::pair<cbor::Value::ArrayValue, size_t> views = BuildClickinessArray(
*group->bidding_browser_signals->view_and_click_counts->view_counts);
browser_signals[cbor::Value("viewCounts")] =
cbor::Value(std::move(views.first));
browser_signals_elements_size +=
TaggedStringLength(constexpr_strlen("viewCounts")) + views.second;
}
if (group->bidding_browser_signals->view_and_click_counts->click_counts) {
std::pair<cbor::Value::ArrayValue, size_t> clicks = BuildClickinessArray(
*group->bidding_browser_signals->view_and_click_counts->click_counts);
browser_signals[cbor::Value("clickCounts")] =
cbor::Value(std::move(clicks.first));
browser_signals_elements_size +=
TaggedStringLength(constexpr_strlen("clickCounts")) + clicks.second;
}
}
cbor::Value::ArrayValue prev_wins;
base::CheckedNumeric<size_t> prev_wins_elements_size = 0;
for (const auto& prev_win : group->bidding_browser_signals->prev_wins) {
cbor::Value::ArrayValue tuple;
base::CheckedNumeric<size_t> tuple_elements_size = 0;
int32_t prev_win_time = (start_time - prev_win->time).InSeconds();
if (prev_win_time < 0) {
prev_win_time = 0;
}
tuple_elements_size += TaggedSIntLength(prev_win_time);
tuple.emplace_back(prev_win_time);
std::optional<base::Value::Dict> ad = base::JSONReader::ReadDict(
prev_win->ad_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
if (!ad) {
continue;
}
if (group->interest_group.auction_server_request_flags.Has(
blink::AuctionServerRequestFlagsEnum::kIncludeFullAds)) {
cbor::Value::MapValue obj;
base::CheckedNumeric<size_t> obj_elements_size = 0;
for (const auto kv : *ad) {
switch (kv.second.type()) {
case base::Value::Type::BOOLEAN:
obj[cbor::Value(kv.first)] = cbor::Value(kv.second.GetBool());
obj_elements_size += TaggedStringLength(kv.first.size()) + 1;
break;
case base::Value::Type::INTEGER:
obj[cbor::Value(kv.first)] = cbor::Value(kv.second.GetInt());
obj_elements_size += TaggedStringLength(kv.first.size()) +
TaggedSIntLength(kv.second.GetInt());
break;
case base::Value::Type::STRING:
obj[cbor::Value(kv.first)] = cbor::Value(kv.second.GetString());
obj_elements_size +=
TaggedStringLength(kv.first.size()) +
TaggedStringLength(kv.second.GetString().size());
break;
default:
LOG(ERROR) << "Unsupported type in prevWins.ad for key '"
<< kv.first << "': " << kv.second.DebugString();
}
}
tuple_elements_size += TaggedMapLength(obj, obj_elements_size);
tuple.emplace_back(std::move(obj));
} else {
std::string* ad_render_id = ad->FindString("adRenderId");
if (ad_render_id) {
tuple.emplace_back(*ad_render_id);
tuple_elements_size += TaggedStringLength(ad_render_id->size());
} else {
tuple.emplace_back("");
tuple_elements_size += TaggedStringLength(0);
}
}
prev_wins_elements_size += TaggedArrayLength(tuple, tuple_elements_size);
prev_wins.emplace_back(std::move(tuple));
}
base::CheckedNumeric<size_t> prev_wins_array_size =
TaggedArrayLength(prev_wins, prev_wins_elements_size);
browser_signals_elements_size +=
TaggedStringLength(constexpr_strlen("prevWins")) + prev_wins_array_size;
browser_signals[cbor::Value("prevWins")] = cbor::Value(std::move(prev_wins));
group_elements_size +=
TaggedStringLength(constexpr_strlen("browserSignals")) +
TaggedMapLength(browser_signals, browser_signals_elements_size);
group_obj[cbor::Value("browserSignals")] =
cbor::Value(std::move(browser_signals));
if (base::FeatureList::IsEnabled(
features::kFledgeSendDebugReportCooldownsToBandA)) {
group_elements_size +=
TaggedStringLength(constexpr_strlen("inCooldownOrLockout")) + 1;
group_obj[cbor::Value("inCooldownOrLockout")] =
cbor::Value(in_cooldown_or_lockout);
}
base::CheckedNumeric<size_t> total_size =
TaggedMapLength(group_obj, group_elements_size);
return {cbor::Value(std::move(group_obj)), total_size, prev_wins_array_size};
}
CompressedInterestGroups CompressInterestGroups(
const url::Origin& owner,
const std::vector<SingleStorageInterestGroup>& groups,
bool in_cooldown_or_lockout,
base::Time start_time,
std::optional<uint32_t> target_uncompressed_size) {
CompressedInterestGroups result{{}, {}, 0, 0};
cbor::Value::ArrayValue groups_array;
base::CheckedNumeric<size_t> groups_elements_size = 0;
for (const SingleStorageInterestGroup& group : groups) {
ValueAndSizeAndPrevWinsSize serialized_group =
SerializeInterestGroup(start_time, group, in_cooldown_or_lockout);
if (serialized_group.prev_wins_array_size.IsValid()) {
result.prev_wins_array_sizes.push_back(static_cast<size_t>(
serialized_group.prev_wins_array_size.ValueOrDie()));
}
base::CheckedNumeric<size_t> uncompressed_size =
serialized_group.size + groups_elements_size;
if (!uncompressed_size.IsValid()) {
DLOG(ERROR) << "Invalid uncompressed size.";
return {};
}
if (target_uncompressed_size &&
uncompressed_size.ValueOrDie() > *target_uncompressed_size) {
break;
}
groups_array.emplace_back(std::move(serialized_group.value));
result.group_names.push_back(group->interest_group.name);
std::optional<url::Origin> maybe_coordinator =
group->interest_group.aggregation_coordinator_origin;
if (maybe_coordinator.has_value()) {
result.group_pagg_coordinators[blink::InterestGroupKey(
owner, group->interest_group.name)] = *maybe_coordinator;
}
groups_elements_size += serialized_group.size;
result.num_groups++;
}
if (groups_array.empty()) {
return result;
}
base::CheckedNumeric<size_t> inner_size =
TaggedArrayLength(groups_array, groups_elements_size);
cbor::Value groups_obj(std::move(groups_array));
std::optional<std::vector<uint8_t>> maybe_sub_message =
cbor::Writer::Write(groups_obj);
DCHECK(maybe_sub_message);
DCHECK_EQ(static_cast<size_t>(inner_size.ValueOrDie()),
maybe_sub_message->size());
std::string compressed_groups;
bool success =
compression::GzipCompress(maybe_sub_message.value(), &compressed_groups);
CHECK(success);
result.uncompressed_size += maybe_sub_message->size();
result.data = std::move(compressed_groups);
return result;
}
SerializedBiddersMap SerializeBidderGroupsWithConfig(
const std::vector<
std::pair<url::Origin, std::vector<SingleStorageInterestGroup>>>&
bidders_and_groups,
const blink::mojom::AuctionDataConfig& config,
bool debug_report_in_lockout,
const std::map<url::Origin, DebugReportCooldown>& debug_report_cooldown_map,
size_t total_size_before_groups,
base::Time start_time) {
BiddingAndAuctionSerializer::TargetSizeEstimator estimator(
total_size_before_groups, &config);
std::vector<CompressedInterestGroups> all_bidders_full_compressed_groups;
all_bidders_full_compressed_groups.reserve(bidders_and_groups.size());
for (size_t idx = 0; idx < bidders_and_groups.size(); ++idx) {
const auto& bidder_groups = bidders_and_groups[idx];
bool in_cooldown_or_lockout =
debug_report_in_lockout ||
IsInDebugReportCooldown(bidder_groups.first, debug_report_cooldown_map,
start_time);
all_bidders_full_compressed_groups.emplace_back(CompressInterestGroups(
bidder_groups.first, bidder_groups.second, in_cooldown_or_lockout,
start_time, std::nullopt));
estimator.UpdatePerBuyerMaxSize(
bidder_groups.first,
all_bidders_full_compressed_groups[idx].data.size());
}
SerializedBiddersMap result{{}, {}, 0, 0, {}};
result.bidders.reserve(bidders_and_groups.size());
result.group_names.reserve(bidders_and_groups.size());
for (size_t idx = 0; idx < bidders_and_groups.size(); ++idx) {
const auto& bidder_groups = bidders_and_groups[idx];
std::string bidder_origin = bidder_groups.first.Serialize();
std::optional<uint64_t> target_compressed_size =
estimator.EstimateTargetSize(bidder_groups.first,
result.bidders_elements_size);
if (target_compressed_size && target_compressed_size.value() <= 0) {
continue;
}
bool in_cooldown_or_lockout =
debug_report_in_lockout ||
IsInDebugReportCooldown(bidder_groups.first, debug_report_cooldown_map,
start_time);
CompressedInterestGroups compressed_groups =
std::move(all_bidders_full_compressed_groups[idx]);
if (target_compressed_size) {
int num_iterations = 0;
while (compressed_groups.data.size() > *target_compressed_size) {
num_iterations++;
if (num_iterations > 20) {
compressed_groups = {};
break;
}
size_t current_uncompressed_target_size =
(*target_compressed_size * compressed_groups.uncompressed_size) /
compressed_groups.data.size();
current_uncompressed_target_size =
(current_uncompressed_target_size * 15) / 16;
compressed_groups = CompressInterestGroups(
bidder_groups.first, bidder_groups.second, in_cooldown_or_lockout,
start_time, current_uncompressed_target_size);
}
base::UmaHistogramCounts100(
"Ads.InterestGroup.ServerAuction.Request.NumIterations",
num_iterations);
}
if (compressed_groups.num_groups == 0) {
continue;
}
for (size_t prev_wins_array_size :
compressed_groups.prev_wins_array_sizes) {
UMA_HISTOGRAM_COUNTS_10000(
"Ads.InterestGroup.ServerAuction.PrevWinsArraySize",
prev_wins_array_size);
}
result.num_groups += compressed_groups.num_groups;
result.bidders_elements_size +=
TaggedStringLength(bidder_origin.size()) +
TaggedStringLength(compressed_groups.data.size());
result.group_names.emplace(bidder_groups.first,
std::move(compressed_groups.group_names));
result.group_pagg_coordinators.insert(
std::make_move_iterator(
compressed_groups.group_pagg_coordinators.begin()),
std::make_move_iterator(
compressed_groups.group_pagg_coordinators.end()));
result.bidders[cbor::Value(bidder_origin)] = cbor::Value(
std::move(compressed_groups.data), cbor::Value::Type::BYTE_STRING);
}
return result;
}
}
BiddingAndAuctionData::BiddingAndAuctionData() = default;
BiddingAndAuctionData::BiddingAndAuctionData(BiddingAndAuctionData&& other) =
default;
BiddingAndAuctionData::~BiddingAndAuctionData() = default;
BiddingAndAuctionData& BiddingAndAuctionData::operator=(
BiddingAndAuctionData&& other) = default;
BiddingAndAuctionSerializer::TargetSizeEstimator::TargetSizeEstimator(
size_t total_size_before_groups,
const blink::mojom::AuctionDataConfig* config)
: total_size_before_groups_(total_size_before_groups), config_(config) {
DCHECK(config_);
DCHECK(config_->request_size || config_->per_buyer_configs.empty());
}
BiddingAndAuctionSerializer::TargetSizeEstimator::~TargetSizeEstimator() =
default;
void BiddingAndAuctionSerializer::TargetSizeEstimator::UpdatePerBuyerMaxSize(
const url::Origin& bidder,
size_t max_size) {
base::CheckedNumeric<size_t> overhead =
TaggedStringLength(bidder.Serialize().size());
overhead += 1 + LengthOfLength(max_size);
size_t new_size = (overhead + max_size).ValueOrDie();
per_buyer_size_[bidder] = new_size;
auto it = config_->per_buyer_configs.find(bidder);
if (it != config_->per_buyer_configs.end() &&
it->second->target_size.has_value()) {
per_buyer_total_allowed_size_ += it->second->target_size.value();
} else {
total_unsized_buyers_++;
}
}
std::optional<uint64_t>
BiddingAndAuctionSerializer::TargetSizeEstimator::EstimateTargetSize(
const url::Origin& bidder,
base::CheckedNumeric<size_t> bidders_elements_size) {
if (!config_->request_size) {
return std::nullopt;
}
base::CheckedNumeric<uint64_t> target_compressed_size;
base::CheckedNumeric<size_t> current_size =
total_size_before_groups_ + bidders_elements_size;
if (!current_size.IsValid() ||
current_size.ValueOrDie() >= *config_->request_size) {
return 0;
}
base::CheckedNumeric<size_t> remaining_size =
*config_->request_size - current_size;
DCHECK_LE(static_cast<uint64_t>(per_buyer_current_allowed_size_.ValueOrDie()),
static_cast<uint64_t>(per_buyer_total_allowed_size_.ValueOrDie()));
auto it = config_->per_buyer_configs.find(bidder);
if (it != config_->per_buyer_configs.end() &&
it->second->target_size.has_value()) {
size_t buyer_size = it->second->target_size.value();
if (total_unsized_buyers_ > 0) {
target_compressed_size = remaining_size.Min(buyer_size);
per_buyer_current_allowed_size_ += buyer_size;
} else {
if (!size_allocated_) {
UpdateSizedGroupSizes(remaining_size.ValueOrDie());
size_allocated_ = true;
}
base::CheckedNumeric<uint64_t> remaining_per_buyer_size =
per_buyer_total_allowed_size_ - per_buyer_current_allowed_size_;
if (per_buyer_size_[bidder] == 0) {
target_compressed_size = 0;
} else {
target_compressed_size =
(base::CheckedNumeric<uint64_t>(per_buyer_size_[bidder]) *
remaining_size) /
remaining_per_buyer_size;
per_buyer_current_allowed_size_ += per_buyer_size_[bidder];
}
}
} else {
DCHECK_EQ(
static_cast<uint64_t>(per_buyer_current_allowed_size_.ValueOrDie()),
static_cast<uint64_t>(per_buyer_total_allowed_size_.ValueOrDie()));
if (!unsized_buyer_size_) {
UpdateUnsizedGroupSizes(remaining_size.ValueOrDie());
}
if (per_buyer_size_[bidder] > unsized_buyer_size_.value()) {
DCHECK_GT(remaining_unallocated_unsized_buyers_, 0u);
DCHECK_GE(static_cast<size_t>(remaining_size.ValueOrDie()),
static_cast<size_t>(
remaining_allocated_unsized_buyer_size_.ValueOrDie()));
target_compressed_size = (base::CheckedNumeric<uint64_t>(remaining_size) -
remaining_allocated_unsized_buyer_size_) /
remaining_unallocated_unsized_buyers_;
remaining_unallocated_unsized_buyers_--;
} else {
target_compressed_size = remaining_size.Min(per_buyer_size_[bidder]);
remaining_allocated_unsized_buyer_size_ -= per_buyer_size_[bidder];
}
}
base::CheckedNumeric<size_t> bidder_origin_overhead =
TaggedStringLength(bidder.Serialize().size());
if (!bidder_origin_overhead.IsValid() ||
target_compressed_size.ValueOrDie() <=
bidder_origin_overhead.ValueOrDie()) {
return 0;
}
base::CheckedNumeric<size_t> overhead = bidder_origin_overhead;
overhead += 1 + MaxLengthOfTaggedData(
(target_compressed_size - overhead).ValueOrDie());
if (!overhead.IsValid() ||
overhead.ValueOrDie() > target_compressed_size.ValueOrDie()) {
return 0;
}
target_compressed_size -= overhead;
return target_compressed_size.ValueOrDie();
}
void BiddingAndAuctionSerializer::TargetSizeEstimator::UpdateSizedGroupSizes(
size_t remaining_size) {
std::map<url::Origin, size_t> allocated_sizes;
base::CheckedNumeric<uint64_t> unallocated_target_size =
per_buyer_total_allowed_size_;
base::CheckedNumeric<size_t> unallocated_size = remaining_size;
std::set<url::Origin> allocated_buyers;
for (size_t iteration = 0; iteration < per_buyer_size_.size(); iteration++) {
base::CheckedNumeric<uint64_t> new_total_per_buyer_size = 0;
for (const auto& [bidder, bidder_config] : config_->per_buyer_configs) {
DCHECK(bidder_config->target_size.has_value());
if (allocated_buyers.contains(bidder)) {
new_total_per_buyer_size += per_buyer_size_[bidder];
continue;
}
base::CheckedNumeric<uint64_t> allocated_size;
if (bidder_config->target_size.value() == 0) {
allocated_size = 0;
} else {
allocated_size = (base::CheckedNumeric<uint64_t>(
bidder_config->target_size.value()) *
unallocated_size) /
unallocated_target_size;
}
if (per_buyer_size_[bidder] <= allocated_size.ValueOrDie()) {
allocated_sizes[bidder] = per_buyer_size_[bidder];
allocated_buyers.insert(bidder);
unallocated_size -= per_buyer_size_[bidder];
unallocated_target_size -= bidder_config->target_size.value();
new_total_per_buyer_size += per_buyer_size_[bidder];
continue;
}
allocated_sizes[bidder] = allocated_size.ValueOrDie<size_t>();
new_total_per_buyer_size += allocated_size;
}
if (new_total_per_buyer_size.ValueOrDie() ==
per_buyer_total_allowed_size_.ValueOrDie() ||
unallocated_target_size.ValueOrDie() == 0) {
per_buyer_total_allowed_size_ = new_total_per_buyer_size;
break;
}
per_buyer_total_allowed_size_ = new_total_per_buyer_size;
}
for (const auto& [bidder, size] : allocated_sizes) {
per_buyer_size_[bidder] = size;
}
}
void BiddingAndAuctionSerializer::TargetSizeEstimator::UpdateUnsizedGroupSizes(
size_t remaining_size) {
DCHECK_GT(total_unsized_buyers_, 0u);
remaining_unallocated_unsized_buyers_ = total_unsized_buyers_;
base::CheckedNumeric<size_t> unallocated_size = remaining_size;
std::set<url::Origin> allocated_buyers;
size_t previous_size_allocation = 0;
for (size_t iteration = 0; iteration < per_buyer_size_.size(); iteration++) {
if (remaining_unallocated_unsized_buyers_ == 0) {
break;
}
size_t equal_size_allocation =
((base::CheckedNumeric<uint64_t>(unallocated_size) +
remaining_unallocated_unsized_buyers_ - 1) /
remaining_unallocated_unsized_buyers_)
.ValueOrDie<size_t>();
if (equal_size_allocation <= previous_size_allocation) {
break;
}
unsized_buyer_size_ = equal_size_allocation;
for (const auto& [bidder, buyer_size] : per_buyer_size_) {
auto it = config_->per_buyer_configs.find(bidder);
if (it != config_->per_buyer_configs.end() &&
it->second->target_size.has_value()) {
continue;
}
if (allocated_buyers.contains(bidder)) {
continue;
}
if (buyer_size <= equal_size_allocation) {
unallocated_size -= buyer_size;
allocated_buyers.insert(bidder);
DCHECK_GT(remaining_unallocated_unsized_buyers_, 0u);
remaining_unallocated_unsized_buyers_--;
remaining_allocated_unsized_buyer_size_ += buyer_size;
continue;
}
}
previous_size_allocation = equal_size_allocation;
}
}
BiddingAndAuctionSerializer::BiddingAndAuctionSerializer() = default;
BiddingAndAuctionSerializer::BiddingAndAuctionSerializer(
BiddingAndAuctionSerializer&& other) = default;
BiddingAndAuctionSerializer::~BiddingAndAuctionSerializer() = default;
void BiddingAndAuctionSerializer::AddGroups(
const url::Origin& owner,
scoped_refptr<StorageInterestGroups> groups) {
std::vector<SingleStorageInterestGroup> groups_to_add =
groups->GetInterestGroups();
std::erase_if(groups_to_add, [](const SingleStorageInterestGroup& group) {
return (!group->interest_group.ads) ||
(group->interest_group.ads->size() == 0);
});
base::RandomShuffle(groups_to_add.begin(), groups_to_add.end());
std::ranges::stable_sort(
groups_to_add, [](const SingleStorageInterestGroup& a,
const SingleStorageInterestGroup& b) {
return a->interest_group.priority > b->interest_group.priority;
});
accumulated_groups_.emplace_back(std::move(owner), std::move(groups_to_add));
}
std::optional<BiddingAndAuctionData> BiddingAndAuctionSerializer::Build() {
DCHECK(config_);
if (config_->per_buyer_configs.empty() && accumulated_groups_.empty()) {
return std::nullopt;
}
BiddingAndAuctionData data;
cbor::Value::MapValue message_obj;
base::CheckedNumeric<size_t> message_elements_size = 0;
message_obj[cbor::Value("version")] = cbor::Value(0);
message_elements_size +=
TaggedStringLength(constexpr_strlen("version")) + TaggedUIntLength(0);
DCHECK(generation_id_.is_valid());
std::string generation_id_str = generation_id_.AsLowercaseString();
message_elements_size +=
TaggedStringLength(constexpr_strlen("generationId")) +
TaggedStringLength(generation_id_str.size());
message_obj[cbor::Value("generationId")] =
cbor::Value(std::move(generation_id_str));
message_obj[cbor::Value("publisher")] = cbor::Value(publisher_);
message_elements_size += TaggedStringLength(constexpr_strlen("publisher")) +
TaggedStringLength(publisher_.size());
message_obj[cbor::Value("enableDebugReporting")] = cbor::Value(true);
message_elements_size +=
TaggedStringLength(constexpr_strlen("enableDebugReporting")) + 1;
if (base::FeatureList::IsEnabled(
features::kFledgeSendDebugReportCooldownsToBandA)) {
message_obj[cbor::Value("inCooldownOrLockout")] = cbor::Value(false);
message_elements_size +=
TaggedStringLength(constexpr_strlen("inCooldownOrLockout")) + 1;
}
if (base::FeatureList::IsEnabled(
blink::features::kFledgeEnableSampleDebugReportOnCookieSetting)) {
bool for_debugging_only_sampling = ShouldSampleDebugReport();
message_obj[cbor::Value("enableSampledDebugReporting")] =
cbor::Value(for_debugging_only_sampling);
message_elements_size +=
TaggedStringLength(constexpr_strlen("enableSampledDebugReporting")) + 1;
}
std::string debug_key =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kProtectedAudiencesConsentedDebugToken);
if (!debug_key.empty()) {
cbor::Value::MapValue debug_map;
base::CheckedNumeric<size_t> debug_map_elements_size = 0;
debug_map[cbor::Value("isConsented")] = cbor::Value(true);
debug_map_elements_size +=
TaggedStringLength(constexpr_strlen("isConsented")) + 1;
debug_map[cbor::Value("token")] = cbor::Value(debug_key);
debug_map_elements_size += TaggedStringLength(constexpr_strlen("token")) +
TaggedStringLength(debug_key.size());
message_elements_size +=
TaggedStringLength(constexpr_strlen("consentedDebugConfig")) +
TaggedMapLength(debug_map, debug_map_elements_size);
message_obj[cbor::Value("consentedDebugConfig")] =
cbor::Value(std::move(debug_map));
}
int64_t timestamp = (timestamp_ - base::Time::UnixEpoch()).InMilliseconds();
message_obj[cbor::Value("requestTimestampMs")] = cbor::Value(timestamp);
message_elements_size +=
TaggedStringLength(constexpr_strlen("requestTimestampMs")) +
TaggedSIntLength(timestamp);
if (base::FeatureList::IsEnabled(features::kEnableBandAKAnonEnforcement) &&
KAnonIsEnabled()) {
message_obj[cbor::Value("enforceKAnon")] = cbor::Value(true);
message_elements_size +=
TaggedStringLength(constexpr_strlen("enforceKAnon")) + 1;
}
message_obj[cbor::Value("interestGroups")] = cbor::Value();
message_elements_size +=
TaggedStringLength(constexpr_strlen("interestGroups"));
const size_t framing_size = GetFramingSize();
const base::CheckedNumeric<size_t> total_size_before_groups =
TaggedMapLength(message_obj,
message_elements_size + 1 +
LengthOfLength(2 * accumulated_groups_.size())) +
framing_size;
if (!total_size_before_groups.IsValid()) {
DLOG(ERROR) << "total_size_before_groups is invalid";
return std::nullopt;
}
if (total_size_before_groups.ValueOrDie() >
config_->request_size.value_or(kBinSizes.back())) {
return std::nullopt;
}
blink::mojom::AuctionDataConfigPtr config = config_->Clone();
if (!config->request_size) {
config->request_size = kBinSizes.back();
}
SerializedBiddersMap groups = SerializeBidderGroupsWithConfig(
accumulated_groups_, *config, debug_report_in_lockout_,
debug_report_cooldown_map_, total_size_before_groups.ValueOrDie(),
timestamp_);
if (config->per_buyer_configs.empty() && groups.bidders.empty()) {
return std::nullopt;
}
message_elements_size +=
TaggedMapLength(groups.bidders, groups.bidders_elements_size);
message_obj[cbor::Value("interestGroups")] =
cbor::Value(std::move(groups.bidders));
message_total_size_ = TaggedMapLength(message_obj, message_elements_size);
message_obj_ = std::move(message_obj);
base::UmaHistogramCounts1000(
"Ads.InterestGroup.ServerAuction.Request.NumGroups", groups.num_groups);
data.group_names = std::move(groups.group_names);
data.group_pagg_coordinators = std::move(groups.group_pagg_coordinators);
return data;
}
std::optional<std::vector<uint8_t>>
BiddingAndAuctionSerializer::BuildRequestFromMessage(const url::Origin& seller,
base::Time now) {
if (message_obj_.empty()) {
NOTREACHED(base::NotFatalUntil::M138);
}
if (base::FeatureList::IsEnabled(
features::kFledgeSendDebugReportCooldownsToBandA)) {
bool debug_report_in_cooldown_or_lockout =
debug_report_in_lockout_ ||
IsInDebugReportCooldown(seller, debug_report_cooldown_map_, now);
message_obj_[cbor::Value("inCooldownOrLockout")] =
cbor::Value(debug_report_in_cooldown_or_lockout);
}
std::optional<std::vector<uint8_t>> maybe_msg =
cbor::Writer::Write(cbor::Value(message_obj_));
DCHECK(maybe_msg);
DCHECK_EQ(static_cast<size_t>(message_total_size_.ValueOrDie()),
maybe_msg->size());
base::UmaHistogramCounts100000(
"Ads.InterestGroup.ServerAuction.Request.UnpaddedSize", maybe_msg->size());
blink::mojom::AuctionDataConfigPtr config = config_->Clone();
if (!config->request_size) {
config->request_size = kBinSizes.back();
}
const size_t framing_size = GetFramingSize();
base::CheckedNumeric<uint32_t> desired_size;
if (config->per_buyer_configs.empty()) {
const size_t size_before_padding =
base::CheckAdd(framing_size, maybe_msg->size()).ValueOrDie();
DCHECK_GE(config->request_size.value(), size_before_padding);
auto size_iter = std::lower_bound(kBinSizes.begin(), kBinSizes.end(),
size_before_padding);
if (size_iter != kBinSizes.end()) {
desired_size = std::min(*size_iter, config->request_size.value());
} else {
desired_size = config->request_size.value();
}
} else {
desired_size = config->request_size.value();
}
base::CheckedNumeric<size_t> padded_size =
desired_size - framing_size + kFramingHeaderSize;
if (!padded_size.IsValid()) {
DLOG(ERROR) << "padded_size is invalid";
return std::nullopt;
}
CHECK_GE(static_cast<size_t>(padded_size.ValueOrDie()),
maybe_msg->size() + kFramingHeaderSize);
std::vector<uint8_t> request(padded_size.ValueOrDie());
base::SpanWriter<uint8_t> span_writer(request);
span_writer.WriteU8BigEndian((kRequestVersion << kRequestVersionBitOffset) |
(kGzipCompression << kCompressionBitOffset));
span_writer.WriteU32BigEndian(static_cast<uint32_t>(maybe_msg->size()));
span_writer.Write(*maybe_msg);
return request;
}
}