#include "content/browser/interest_group/interest_group_auction.h"
#include <stdint.h>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/rand_util.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/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/token.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_id_helper.h"
#include "base/types/optional_ref.h"
#include "base/uuid.h"
#include "base/values.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/fenced_frame/fenced_frame_config.h"
#include "content/browser/interest_group/ad_auction_page_data.h"
#include "content/browser/interest_group/additional_bid_result.h"
#include "content/browser/interest_group/additional_bids_util.h"
#include "content/browser/interest_group/auction_metrics_recorder.h"
#include "content/browser/interest_group/auction_nonce_manager.h"
#include "content/browser/interest_group/auction_process_manager.h"
#include "content/browser/interest_group/auction_url_loader_factory_proxy.h"
#include "content/browser/interest_group/auction_worklet_manager.h"
#include "content/browser/interest_group/bidding_and_auction_response.h"
#include "content/browser/interest_group/debuggable_auction_worklet.h"
#include "content/browser/interest_group/for_debugging_only_report_util.h"
#include "content/browser/interest_group/group_by_origin_key.h"
#include "content/browser/interest_group/header_direct_from_seller_signals.h"
#include "content/browser/interest_group/interest_group_auction_reporter.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/interest_group_k_anonymity_manager.h"
#include "content/browser/interest_group/interest_group_manager_impl.h"
#include "content/browser/interest_group/interest_group_pa_report_util.h"
#include "content/browser/interest_group/interest_group_priority_util.h"
#include "content/browser/interest_group/interest_group_real_time_report_util.h"
#include "content/browser/interest_group/storage_interest_group.h"
#include "content/browser/interest_group/trusted_signals_cache_impl.h"
#include "content/common/features.h"
#include "content/public/browser/auction_result.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/services/auction_worklet/public/cpp/auction_worklet_features.h"
#include "content/services/auction_worklet/public/cpp/private_aggregation_reporting.h"
#include "content/services/auction_worklet/public/cpp/real_time_reporting.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom-forward.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
#include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
#include "content/services/auction_worklet/public/mojom/trusted_signals_cache.mojom.h"
#include "crypto/sha2.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_receiver_set.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "net/third_party/quiche/src/quiche/oblivious_http/buffers/oblivious_http_response.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/client_security_state.mojom.h"
#include "services/network/public/mojom/ip_address_space.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/common/interest_group/ad_auction_constants.h"
#include "third_party/blink/public/common/interest_group/ad_auction_currencies.h"
#include "third_party/blink/public/common/interest_group/ad_display_size_utils.h"
#include "third_party/blink/public/common/interest_group/auction_config.h"
#include "third_party/blink/public/common/interest_group/devtools_serialization.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/blink/public/mojom/private_aggregation/private_aggregation_host.mojom.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
constexpr base::TimeDelta kMaxPerBuyerTimeout = base::Milliseconds(500);
constexpr base::TimeDelta kGroupFreshnessMin = base::Minutes(1);
constexpr base::TimeDelta kGroupFreshnessMax = base::Days(30);
constexpr base::TimeDelta kGroupFreshnessMax90d = base::Days(90);
constexpr int kGroupFreshnessBuckets = 100;
constexpr base::TimeDelta kMinUpdateIfOlderThanHistogram = base::Minutes(-10);
constexpr base::TimeDelta kMaxUpdateIfOlderThanHistogram = base::Hours(30);
constexpr size_t kUpdateIfOlderThanBuckets = 100;
constexpr char kInvalidServerResponseReasonUMAName[] =
"Ads.InterestGroup.ServerAuction.InvalidServerResponseReason";
enum class InvalidServerResponseReason {
kUnknown = 0,
kNotWitnessed = 1,
kUnknownRequestId = 2,
kSellerMismatch = 3,
kDecryptionFailure = 4,
kUnframingFailure = 5,
kDecompressFailure = 6,
kCBORParseFailure = 7,
kUnexpectedResponseStructure = 8,
kNotMultilevel = 9,
kTopLevelSellerMismatch = 10,
kNotSingleLevel = 11,
kInvalidBid = 12,
kWinningGroupNotBidder = 13,
kMissingWinningGroup = 14,
kMissingWinningGroupBidURL = 15,
kConstructBidFailure = 16,
kBadCurrency = 17,
kDecoderShutdown = 18,
kMaxValue = kDecoderShutdown,
};
std::string DirectFromSellerSignalsHeaderAdSlotNoMatchError(
const std::string& ad_slot) {
return base::StringPrintf(
"When looking for directFromSellerSignalsHeaderAdSlot %s, failed to "
"find a matching response.",
ad_slot.c_str());
}
bool IsUrlValid(const GURL& url) {
return url.is_valid() && url.SchemeIs(url::kHttpsScheme);
}
bool IsKAnon(const base::flat_set<std::string>& kanon_keys,
const std::string& key) {
return kanon_keys.contains(key);
}
bool IsBidKAnon(const InterestGroupAuction::Bid& bid) {
if (!IsKAnon(bid.bid_state->kanon_keys,
blink::HashedKAnonKeyForAdBid(*bid.interest_group,
bid.ad_descriptor))) {
return false;
}
for (const auto& component : bid.selected_ad_components) {
if (!IsKAnon(
bid.bid_state->kanon_keys,
blink::HashedKAnonKeyForAdComponentBid(component.ad_descriptor))) {
return false;
}
}
return true;
}
bool IsSelectedReportingIdValid(
base::optional_ref<const std::vector<std::string>> selectable_ids,
const std::string& selected_id,
bool limit_to_truncated_selected_reporting_ids) {
if (!selectable_ids.has_value()) {
return false;
}
auto iter =
std::find(selectable_ids->begin(), selectable_ids->end(), selected_id);
if (iter == selectable_ids->end()) {
return false;
}
if (limit_to_truncated_selected_reporting_ids &&
base::FeatureList::IsEnabled(
blink::features::
kFledgeTruncateSelectableBuyerAndSellerReportingIdsToKAnonLimit) &&
blink::features::
kFledgeSelectableBuyerAndSellerReportingIdsFetchedFromKAnonLimit
.Get() >= 0 &&
std::distance(selectable_ids->begin(), iter) >=
blink::features::
kFledgeSelectableBuyerAndSellerReportingIdsFetchedFromKAnonLimit
.Get()) {
return false;
}
return true;
}
std::vector<auction_worklet::mojom::KAnonKeyPtr> KAnonKeysToMojom(
const base::flat_set<std::string>& kanon_keys) {
std::vector<auction_worklet::mojom::KAnonKeyPtr> result;
for (const auto& key : kanon_keys) {
result.emplace_back(auction_worklet::mojom::KAnonKey::New(key));
}
return result;
}
const blink::InterestGroup::Ad* FindMatchingAd(
const std::optional<std::vector<blink::InterestGroup::Ad>>& ads,
const base::flat_set<std::string>& kanon_keys,
const blink::InterestGroup& interest_group,
auction_worklet::mojom::BidRole bid_role,
base::optional_ref<const std::string>
selected_buyer_and_seller_reporting_id,
bool is_component_ad,
const blink::AdDescriptor& ad_descriptor) {
if (!ads.has_value()) {
return nullptr;
}
if (!IsUrlValid(ad_descriptor.url)) {
return nullptr;
}
if (ad_descriptor.size && !IsValidAdSize(ad_descriptor.size.value())) {
return nullptr;
}
const blink::InterestGroup::Ad* maybe_matching_ad = nullptr;
for (const auto& ad : *ads) {
if (ad.render_url() != ad_descriptor.url) {
continue;
}
if (!ad.size_group && !ad_descriptor.size) {
maybe_matching_ad = &ad;
break;
}
if (!ad.size_group != !ad_descriptor.size) {
maybe_matching_ad = &ad;
break;
}
auto has_matching_ad_size = [&interest_group,
&ad_descriptor](const std::string& ad_size) {
return interest_group.ad_sizes->at(ad_size) == *ad_descriptor.size;
};
if (std::ranges::any_of(interest_group.size_groups->at(ad.size_group),
has_matching_ad_size)) {
maybe_matching_ad = &ad;
break;
}
}
if (maybe_matching_ad &&
bid_role != auction_worklet::mojom::BidRole::kUnenforcedKAnon) {
const std::string kanon_key =
is_component_ad
? blink::HashedKAnonKeyForAdComponentBid(ad_descriptor)
: blink::HashedKAnonKeyForAdBid(interest_group, ad_descriptor);
if (!IsKAnon(kanon_keys, kanon_key)) {
return nullptr;
}
if (!is_component_ad &&
selected_buyer_and_seller_reporting_id.has_value()) {
const std::string reporting_key = blink::HashedKAnonKeyForAdNameReporting(
interest_group, *maybe_matching_ad,
selected_buyer_and_seller_reporting_id);
if (!IsKAnon(kanon_keys, reporting_key)) {
return nullptr;
}
}
}
return maybe_matching_ad;
}
bool IsValidBid(double bid) {
return !std::isnan(bid) && std::isfinite(bid) && bid > 0;
}
bool IsValidMultiBid(
auction_worklet::mojom::KAnonymityBidMode kanon_mode,
AuctionMetricsRecorder* auction_metrics_recorder,
const std::vector<auction_worklet::mojom::BidderWorkletBidPtr>& mojo_bids,
uint16_t multi_bid_limit) {
unsigned kanon_bids = 0;
unsigned non_kanon_bids = 0;
for (const auto& bid : mojo_bids) {
switch (bid->bid_role) {
case auction_worklet::mojom::BidRole::kEnforcedKAnon:
++kanon_bids;
break;
case auction_worklet::mojom::BidRole::kUnenforcedKAnon:
++non_kanon_bids;
break;
case auction_worklet::mojom::BidRole::kBothKAnonModes:
++kanon_bids;
++non_kanon_bids;
break;
}
}
auction_metrics_recorder->RecordNumberOfBidsFromGenerateBid(kanon_bids,
mojo_bids.size());
if (kanon_mode == auction_worklet::mojom::KAnonymityBidMode::kNone &&
kanon_bids != 0) {
return false;
}
return (kanon_bids <= multi_bid_limit) && (non_kanon_bids <= multi_bid_limit);
}
struct BidStatesDescByPriority {
bool operator()(const std::unique_ptr<InterestGroupAuction::BidState>& a,
const std::unique_ptr<InterestGroupAuction::BidState>& b) {
return a->calculated_priority > b->calculated_priority;
}
bool operator()(const std::unique_ptr<InterestGroupAuction::BidState>& a,
double b_priority) {
return a->calculated_priority > b_priority;
}
bool operator()(double a_priority,
const std::unique_ptr<InterestGroupAuction::BidState>& b) {
return a_priority > b->calculated_priority;
}
};
struct BidStatesDescByPriorityAndGroupByJoinOrigin {
bool operator()(const std::unique_ptr<InterestGroupAuction::BidState>& a,
const std::unique_ptr<InterestGroupAuction::BidState>& b) {
return std::tie(a->calculated_priority, a->group_by_origin_id,
a->bidder->interest_group.execution_mode) >
std::tie(b->calculated_priority, b->group_by_origin_id,
b->bidder->interest_group.execution_mode);
}
};
bool IsBidRoleUsedForWinner(
auction_worklet::mojom::KAnonymityBidMode kanon_mode,
auction_worklet::mojom::BidRole bid_role) {
if (kanon_mode == auction_worklet::mojom::KAnonymityBidMode::kEnforce) {
return bid_role != auction_worklet::mojom::BidRole::kUnenforcedKAnon;
} else {
return bid_role != auction_worklet::mojom::BidRole::kEnforcedKAnon;
}
}
static const char* ScoreAdTraceEventName(const InterestGroupAuction::Bid& bid) {
if (bid.bid_role == auction_worklet::mojom::BidRole::kEnforcedKAnon) {
return "seller_worklet_score_kanon_enforced_ad";
} else {
return "seller_worklet_score_ad";
}
}
bool GroupSatisfiesAllCapabilities(const blink::InterestGroup& interest_group,
blink::SellerCapabilitiesType capabilities,
const url::Origin& seller) {
if (interest_group.seller_capabilities) {
auto it = interest_group.seller_capabilities->find(seller);
if (it != interest_group.seller_capabilities->end()) {
return it->second.HasAll(capabilities);
}
}
return interest_group.all_sellers_capabilities.HasAll(capabilities);
}
bool CanReportPaBuyersValue(const blink::InterestGroup& interest_group,
blink::SellerCapabilities capability,
const url::Origin& seller) {
return GroupSatisfiesAllCapabilities(interest_group, {capability}, seller);
}
std::optional<absl::uint128> BucketBaseForReportPaBuyers(
const blink::AuctionConfig& config,
const url::Origin& buyer) {
if (!config.non_shared_params.auction_report_buyer_keys) {
return std::nullopt;
}
CHECK(config.non_shared_params.interest_group_buyers);
const std::vector<url::Origin>& buyers =
*config.non_shared_params.interest_group_buyers;
std::optional<size_t> index;
for (size_t i = 0; i < buyers.size(); i++) {
if (buyer == buyers.at(i)) {
index = i;
break;
}
}
CHECK(index);
if (*index >= config.non_shared_params.auction_report_buyer_keys->size()) {
return std::nullopt;
}
return config.non_shared_params.auction_report_buyer_keys->at(*index);
}
std::optional<blink::AuctionConfig::NonSharedParams::AuctionReportBuyersConfig>
ReportBuyersConfigForPaBuyers(
blink::AuctionConfig::NonSharedParams::BuyerReportType buyer_report_type,
const blink::AuctionConfig& config) {
if (!config.non_shared_params.auction_report_buyers) {
return std::nullopt;
}
const auto& report_buyers = *config.non_shared_params.auction_report_buyers;
auto it = report_buyers.find(buyer_report_type);
if (it == report_buyers.end()) {
return std::nullopt;
}
return it->second;
}
double PercentMetric(size_t numerator, size_t denominator) {
if (denominator == 0) {
return 0.0;
}
return std::min(100.0 * numerator / denominator, 110.0);
}
double SoftBound(size_t value, size_t cap) {
return std::min(static_cast<double>(value), 1.1 * cap);
}
using PrivateAggregationReservedOnceReps =
std::array<const InterestGroupAuction::BidState*,
base::checked_cast<size_t>(PrivateAggregationPhase::kNumPhases)>;
void TakePrivateAggregationRequestsForBidState(
std::unique_ptr<InterestGroupAuction::BidState>& state,
bool is_component_auction,
const InterestGroupAuction::BidState* winner,
const InterestGroupAuction::BidState* non_kanon_winner,
const PrivateAggregationReservedOnceReps& reserved_once_reps,
const InterestGroupAuction::PostAuctionSignals& signals,
const std::optional<InterestGroupAuction::PostAuctionSignals>&
top_level_signals,
const InterestGroupAuction::PrivateAggregationAllParticipantsDataPtrs&
all_participant_data,
std::map<PrivateAggregationKey,
InterestGroupAuctionReporter::FinalizedPrivateAggregationRequests>&
private_aggregation_requests_reserved,
std::map<std::string,
InterestGroupAuctionReporter::FinalizedPrivateAggregationRequests>&
private_aggregation_requests_non_reserved) {
bool is_winner = state.get() == winner;
for (auto& [key, requests] : state->private_aggregation_requests) {
const url::Origin& origin = key.reporting_origin;
PrivateAggregationPhase phase = key.phase;
bool is_reserved_once_rep =
state.get() == reserved_once_reps[static_cast<int>(phase)];
const std::optional<url::Origin>& aggregation_coordinator_origin =
key.aggregation_coordinator_origin;
double winning_bid_to_use = signals.winning_bid;
double highest_scoring_other_bid_to_use = signals.highest_scoring_other_bid;
if (phase == PrivateAggregationPhase::kTopLevelSeller &&
is_component_auction) {
highest_scoring_other_bid_to_use = 0;
winning_bid_to_use =
top_level_signals.has_value() ? top_level_signals->winning_bid : 0.0;
}
const PrivateAggregationParticipantData* participant_data =
all_participant_data[static_cast<size_t>(phase)];
CHECK(participant_data);
for (auction_worklet::mojom::PrivateAggregationRequestPtr& request :
requests) {
if (ShouldKeepRequestOnlyIfReservedOnceRep(*request) &&
!is_reserved_once_rep) {
continue;
}
std::optional<PrivateAggregationRequestWithEventType> converted_request =
FillInPrivateAggregationRequest(
std::move(request), winning_bid_to_use,
highest_scoring_other_bid_to_use, state->reject_reason,
*participant_data, state->pa_timings(phase), is_winner);
if (converted_request.has_value()) {
PrivateAggregationRequestWithEventType converted_request_value =
std::move(converted_request.value());
const std::optional<std::string>& event_type =
converted_request_value.event_type;
if (event_type.has_value()) {
private_aggregation_requests_non_reserved[event_type.value()]
.emplace_back(std::move(converted_request_value.request));
} else {
PrivateAggregationKey agg_key = {origin,
aggregation_coordinator_origin};
private_aggregation_requests_reserved[std::move(agg_key)]
.emplace_back(std::move(converted_request_value.request));
}
}
}
}
if (non_kanon_winner == state.get()) {
bool is_reserved_once_rep =
state.get() ==
reserved_once_reps[static_cast<int>(PrivateAggregationPhase::kBidder)];
const url::Origin& bidder = state->bidder->interest_group.owner;
const std::optional<url::Origin>& aggregation_coordinator_origin =
state->bidder->interest_group.aggregation_coordinator_origin;
for (auction_worklet::mojom::PrivateAggregationRequestPtr& request :
state->non_kanon_private_aggregation_requests) {
bool reserved_once = IsPrivateAggregationRequestReservedOnce(*request);
if (reserved_once && !is_reserved_once_rep) {
continue;
}
const PrivateAggregationParticipantData* participant_data =
all_participant_data[static_cast<size_t>(
PrivateAggregationPhase::kBidder)];
CHECK(participant_data);
std::optional<PrivateAggregationRequestWithEventType> converted_request =
FillInPrivateAggregationRequest(
std::move(request), signals.winning_bid,
signals.highest_scoring_other_bid,
auction_worklet::mojom::RejectReason::kBelowKAnonThreshold,
*participant_data,
state->pa_timings(PrivateAggregationPhase::kBidder), false);
if (converted_request.has_value()) {
PrivateAggregationKey agg_key = {bidder,
aggregation_coordinator_origin};
PrivateAggregationRequestWithEventType converted_request_value =
std::move(converted_request.value());
CHECK(!converted_request_value.event_type.has_value());
private_aggregation_requests_reserved[std::move(agg_key)].emplace_back(
std::move(converted_request_value.request));
}
}
}
for (auto& [key, requests] : state->server_filtered_pagg_requests_reserved) {
if (!requests.empty()) {
FinalizedPrivateAggregationRequests& destination_vector =
private_aggregation_requests_reserved[key];
destination_vector.insert(destination_vector.end(),
std::move_iterator(requests.begin()),
std::move_iterator(requests.end()));
}
}
for (auto& [event, requests] :
state->server_filtered_pagg_requests_non_reserved) {
if (!requests.empty()) {
FinalizedPrivateAggregationRequests& destination_vector =
private_aggregation_requests_non_reserved[event];
destination_vector.insert(destination_vector.end(),
std::move_iterator(requests.begin()),
std::move_iterator(requests.end()));
}
}
}
void TakeRealTimeContributionsForBidState(
InterestGroupAuction::BidState& state,
std::map<url::Origin, InterestGroupAuction::RealTimeReportingContributions>&
contributions) {
for (auto& [origin, per_origin_contributions] :
state.real_time_contributions) {
InterestGroupAuction::RealTimeReportingContributions&
contributions_for_origin = contributions[origin];
contributions_for_origin.insert(
contributions_for_origin.end(),
std::move_iterator(per_origin_contributions.begin()),
std::move_iterator(per_origin_contributions.end()));
}
}
bool IsOriginInDebugReportCooldownOrLockout(
const url::Origin& origin,
const std::optional<DebugReportLockoutAndCooldowns>&
debug_report_lockout_and_cooldowns,
const base::Time now) {
if (!debug_report_lockout_and_cooldowns.has_value()) {
return false;
}
return IsInDebugReportLockout(debug_report_lockout_and_cooldowns->lockout,
now) ||
IsInDebugReportCooldown(
origin,
debug_report_lockout_and_cooldowns->debug_report_cooldown_map,
now);
}
void UpdateDebugReportCooldown(
const url::Origin& origin,
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns,
base::Time now_nearest_next_hour) {
int restricted_cooldown_random_max =
blink::features::kFledgeDebugReportSamplingRestrictedCooldownRandomMax
.Get();
CHECK_GE(restricted_cooldown_random_max, 0);
int cooldown_rand = base::RandInt(0, restricted_cooldown_random_max);
DebugReportCooldownType cooldown_type =
restricted_cooldown_random_max == INT_MAX || cooldown_rand != 0
? DebugReportCooldownType::kShortCooldown
: DebugReportCooldownType::kRestrictedCooldown;
base::UmaHistogramEnumeration(
"Ads.InterestGroup.Auction.ForDebuggingOnlyCooldownType", cooldown_type);
if ((cooldown_type == DebugReportCooldownType::kShortCooldown &&
blink::features::kFledgeDebugReportShortCooldown.Get() !=
base::Milliseconds(0)) ||
(cooldown_type == DebugReportCooldownType::kRestrictedCooldown &&
blink::features::kFledgeDebugReportRestrictedCooldown.Get() !=
base::Milliseconds(0))) {
new_debug_report_lockout_and_cooldowns.debug_report_cooldown_map[origin] =
DebugReportCooldown(now_nearest_next_hour, cooldown_type);
}
}
bool SampleDebugReport(
const url::Origin& origin,
bool is_from_server_response,
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns) {
bool can_send_debug_report = false;
int sampling_random_max =
blink::features::kFledgeDebugReportSamplingRandomMax.Get();
CHECK_GE(sampling_random_max, 0);
base::Time now_nearest_next_hour = base::Time::FromDeltaSinceWindowsEpoch(
base::Time::Now().ToDeltaSinceWindowsEpoch().CeilToMultiple(
base::Hours(1)));
int sampling_rand = base::RandInt(0, sampling_random_max);
if (is_from_server_response ||
(sampling_random_max != INT_MAX && sampling_rand == 0)) {
can_send_debug_report = true;
if (blink::features::kFledgeDebugReportLockout.Get() !=
base::Milliseconds(0)) {
new_debug_report_lockout_and_cooldowns.lockout =
DebugReportLockout(now_nearest_next_hour,
blink::features::kFledgeDebugReportLockout.Get());
}
}
base::UmaHistogramBoolean(
"Ads.InterestGroup.Auction.ForDebuggingOnlyReportAllowedAfterSampling",
can_send_debug_report);
UpdateDebugReportCooldown(origin, new_debug_report_lockout_and_cooldowns,
now_nearest_next_hour);
return can_send_debug_report;
}
bool KeepDebugReport(
const url::Origin& origin,
bool is_from_server_response,
std::optional<DebugReportLockoutAndCooldowns>&
debug_report_lockout_and_cooldowns,
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns) {
if (!base::FeatureList::IsEnabled(
blink::features::kFledgeSampleDebugReports)) {
return true;
}
bool should_sample_debug_report = false;
bool selected_by_sampling = false;
base::Time now = base::Time::Now();
if (!base::FeatureList::IsEnabled(
blink::features::kFledgeEnableSampleDebugReportOnCookieSetting)) {
should_sample_debug_report =
blink::features::kFledgeEnableFilteringDebugReportStartingFrom.Get() !=
base::Milliseconds(0);
} else {
should_sample_debug_report = ShouldSampleDebugReport();
}
if (!IsOriginInDebugReportCooldownOrLockout(
origin, debug_report_lockout_and_cooldowns, now) &&
!IsOriginInDebugReportCooldownOrLockout(
origin, new_debug_report_lockout_and_cooldowns, now)) {
selected_by_sampling =
SampleDebugReport(origin, is_from_server_response,
new_debug_report_lockout_and_cooldowns);
}
return !should_sample_debug_report || selected_by_sampling;
}
void TakeDebugReportUrlsForWinner(
const InterestGroupAuction::BidState* winner,
const InterestGroupAuction::PostAuctionSignals& signals,
const std::optional<InterestGroupAuction::PostAuctionSignals>&
top_level_signals,
const url::Origin& bidder,
const url::Origin& seller,
const std::optional<url::Origin>& top_level_seller,
std::optional<DebugReportLockoutAndCooldowns>&
debug_report_lockout_and_cooldowns,
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns,
std::vector<GURL>& debug_win_report_urls) {
if (winner->bidder_debug_win_report_url.has_value() &&
KeepDebugReport(bidder, winner->is_from_server_response,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
debug_win_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(winner->bidder_debug_win_report_url).value(), signals));
}
if (winner->seller_debug_win_report_url.has_value() &&
KeepDebugReport(seller, winner->is_from_server_response,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
debug_win_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(winner->seller_debug_win_report_url).value(), signals,
top_level_signals));
}
if (winner->top_level_seller_debug_win_report_url.has_value()) {
CHECK(top_level_seller.has_value());
if (KeepDebugReport(*top_level_seller, winner->is_from_server_response,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
debug_win_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(winner->top_level_seller_debug_win_report_url).value(),
top_level_signals.value()));
}
}
}
void TakeDebugReportUrlsForLosingBidState(
std::unique_ptr<InterestGroupAuction::BidState>& bid_state,
const InterestGroupAuction::PostAuctionSignals& signals,
const std::optional<InterestGroupAuction::PostAuctionSignals>&
top_level_signals,
const url::Origin& bidder,
const url::Origin& seller,
const std::optional<url::Origin>& top_level_seller,
std::optional<DebugReportLockoutAndCooldowns>&
debug_report_lockout_and_cooldowns,
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns,
std::vector<GURL>& debug_loss_report_urls) {
if (bid_state->bidder_debug_loss_report_url.has_value() &&
KeepDebugReport(bidder, bid_state->is_from_server_response,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
debug_loss_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(bid_state->bidder_debug_loss_report_url).value(),
InterestGroupAuction::PostAuctionSignals(
signals.winning_bid, signals.winning_bid_currency,
signals.made_winning_bid, 0.0,
std::nullopt,
false),
std::nullopt, bid_state->reject_reason));
}
if (bid_state->seller_debug_loss_report_url.has_value() &&
KeepDebugReport(seller, bid_state->is_from_server_response,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
debug_loss_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(bid_state->seller_debug_loss_report_url).value(), signals,
top_level_signals));
}
if (bid_state->top_level_seller_debug_loss_report_url.has_value()) {
CHECK(top_level_seller.has_value());
if (KeepDebugReport(*top_level_seller, bid_state->is_from_server_response,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
debug_loss_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(bid_state->top_level_seller_debug_loss_report_url)
.value(),
top_level_signals.value()));
}
}
}
void TakeServerFilteredDebugReportUrls(
std::map<url::Origin, std::vector<GURL>>&
server_filtered_debugging_only_reports,
std::optional<DebugReportLockoutAndCooldowns>&
debug_report_lockout_and_cooldowns,
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns,
std::vector<GURL>& debug_loss_report_urls) {
base::Time now = base::Time::Now();
base::Time now_nearest_next_hour = base::Time::FromDeltaSinceWindowsEpoch(
now.ToDeltaSinceWindowsEpoch().CeilToMultiple(base::Hours(1)));
for (const auto& [origin, reportUrls] :
server_filtered_debugging_only_reports) {
if (reportUrls.empty()) {
if (!IsOriginInDebugReportCooldownOrLockout(
origin, debug_report_lockout_and_cooldowns, now) &&
!IsOriginInDebugReportCooldownOrLockout(
origin, new_debug_report_lockout_and_cooldowns, now)) {
UpdateDebugReportCooldown(origin,
new_debug_report_lockout_and_cooldowns,
now_nearest_next_hour);
}
continue;
}
for (const auto& report : reportUrls) {
if (KeepDebugReport(origin, true,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
debug_loss_report_urls.emplace_back(report);
}
}
}
}
void TakeDebugReportUrlsForBidState(
std::unique_ptr<InterestGroupAuction::BidState>& bid_state,
const InterestGroupAuction::BidState* winner,
const InterestGroupAuction::PostAuctionSignals& signals,
const std::optional<InterestGroupAuction::PostAuctionSignals>&
top_level_signals,
const url::Origin& bidder,
const url::Origin& seller,
const std::optional<url::Origin>& top_level_seller,
std::optional<DebugReportLockoutAndCooldowns>&
debug_report_lockout_and_cooldowns,
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns,
std::vector<GURL>& debug_win_report_urls,
std::vector<GURL>& debug_loss_report_urls) {
if (bid_state.get() == winner) {
TakeDebugReportUrlsForWinner(
winner, signals, top_level_signals, bidder, seller, top_level_seller,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns, debug_win_report_urls);
} else {
TakeDebugReportUrlsForLosingBidState(
bid_state, signals, top_level_signals, bidder, seller, top_level_seller,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns, debug_loss_report_urls);
}
TakeServerFilteredDebugReportUrls(
bid_state->server_filtered_debugging_only_reports,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns, debug_loss_report_urls);
}
std::optional<base::TimeDelta> PerBuyerTimeoutHelper(
const url::Origin& buyer,
const blink::AuctionConfig::MaybePromiseBuyerTimeouts& buyer_timeouts) {
DCHECK(!buyer_timeouts.is_promise());
const auto& per_buyer_timeouts = buyer_timeouts.value().per_buyer_timeouts;
if (per_buyer_timeouts.has_value()) {
auto it = per_buyer_timeouts->find(buyer);
if (it != per_buyer_timeouts->end()) {
return it->second;
}
}
const auto& all_buyers_timeout = buyer_timeouts.value().all_buyers_timeout;
if (all_buyers_timeout.has_value()) {
return all_buyers_timeout.value();
}
return std::nullopt;
}
std::optional<base::TimeDelta> PerBuyerTimeout(
const url::Origin& buyer,
const blink::AuctionConfig& auction_config) {
std::optional<base::TimeDelta> out = PerBuyerTimeoutHelper(
buyer, auction_config.non_shared_params.buyer_timeouts);
if (!out) {
return out;
}
return std::min(*out, kMaxPerBuyerTimeout);
}
std::optional<base::TimeDelta> PerBuyerCumulativeTimeout(
const url::Origin& buyer,
const blink::AuctionConfig& auction_config) {
return PerBuyerTimeoutHelper(
buyer, auction_config.non_shared_params.buyer_cumulative_timeouts);
}
std::optional<blink::AdCurrency> PerBuyerCurrency(
const url::Origin& buyer,
const blink::AuctionConfig& auction_config) {
const blink::AuctionConfig::MaybePromiseBuyerCurrencies& buyer_currencies =
auction_config.non_shared_params.buyer_currencies;
DCHECK(!buyer_currencies.is_promise());
const auto& per_buyer_currencies =
buyer_currencies.value().per_buyer_currencies;
if (per_buyer_currencies.has_value()) {
auto it = per_buyer_currencies->find(buyer);
if (it != per_buyer_currencies->end()) {
return it->second;
}
}
const auto& all_buyers_currency =
buyer_currencies.value().all_buyers_currency;
return all_buyers_currency;
}
template <typename MojoReceiver>
bool ValidatePrivateAggregationRequests(
MojoReceiver& receiver,
const PrivateAggregationRequests& pa_requests) {
std::optional<std::string> error =
content::ValidatePrivateAggregationRequests(pa_requests);
if (error.has_value()) {
receiver.ReportBadMessage(*error);
return false;
}
return true;
}
bool ValidateBidderPrivateAggregationRequests(
mojo::AssociatedReceiverSet<auction_worklet::mojom::GenerateBidClient,
InterestGroupAuction::BidState*>&
generate_bid_client_receiver_set,
const PrivateAggregationRequests& pa_requests,
const PrivateAggregationRequests& non_kanon_pa_requests) {
if (!ValidatePrivateAggregationRequests(generate_bid_client_receiver_set,
pa_requests)) {
return false;
}
if (!ValidatePrivateAggregationRequests(generate_bid_client_receiver_set,
non_kanon_pa_requests)) {
return false;
}
for (const auto& non_kanon_request : non_kanon_pa_requests) {
if (!auction_worklet::HasKAnonFailureComponent(*non_kanon_request)) {
generate_bid_client_receiver_set.ReportBadMessage(
"Incorrect non-kanon Private Aggregation request");
return false;
}
}
return true;
}
std::optional<std::pair<std::string_view, std::string>>
CreateTrustedBiddingSignalsSlotSizeParams(
const blink::AuctionConfig& config,
blink::InterestGroup::TrustedBiddingSignalsSlotSizeMode
trusted_bidding_signals_slot_size_mode) {
switch (trusted_bidding_signals_slot_size_mode) {
case blink::InterestGroup::TrustedBiddingSignalsSlotSizeMode::kNone:
return std::nullopt;
case blink::InterestGroup::TrustedBiddingSignalsSlotSizeMode::kSlotSize:
if (!config.non_shared_params.requested_size) {
return std::nullopt;
}
return {{"slotSize", blink::ConvertAdSizeToString(
*config.non_shared_params.requested_size)}};
case blink::InterestGroup::TrustedBiddingSignalsSlotSizeMode::
kAllSlotsRequestedSizes: {
if (!config.non_shared_params.all_slots_requested_sizes ||
config.non_shared_params.all_slots_requested_sizes->empty()) {
return std::nullopt;
}
std::string all_ad_sizes;
for (const blink::AdSize& ad_size :
*config.non_shared_params.all_slots_requested_sizes) {
if (!all_ad_sizes.empty()) {
all_ad_sizes += ",";
}
all_ad_sizes += blink::ConvertAdSizeToString(ad_size);
}
return {{"allSlotsRequestedSizes", std::move(all_ad_sizes)}};
}
}
}
std::optional<BiddingAndAuctionResponse::GhostWinnerForTopLevelAuction>
ConstructGhostWinnerFromGroupAndCandidate(
const blink::InterestGroup& group,
const BiddingAndAuctionResponse::KAnonJoinCandidate& candidate) {
BiddingAndAuctionResponse::GhostWinnerForTopLevelAuction result;
result.modified_bid = 0.0001;
if (!group.ads) {
return std::nullopt;
}
std::string_view ad_hash =
base::as_string_view(base::span(candidate.ad_render_url_hash));
auto ad_it = std::ranges::find_if(
*group.ads, [&group, &ad_hash](const blink::InterestGroup::Ad& ad) {
return blink::HashedKAnonKeyForAdBid(group, ad.render_url()) == ad_hash;
});
if (ad_it == group.ads->end()) {
return std::nullopt;
}
result.ad_render_url = GURL(ad_it->render_url());
if (!result.ad_render_url.is_valid()) {
return std::nullopt;
}
std::string_view reporting_hash =
base::as_string_view(base::span(candidate.reporting_id_hash));
if (blink::HashedKAnonKeyForAdNameReporting(group, *ad_it, std::nullopt) !=
reporting_hash) {
if (!ad_it->selectable_buyer_and_seller_reporting_ids) {
return std::nullopt;
}
auto match = std::ranges::find_if(
*ad_it->selectable_buyer_and_seller_reporting_ids,
[&group, &ad_it,
&reporting_hash](const std::string& selected_reporting_id) {
return blink::HashedKAnonKeyForAdNameReporting(
group, *ad_it, selected_reporting_id) == reporting_hash;
});
if (match == ad_it->selectable_buyer_and_seller_reporting_ids->end()) {
return std::nullopt;
}
result.selected_buyer_and_seller_reporting_id = *match;
}
result.buyer_reporting_id = ad_it->buyer_reporting_id;
result.buyer_and_seller_reporting_id = ad_it->buyer_and_seller_reporting_id;
if (!candidate.ad_component_render_urls_hash.empty() &&
!group.ad_components) {
return std::nullopt;
}
for (const auto& component_hash : candidate.ad_component_render_urls_hash) {
std::string_view component_ad_hash =
base::as_string_view(base::span(component_hash));
auto component_ad_it = std::ranges::find_if(
*group.ad_components,
[&component_ad_hash](const blink::InterestGroup::Ad& ad) {
return blink::HashedKAnonKeyForAdComponentBid(ad.render_url()) ==
component_ad_hash;
});
if (component_ad_it == group.ads->end()) {
return std::nullopt;
}
result.ad_components.emplace_back(component_ad_it->render_url());
}
return result;
}
}
InterestGroupAuction::PostAuctionSignals::PostAuctionSignals() = default;
InterestGroupAuction::PostAuctionSignals::PostAuctionSignals(
double winning_bid,
std::optional<blink::AdCurrency> winning_bid_currency,
bool made_winning_bid)
: winning_bid(winning_bid),
winning_bid_currency(std::move(winning_bid_currency)),
made_winning_bid(made_winning_bid) {}
InterestGroupAuction::PostAuctionSignals::PostAuctionSignals(
double winning_bid,
std::optional<blink::AdCurrency> winning_bid_currency,
bool made_winning_bid,
double highest_scoring_other_bid,
std::optional<blink::AdCurrency> highest_scoring_other_bid_currency,
bool made_highest_scoring_other_bid)
: winning_bid(winning_bid),
winning_bid_currency(std::move(winning_bid_currency)),
made_winning_bid(made_winning_bid),
highest_scoring_other_bid(highest_scoring_other_bid),
highest_scoring_other_bid_currency(
std::move(highest_scoring_other_bid_currency)),
made_highest_scoring_other_bid(made_highest_scoring_other_bid) {}
InterestGroupAuction::PostAuctionSignals::~PostAuctionSignals() = default;
void InterestGroupAuction::PostAuctionSignals::FillWinningBidInfo(
const url::Origin& owner,
std::optional<url::Origin> winner_owner,
double winning_bid,
std::optional<double> winning_bid_in_seller_currency,
const std::optional<blink::AdCurrency>& seller_currency,
bool& out_made_winning_bid,
double& out_winning_bid,
std::optional<blink::AdCurrency>& out_winning_bid_currency) {
out_made_winning_bid = false;
if (winner_owner.has_value()) {
out_made_winning_bid = owner == *winner_owner;
}
if (seller_currency.has_value()) {
out_winning_bid = winning_bid_in_seller_currency.value_or(0.0);
out_winning_bid_currency = *seller_currency;
} else {
out_winning_bid = winning_bid;
out_winning_bid_currency = std::nullopt;
}
}
void InterestGroupAuction::PostAuctionSignals::
FillRelevantHighestScoringOtherBidInfo(
const url::Origin& owner,
std::optional<url::Origin> highest_scoring_other_bid_owner,
double highest_scoring_other_bid,
std::optional<double> highest_scoring_other_bid_in_seller_currency,
const std::optional<blink::AdCurrency>& seller_currency,
bool& out_made_highest_scoring_other_bid,
double& out_highest_scoring_other_bid,
std::optional<blink::AdCurrency>&
out_highest_scoring_other_bid_currency) {
out_made_highest_scoring_other_bid = false;
if (highest_scoring_other_bid_owner.has_value()) {
DCHECK_GT(highest_scoring_other_bid, 0);
out_made_highest_scoring_other_bid =
owner == highest_scoring_other_bid_owner.value();
}
if (seller_currency.has_value()) {
out_highest_scoring_other_bid =
highest_scoring_other_bid_in_seller_currency.value_or(0);
out_highest_scoring_other_bid_currency = *seller_currency;
} else {
out_highest_scoring_other_bid = highest_scoring_other_bid;
out_highest_scoring_other_bid_currency = std::nullopt;
}
}
InterestGroupAuction::BidState::~BidState() {
if (trace_id.has_value()) {
EndTracing();
}
}
InterestGroupAuction::BidState::BidState(BidState&&) = default;
InterestGroupAuction::BidState::BidState(
const SingleStorageInterestGroup&& bidder)
: bidder(std::move(bidder)) {}
void InterestGroupAuction::BidState::BeginTracing() {
DCHECK(!trace_id.has_value());
trace_id = base::trace_event::GetNextGlobalTraceId();
const blink::InterestGroup& interest_group = bidder->interest_group;
TRACE_EVENT_BEGIN("fledge", "bid", perfetto::Track(*trace_id), "bidding_url",
interest_group.bidding_url, "interest_group_name",
interest_group.name);
}
void InterestGroupAuction::BidState::EndTracing() {
DCHECK(trace_id.has_value());
TRACE_EVENT_END("fledge", perfetto::Track(*trace_id));
trace_id = std::nullopt;
}
InterestGroupAuction::Bid::Bid(
auction_worklet::mojom::BidRole bid_role,
std::string ad_metadata,
double bid,
std::optional<blink::AdCurrency> bid_currency,
std::optional<double> ad_cost,
blink::AdDescriptor ad_descriptor,
std::vector<ComponentAdInfo> selected_ad_components,
std::optional<uint16_t> modeling_signals,
std::optional<std::string> aggregate_win_signals,
base::TimeDelta bid_duration,
std::optional<uint32_t> bidding_signals_data_version,
const blink::InterestGroup::Ad* bid_ad,
std::optional<std::string> selected_buyer_and_seller_reporting_id,
BidState* bid_state,
InterestGroupAuction* auction)
: bid_role(bid_role),
ad_metadata(std::move(ad_metadata)),
bid(bid),
bid_currency(std::move(bid_currency)),
ad_cost(std::move(ad_cost)),
ad_descriptor(std::move(ad_descriptor)),
selected_ad_components(std::move(selected_ad_components)),
modeling_signals(modeling_signals),
aggregate_win_signals(std::move(aggregate_win_signals)),
bid_duration(bid_duration),
bidding_signals_data_version(bidding_signals_data_version),
selected_buyer_and_seller_reporting_id(
std::move(selected_buyer_and_seller_reporting_id)),
interest_group(&bid_state->bidder->interest_group),
bid_ad(bid_ad),
bid_state(bid_state),
auction(auction) {
DCHECK(IsValidBid(bid));
}
InterestGroupAuction::Bid::Bid(Bid&) = default;
InterestGroupAuction::Bid::~Bid() {
if (trace_id.has_value()) {
EndTracingForScoring();
}
}
void InterestGroupAuction::Bid::BeginTracingForScoring() {
DCHECK(!trace_id.has_value());
trace_id = base::trace_event::GetNextGlobalTraceId();
TRACE_EVENT_BEGIN("fledge", "score", perfetto::Track(*trace_id),
"bidding_url", interest_group->bidding_url,
"interest_group_name", interest_group->name);
}
void InterestGroupAuction::Bid::EndTracingForScoring() {
DCHECK(trace_id.has_value());
TRACE_EVENT_END("fledge", perfetto::Track(*trace_id));
trace_id = std::nullopt;
}
std::vector<GURL> InterestGroupAuction::Bid::GetAdComponentUrls() const {
std::vector<GURL> ad_component_urls;
ad_component_urls.reserve(selected_ad_components.size());
std::ranges::transform(selected_ad_components,
std::back_inserter(ad_component_urls),
[](const ComponentAdInfo& component_info) {
return component_info.ad_descriptor.url;
});
return ad_component_urls;
}
std::vector<auction_worklet::mojom::CreativeInfoWithoutOwnerPtr>
InterestGroupAuction::Bid::GetAdComponentCreativeInfo() const {
std::vector<auction_worklet::mojom::CreativeInfoWithoutOwnerPtr> out;
out.reserve(selected_ad_components.size());
for (const auto& in : selected_ad_components) {
out.push_back(auction_worklet::mojom::CreativeInfoWithoutOwner::New(
in.ad_descriptor, in.ad->creative_scanning_metadata));
}
return out;
}
blink::AdDescriptor
InterestGroupAuction::Bid::GetAdDescriptorWithReplacements() {
std::vector<std::pair<std::string, std::string>> local_replacements;
for (const auto& replacement :
auction->GetDeprecatedRenderURLReplacements()) {
local_replacements.emplace_back(replacement.match, replacement.replacement);
}
GURL url_with_replacements = GURL(
SubstituteMappedStrings(ad_descriptor.url.spec(), local_replacements));
if (url_with_replacements.is_valid()) {
return blink::AdDescriptor(GURL(std::move(url_with_replacements)),
ad_descriptor.size);
}
return ad_descriptor;
}
std::vector<blink::AdDescriptor>
InterestGroupAuction::Bid::GetComponentAdDescriptorsWithReplacements() {
std::vector<blink::AdDescriptor> local_component_ad_descriptors;
std::vector<std::pair<std::string, std::string>> local_replacements;
for (const auto& replacement :
auction->GetDeprecatedRenderURLReplacements()) {
local_replacements.emplace_back(replacement.match, replacement.replacement);
}
for (auto& component_info : selected_ad_components) {
const blink::AdDescriptor& ad_component_descriptor =
component_info.ad_descriptor;
GURL url_with_replacements = GURL(SubstituteMappedStrings(
ad_component_descriptor.url.spec(), local_replacements));
if (url_with_replacements.is_valid()) {
local_component_ad_descriptors.emplace_back(
GURL(std::move(url_with_replacements)), ad_component_descriptor.size);
} else {
local_component_ad_descriptors.emplace_back(ad_component_descriptor);
}
}
return local_component_ad_descriptors;
}
InterestGroupAuction::ScoredBid::ScoredBid(
double score,
std::optional<uint32_t> scoring_signals_data_version,
std::unique_ptr<Bid> bid,
std::optional<double> bid_in_seller_currency,
auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr
component_auction_modified_bid_params)
: score(score),
scoring_signals_data_version(scoring_signals_data_version),
bid(std::move(bid)),
bid_in_seller_currency(std::move(bid_in_seller_currency)),
component_auction_modified_bid_params(
std::move(component_auction_modified_bid_params)) {
DCHECK_GT(score, 0);
}
InterestGroupAuction::ScoredBid::~ScoredBid() = default;
class InterestGroupAuction::BuyerHelper
: public auction_worklet::mojom::GenerateBidClient {
public:
BuyerHelper(InterestGroupAuction* auction,
std::vector<SingleStorageInterestGroup>&& interest_groups)
: auction_(auction),
owner_(interest_groups[0]->interest_group.owner),
multi_bid_limit_(auction_->GetBuyerMultiBidLimit(owner_)) {
DCHECK(!interest_groups.empty());
for (SingleStorageInterestGroup& bidder : interest_groups) {
double priority = bidder->interest_group.priority;
if (bidder->interest_group.priority_vector &&
!bidder->interest_group.priority_vector->empty()) {
priority = CalculateInterestGroupPriority(
*auction_->config_, *bidder, auction_->auction_start_time_,
*bidder->interest_group.priority_vector);
if (priority < 0) {
auction_->auction_metrics_recorder_
->RecordBidFilteredDuringInterestGroupLoad();
continue;
}
}
if (bidder->interest_group.enable_bidding_signals_prioritization) {
enable_bidding_signals_prioritization_ = true;
}
auto state = std::make_unique<BidState>(std::move(bidder));
state->calculated_priority = priority;
bid_states_.emplace_back(std::move(state));
}
size_limit_ = auction_->config_->non_shared_params.all_buyers_group_limit;
const auto limit_iter =
auction_->config_->non_shared_params.per_buyer_group_limits.find(
owner_);
if (limit_iter !=
auction_->config_->non_shared_params.per_buyer_group_limits.cend()) {
size_limit_ = static_cast<size_t>(limit_iter->second);
}
size_limit_ = std::min(bid_states_.size(), size_limit_);
if (size_limit_ == 0) {
bid_states_.clear();
return;
}
if (!enable_bidding_signals_prioritization_) {
ApplySizeLimitAndSort();
} else {
SortByPriorityAndGroupByJoinOrigin();
}
std::set<AuctionWorkletManager::WorkletKey> seen_keys;
for (auto it = bid_states_.rbegin(); it != bid_states_.rend(); ++it) {
std::unique_ptr<BidState>& bid_state = *it;
auto [iter, success] =
seen_keys.insert(auction_->BidderWorkletKey(*bid_state));
bid_state->send_pending_trusted_signals_after_generate_bid = success;
}
}
~BuyerHelper() override = default;
void StartGeneratingBids() {
DCHECK(!bid_states_.empty());
DCHECK_EQ(0, num_outstanding_bids_);
num_outstanding_bids_ = bid_states_.size();
num_outstanding_begin_generate_bid_calls_ = num_outstanding_bids_;
num_outstanding_bidding_signals_received_calls_ = num_outstanding_bids_;
start_generating_bids_time_ = base::TimeTicks::Now();
size_t number_of_bidder_threads = 1;
if (num_outstanding_bids_ > 0) {
number_of_bidder_threads += static_cast<int>(
features::kFledgeBidderWorkletThreadPoolSizeLogarithmicScalingFactor
.Get() *
std::log10(num_outstanding_bids_));
}
number_of_bidder_threads = std::min<size_t>(number_of_bidder_threads, 10);
for (auto& bid_state : bid_states_) {
bid_state->BeginTracing();
TRACE_EVENT_BEGIN("fledge", "bidder_worklet_generate_bid",
perfetto::Track(*bid_state->trace_id));
auto worklet_key = auction_->BidderWorkletKey(*bid_state);
auction_->auction_metrics_recorder_->ReportBidderWorkletKey(worklet_key);
auction_->auction_worklet_manager_->RequestWorkletByKey(
worklet_key, auction_->devtools_auction_id_,
base::OnceClosure(),
base::BindOnce(&BuyerHelper::OnBidderWorkletReceived,
base::Unretained(this), bid_state.get()),
base::BindOnce(&BuyerHelper::OnBidderWorkletGenerateBidFatalError,
base::Unretained(this), bid_state.get()),
bid_state->worklet_handle, number_of_bidder_threads,
auction_->auction_metrics_recorder_, bid_state->trace_id);
}
}
void OnBiddingSignalsReceived(
const base::flat_map<std::string, double>& priority_vector,
base::TimeDelta trusted_signals_fetch_latency,
std::optional<base::TimeDelta> update_if_older_than,
base::OnceClosure resume_generate_bid_callback) override {
BidState* state = generate_bid_client_receiver_set_.current_context();
const blink::InterestGroup& interest_group = state->bidder->interest_group;
state->pa_timings(PrivateAggregationPhase::kBidder).signals_fetch_time =
trusted_signals_fetch_latency;
auction_->ReportTrustedSignalsFetchLatency(interest_group,
trusted_signals_fetch_latency);
auction_->HandleUpdateIfOlderThan(interest_group.owner, interest_group.name,
update_if_older_than);
std::optional<double> new_priority;
if (!priority_vector.empty()) {
new_priority = CalculateInterestGroupPriority(
*auction_->config_, *(state->bidder), auction_->auction_start_time_,
priority_vector,
(interest_group.priority_vector &&
!interest_group.priority_vector->empty())
? state->calculated_priority
: std::optional<double>());
if (*new_priority < 0) {
auction_->auction_metrics_recorder_
->RecordBidFilteredDuringReprioritization();
}
}
OnBiddingSignalsReceivedInternal(state, new_priority,
std::move(resume_generate_bid_callback));
}
void OnGenerateBidComplete(
std::vector<auction_worklet::mojom::BidderWorkletBidPtr> mojo_bids,
std::optional<uint32_t> bidding_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
std::optional<double> set_priority,
base::flat_map<std::string,
auction_worklet::mojom::PrioritySignalsDoublePtr>
update_priority_signals_overrides,
PrivateAggregationRequests pa_requests,
PrivateAggregationRequests non_kanon_pa_requests,
RealTimeReportingContributions real_time_contributions,
auction_worklet::mojom::BidderTimingMetricsPtr generate_bid_metrics,
auction_worklet::mojom::GenerateBidDependencyLatenciesPtr
generate_bid_dependency_latencies,
auction_worklet::mojom::RejectReason reject_reason,
const std::vector<std::string>& errors) override {
BidState* state = generate_bid_client_receiver_set_.current_context();
const blink::InterestGroup& interest_group = state->bidder->interest_group;
state->pa_timings(PrivateAggregationPhase::kBidder).script_run_time =
generate_bid_metrics->script_latency;
auction_->ReportBiddingLatency(interest_group,
generate_bid_metrics->script_latency);
if (generate_bid_metrics->js_fetch_latency.has_value()) {
code_fetch_time_.RecordLatency(*generate_bid_metrics->js_fetch_latency);
}
if (generate_bid_metrics->wasm_fetch_latency.has_value()) {
code_fetch_time_.RecordLatency(*generate_bid_metrics->wasm_fetch_latency);
}
if (generate_bid_metrics->script_timed_out) {
++bidder_scripts_timed_out_;
}
auction_->auction_metrics_recorder_->RecordBidForOneInterestGroupLatency(
base::TimeTicks::Now() - start_generating_bids_time_);
auction_->auction_metrics_recorder_->RecordGenerateBidDependencyLatencies(
*generate_bid_dependency_latencies);
OnGenerateBidCompleteInternal(
state, std::move(mojo_bids), bidding_signals_data_version,
debug_loss_report_url, debug_win_report_url, set_priority,
std::move(update_priority_signals_overrides), std::move(pa_requests),
std::move(non_kanon_pa_requests), std::move(real_time_contributions),
reject_reason, errors);
}
void SetForDebuggingOnlyInCooldownOrLockout(
bool for_debugging_only_in_cooldown_or_lockout) {
for (auto& bid_state : bid_states_) {
bid_state->bidder->bidding_browser_signals
->for_debugging_only_in_cooldown_or_lockout =
for_debugging_only_in_cooldown_or_lockout;
}
}
void ClosePipes() {
weak_ptr_factory_.InvalidateWeakPtrs();
for (auto& bid_state : bid_states_) {
CloseBidStatePipes(*bid_state);
}
DCHECK(generate_bid_client_receiver_set_.empty());
cumulative_buyer_timeout_timer_.Stop();
stop_measuring_cumulative_time_ = base::TimeTicks::Now();
}
bool has_potential_bidder() const { return !bid_states_.empty(); }
size_t num_potential_bidders() const { return bid_states_.size(); }
const url::Origin& owner() const { return owner_; }
const PrivateAggregationParticipantData& buyer_metrics() const {
return buyer_metrics_;
}
void FillInBidderParticipantDataMetrics() {
if (code_fetch_time_.GetNumRecords() != 0) {
buyer_metrics_.average_code_fetch_time =
code_fetch_time_.GetMeanLatency();
}
buyer_metrics_.participating_interest_group_count = bid_states_.size();
buyer_metrics_.percent_scripts_timeout =
PercentMetric(bidder_scripts_timed_out_,
buyer_metrics_.participating_interest_group_count);
buyer_metrics_.percent_igs_cumulative_timeout =
PercentMetric(num_bids_affected_by_cumulative_timeout_,
buyer_metrics_.participating_interest_group_count);
if (cumulative_buyer_timeout_triggered_) {
buyer_metrics_.cumulative_buyer_time =
*cumulative_buyer_timeout_ + base::Milliseconds(1000);
} else if (cumulative_buyer_timeout_.has_value() &&
stop_measuring_cumulative_time_ >=
start_measuring_cumulative_time_) {
buyer_metrics_.cumulative_buyer_time = std::min(
stop_measuring_cumulative_time_ - start_measuring_cumulative_time_,
*cumulative_buyer_timeout_);
}
}
void SetStorageMetrics(int regular_igs,
int negative_igs,
size_t igs_storage_used) {
buyer_metrics_.regular_igs = SoftBound(
regular_igs, InterestGroupStorage::MaxOwnerRegularInterestGroups());
buyer_metrics_.percent_regular_igs_quota_used = PercentMetric(
regular_igs, InterestGroupStorage::MaxOwnerRegularInterestGroups());
buyer_metrics_.negative_igs = SoftBound(
negative_igs, InterestGroupStorage::MaxOwnerNegativeInterestGroups());
buyer_metrics_.percent_negative_igs_quota_used = PercentMetric(
negative_igs, InterestGroupStorage::MaxOwnerNegativeInterestGroups());
buyer_metrics_.igs_storage_used = SoftBound(
igs_storage_used, InterestGroupStorage::MaxOwnerStorageSize());
buyer_metrics_.percent_igs_storage_quota_used = PercentMetric(
igs_storage_used, InterestGroupStorage::MaxOwnerStorageSize());
}
void GetInterestGroupsThatBidAndReportBidCounts(
blink::InterestGroupSet& interest_groups) const {
size_t bid_count = 0;
for (const auto& bid_state : bid_states_) {
if (bid_state->made_bid) {
interest_groups.emplace(bid_state->bidder->interest_group.owner,
bid_state->bidder->interest_group.name);
bid_count++;
}
}
for (const auto& bid_state : bid_states_) {
if (auction_->ReportBidCount(bid_state->bidder->interest_group,
bid_count)) {
break;
}
}
}
void TakeDebugReportUrls(
const BidState* winner,
const PostAuctionSignals& signals,
const std::optional<PostAuctionSignals>& top_level_signals,
const url::Origin& seller,
const std::optional<url::Origin>& top_level_seller,
std::vector<GURL>& debug_win_report_urls,
std::vector<GURL>& debug_loss_report_urls) {
for (std::unique_ptr<BidState>& bid_state : bid_states_) {
TakeDebugReportUrlsForBidState(
bid_state, winner, signals, top_level_signals, owner_, seller,
top_level_seller, auction_->debug_report_lockout_and_cooldowns_,
auction_->new_debug_report_lockout_and_cooldowns_,
debug_win_report_urls, debug_loss_report_urls);
}
}
void TakePrivateAggregationRequests(
const BidState* winner,
const BidState* non_kanon_winner,
const PostAuctionSignals& signals,
const std::optional<PostAuctionSignals>& top_level_signals,
const BidState* non_top_level_seller_once_rep,
const BidState* top_level_seller_once_rep,
const PrivateAggregationParticipantData* non_top_level_seller_data,
const PrivateAggregationParticipantData* top_level_seller_data,
std::map<PrivateAggregationKey, FinalizedPrivateAggregationRequests>&
private_aggregation_requests_reserved,
std::map<std::string, FinalizedPrivateAggregationRequests>&
private_aggregation_requests_non_reserved) {
if (bid_states_.empty()) {
return;
}
FillInBidderParticipantDataMetrics();
PrivateAggregationReservedOnceReps reps;
PrivateAggregationAllParticipantsDataPtrs all_participant_data;
reps[static_cast<size_t>(PrivateAggregationPhase::kTopLevelSeller)] =
top_level_seller_once_rep;
all_participant_data[static_cast<size_t>(
PrivateAggregationPhase::kTopLevelSeller)] = top_level_seller_data;
reps[static_cast<size_t>(PrivateAggregationPhase::kNonTopLevelSeller)] =
non_top_level_seller_once_rep;
all_participant_data[static_cast<size_t>(
PrivateAggregationPhase::kNonTopLevelSeller)] =
non_top_level_seller_data;
all_participant_data[static_cast<size_t>(
PrivateAggregationPhase::kBidder)] = &buyer_metrics_;
if (bid_states_.size() != num_bids_affected_by_cumulative_timeout_) {
CHECK_LT(num_bids_affected_by_cumulative_timeout_, bid_states_.size());
uint64_t skip = base::RandGenerator(
bid_states_.size() - num_bids_affected_by_cumulative_timeout_);
uint64_t pos = 0;
while (true) {
while (bid_states_[pos]->affected_by_cumulative_timeout) {
++pos;
}
if (skip == 0) {
break;
}
--skip;
++pos;
}
reps[static_cast<size_t>(PrivateAggregationPhase::kBidder)] =
bid_states_[pos].get();
} else {
reps[static_cast<size_t>(PrivateAggregationPhase::kBidder)] = nullptr;
}
for (std::unique_ptr<BidState>& state : bid_states_) {
TakePrivateAggregationRequestsForBidState(
state, auction_->parent_, winner,
non_kanon_winner, reps, signals, top_level_signals,
all_participant_data, private_aggregation_requests_reserved,
private_aggregation_requests_non_reserved);
}
}
void TakeRealTimeContributions(
std::map<url::Origin,
InterestGroupAuction::RealTimeReportingContributions>&
contributions) {
for (std::unique_ptr<BidState>& state : bid_states_) {
TakeRealTimeContributionsForBidState(*state, contributions);
}
}
void NotifyConfigPromisesResolved() {
DCHECK(auction_->config_promises_resolved_);
NotifyConfigDependencyResolved();
}
void NotifyConfigDependencyResolved() {
if (num_outstanding_bids_ == 0) {
return;
}
MaybeStartCumulativeTimeoutTimer();
for (const auto& bid_state : bid_states_) {
FinishGenerateBidIfReady(bid_state.get());
}
}
std::unique_ptr<Bid> TryToCreateBidFromServerResponse(
const blink::InterestGroupKey& group_key,
auction_worklet::mojom::BidRole bid_role,
double bid,
const std::optional<blink::AdCurrency>& bid_currency,
const std::optional<std::string>& ad_metadata,
const std::optional<std::string>& buyer_reporting_id,
const std::optional<std::string>& buyer_and_seller_reporting_id,
const std::optional<std::string>& selected_buyer_and_seller_reporting_id,
const base::optional_ref<BiddingAndAuctionResponse::KAnonJoinCandidate>
winner_hashes,
blink::AdDescriptor ad_descriptor,
std::vector<blink::AdDescriptor> ad_component_descriptors,
std::map<PrivateAggregationPhaseKey, PrivateAggregationRequests>
component_win_pagg_requests,
std::map<PrivateAggregationKey, FinalizedPrivateAggregationRequests>
server_filtered_pagg_requests_reserved,
std::map<std::string, FinalizedPrivateAggregationRequests>
server_filtered_pagg_requests_non_reserved,
PrivateAggregationRequests non_kanon_private_aggregation_requests,
std::map<BiddingAndAuctionResponse::DebugReportKey, std::optional<GURL>>
component_win_debugging_only_reports,
std::map<url::Origin, std::vector<GURL>>
server_filtered_debugging_only_reports) {
DCHECK_EQ(owner_, group_key.owner);
BidState* bid_state = nullptr;
for (auto& bid_state_ref : bid_states_) {
if (bid_state_ref->bidder->interest_group.name == group_key.name) {
bid_state = bid_state_ref.get();
break;
}
}
if (!bid_state) {
DCHECK(false);
return nullptr;
}
bid_state->made_bid = true;
bid_state->is_from_server_response = true;
if (winner_hashes) {
bid_state->kanon_keys.emplace(
base::as_string_view(base::span(winner_hashes->ad_render_url_hash)));
for (const auto& component_hash :
winner_hashes->ad_component_render_urls_hash) {
bid_state->kanon_keys.emplace(
base::as_string_view(base::span(component_hash)));
}
bid_state->kanon_keys.emplace(
base::as_string_view(base::span(winner_hashes->reporting_id_hash)));
}
const blink::InterestGroup& interest_group =
bid_state->bidder->interest_group;
const blink::InterestGroup::Ad* matching_ad = FindMatchingAd(
interest_group.ads, bid_state->kanon_keys, interest_group, bid_role,
selected_buyer_and_seller_reporting_id,
false, ad_descriptor);
if (!matching_ad) {
return nullptr;
}
if (bid_role == auction_worklet::mojom::BidRole::kEnforcedKAnon) {
if (!bid_state->kanon_keys.contains(
blink::HashedKAnonKeyForAdNameReporting(
interest_group, *matching_ad,
selected_buyer_and_seller_reporting_id))) {
return nullptr;
}
}
if (selected_buyer_and_seller_reporting_id.has_value() &&
!IsSelectedReportingIdValid(
matching_ad->selectable_buyer_and_seller_reporting_ids,
*selected_buyer_and_seller_reporting_id,
false)) {
return nullptr;
}
if (buyer_and_seller_reporting_id &&
matching_ad->buyer_and_seller_reporting_id !=
*buyer_and_seller_reporting_id) {
return nullptr;
}
if (buyer_reporting_id &&
matching_ad->buyer_reporting_id != *buyer_reporting_id) {
return nullptr;
}
std::vector<Bid::ComponentAdInfo> selected_ad_components;
selected_ad_components.reserve(ad_component_descriptors.size());
for (auto& ad_component_descriptor : ad_component_descriptors) {
const blink::InterestGroup::Ad* matching_ad_component = nullptr;
if (interest_group.ad_components.has_value()) {
matching_ad_component = FindMatchingAd(
interest_group.ad_components, bid_state->kanon_keys, interest_group,
bid_role,
std::nullopt,
true, ad_component_descriptor);
}
if (!matching_ad_component) {
return nullptr;
}
Bid::ComponentAdInfo component_ad_info = {
std::move(ad_component_descriptor), matching_ad_component};
selected_ad_components.push_back(std::move(component_ad_info));
}
bid_state->private_aggregation_requests =
std::move(component_win_pagg_requests);
bid_state->server_filtered_pagg_requests_reserved =
std::move(server_filtered_pagg_requests_reserved);
bid_state->server_filtered_pagg_requests_non_reserved =
std::move(server_filtered_pagg_requests_non_reserved);
for (auto& non_kanon_private_aggregation_request :
non_kanon_private_aggregation_requests) {
bid_state->non_kanon_private_aggregation_requests.emplace_back(
std::move(non_kanon_private_aggregation_request));
}
for (auto& [debug_key, maybeReportUrl] :
component_win_debugging_only_reports) {
if (debug_key.is_seller_report) {
if (debug_key.is_win_report) {
bid_state->seller_debug_win_report_url = maybeReportUrl;
} else {
bid_state->seller_debug_loss_report_url = maybeReportUrl;
}
} else {
if (debug_key.is_win_report) {
bid_state->bidder_debug_win_report_url = maybeReportUrl;
} else {
bid_state->bidder_debug_loss_report_url = maybeReportUrl;
}
}
}
bid_state->server_filtered_debugging_only_reports =
std::move(server_filtered_debugging_only_reports);
return std::make_unique<Bid>(
bid_role, ad_metadata.value_or("null"), bid, bid_currency,
std::nullopt, std::move(ad_descriptor),
std::move(selected_ad_components),
std::nullopt,
std::nullopt,
base::Seconds(0),
std::nullopt, matching_ad,
selected_buyer_and_seller_reporting_id, bid_state, auction_);
}
void OnBuyerTkvPromiseResolved() {
CHECK(!GetTkvSignals()->is_promise());
for (auto& bid_state : bid_states_) {
if (!bid_state->waiting_for_tkv_promise) {
continue;
}
bid_state->waiting_for_tkv_promise = false;
MaybeBeginGenerateBid(bid_state.get());
}
}
private:
void SortByPriorityAndGroupByJoinOrigin() {
std::sort(bid_states_.begin(), bid_states_.end(),
BidStatesDescByPriorityAndGroupByJoinOrigin());
}
void ApplySizeLimitAndSort() {
SortByPriorityAndGroupByJoinOrigin();
double min_priority = bid_states_[size_limit_ - 1]->calculated_priority;
auto rand_begin = std::lower_bound(bid_states_.begin(), bid_states_.end(),
min_priority, BidStatesDescByPriority());
auto rand_end = std::upper_bound(rand_begin, bid_states_.end(),
min_priority, BidStatesDescByPriority());
base::RandomShuffle(rand_begin, rand_end);
for (size_t i = size_limit_; i < bid_states_.size(); ++i) {
CloseBidStatePipes(*bid_states_[i]);
if (bid_states_[i]->trace_id) {
TRACE_EVENT_INSTANT("fledge", "bid_exceeds_size_limit",
perfetto::Track(*bid_states_[i]->trace_id));
}
}
auction_->auction_metrics_recorder_->RecordBidsFilteredByPerBuyerLimits(
bid_states_.size() - size_limit_);
bid_states_.resize(size_limit_);
std::sort(rand_begin, bid_states_.end(),
BidStatesDescByPriorityAndGroupByJoinOrigin());
}
void OnBidderWorkletGenerateBidFatalError(
BidState* bid_state,
AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
auction_->auction_metrics_recorder_
->RecordBidAbortedByBidderWorkletFatalError();
if (fatal_error_type ==
AuctionWorkletManager::FatalErrorType::kWorkletCrash) {
OnFatalError(
bid_state,
{base::StrCat({bid_state->bidder->interest_group.bidding_url->spec(),
" crashed while trying to run generateBid()."})});
} else {
auction_->MaybeAddScriptFailureRealTimeContribution(
true, bid_state->bidder->interest_group.owner);
OnFatalError(bid_state, errors);
}
}
void OnFatalError(BidState* bid_state, std::vector<std::string> errors) {
auction_->errors_.insert(auction_->errors_.end(),
std::make_move_iterator(errors.begin()),
std::make_move_iterator(errors.end()));
if (!bid_state->begin_generate_bid_called) {
bid_state->bidding_signals_handle.reset();
bid_state->waiting_for_tkv_promise = false;
OnBeginGenerateBidCalled(bid_state);
}
if (!bid_state->bidding_signals_received) {
OnBiddingSignalsReceivedInternal(bid_state,
-1,
base::OnceClosure());
return;
}
OnGenerateBidCompleteInternal(
bid_state,
{},
std::nullopt,
std::nullopt,
std::nullopt,
std::nullopt,
{},
{},
{},
{},
auction_worklet::mojom::RejectReason::kNotAvailable,
{});
}
void OnBeginGenerateBidCalled(BidState* bid_state) {
DCHECK(!bid_state->begin_generate_bid_called);
bid_state->begin_generate_bid_called = true;
DCHECK_GT(num_outstanding_begin_generate_bid_calls_, 0);
--num_outstanding_begin_generate_bid_calls_;
if (num_outstanding_begin_generate_bid_calls_ > 0) {
return;
}
for (auto& other_bid_state : bid_states_) {
DCHECK(other_bid_state->begin_generate_bid_called);
if (other_bid_state->bidding_signals_handle) {
other_bid_state->bidding_signals_handle->StartFetch();
}
}
}
base::flat_set<std::string> ComputeKAnon(
const SingleStorageInterestGroup& storage_interest_group,
auction_worklet::mojom::KAnonymityBidMode kanon_mode) {
base::Time start_time = auction_->auction_start_time_;
if (IsKAnonDataExpired(storage_interest_group->last_k_anon_updated,
start_time)) {
return {};
}
return storage_interest_group->hashed_kanon_keys;
}
void OnBidderWorkletReceived(BidState* bid_state) {
if (!bidder_process_received_) {
bidder_process_received_ = true;
MaybeStartCumulativeTimeoutTimer();
}
MaybeBeginGenerateBid(bid_state);
}
void MaybeBeginGenerateBid(BidState* bid_state) {
DCHECK(!bid_state->waiting_for_tkv_promise);
const blink::InterestGroup& interest_group =
bid_state->bidder->interest_group;
if (NeedsBiddingSignalsFromCache(interest_group)) {
const blink::AuctionConfig::MaybePromiseJson* tkv_signals =
GetTkvSignals();
if (tkv_signals && tkv_signals->is_promise()) {
bid_state->waiting_for_tkv_promise = true;
return;
}
}
mojo::PendingAssociatedRemote<auction_worklet::mojom::GenerateBidClient>
pending_remote;
bid_state->generate_bid_client_receiver_id =
generate_bid_client_receiver_set_.Add(
this, pending_remote.InitWithNewEndpointAndPassReceiver(),
bid_state);
auction_worklet::mojom::KAnonymityBidMode kanon_mode =
auction_->kanon_mode();
bid_state->kanon_keys = ComputeKAnon(bid_state->bidder, kanon_mode);
SubresourceUrlBuilder* url_builder =
auction_->SubresourceUrlBuilderIfReady();
if (url_builder) {
bid_state->worklet_handle->AuthorizeSubresourceUrls(*url_builder);
bid_state->handled_direct_from_seller_signals_in_begin_generate_bid =
true;
}
bid_state->group_by_origin_id =
bid_state->worklet_handle->GetGroupByOriginKeyMapper()
.LookupGroupByOriginId(
bid_state->bidder,
bid_state->bidder->interest_group.execution_mode);
bool browser_signal_for_debugging_only_sampling = ShouldSampleDebugReport();
bid_state->worklet_handle->GetBidderWorklet()->BeginGenerateBid(
auction_worklet::mojom::BidderWorkletNonSharedParams::New(
interest_group.name,
interest_group.trusted_bidding_signals_slot_size_mode,
interest_group.enable_bidding_signals_prioritization,
interest_group.priority_vector, interest_group.execution_mode,
interest_group.update_url,
interest_group.trusted_bidding_signals_keys,
interest_group.max_trusted_bidding_signals_url_length,
interest_group.user_bidding_signals, interest_group.ads,
interest_group.ad_components,
KAnonKeysToMojom(bid_state->kanon_keys)),
MaybeRequestBiddingSignalsFromCache(*bid_state), kanon_mode,
bid_state->bidder->joining_origin,
GetDirectFromSellerPerBuyerSignals(url_builder, owner_),
GetDirectFromSellerAuctionSignals(url_builder),
auction_->config_->seller,
auction_->parent_ ? auction_->parent_->config_->seller
: std::optional<url::Origin>(),
(base::Time::Now() - bid_state->bidder->join_time)
.RoundToMultiple(base::Milliseconds(100)),
browser_signal_for_debugging_only_sampling,
bid_state->bidder->bidding_browser_signals.Clone(),
auction_->auction_start_time_, auction_->RequestedAdSize(),
multi_bid_limit_, bid_state->group_by_origin_id, *bid_state->trace_id,
std::move(pending_remote),
bid_state->bid_finalizer.BindNewEndpointAndPassReceiver());
if (bid_state->send_pending_trusted_signals_after_generate_bid) {
bid_state->worklet_handle->GetBidderWorklet()
->SendPendingSignalsRequests();
}
OnBeginGenerateBidCalled(bid_state);
FinishGenerateBidIfReady(bid_state);
}
bool NeedsBiddingSignalsFromCache(
const blink::InterestGroup& interest_group) {
return interest_group.trusted_bidding_signals_coordinator &&
interest_group.trusted_bidding_signals_url &&
auction_->interest_group_manager_->trusted_signals_cache();
}
auction_worklet::mojom::TrustedSignalsCacheKeyPtr
MaybeRequestBiddingSignalsFromCache(BidState& bid_state) {
const blink::InterestGroup& interest_group =
bid_state.bidder->interest_group;
if (!NeedsBiddingSignalsFromCache(interest_group)) {
return nullptr;
}
base::Value::Dict additional_params;
std::optional<int16_t> experiment_id =
InterestGroupAuction::GetBuyerExperimentId(*auction_->config_,
interest_group.owner);
if (experiment_id) {
additional_params.Set("experimentGroupId",
base::NumberToString(*experiment_id));
}
auto optional_pair = CreateTrustedBiddingSignalsSlotSizeParams(
*auction_->config_,
interest_group.trusted_bidding_signals_slot_size_mode);
if (optional_pair) {
additional_params.Set(optional_pair->first,
std::move(optional_pair->second));
}
int partition_id;
bid_state.bidding_signals_handle =
auction_->interest_group_manager_->trusted_signals_cache()
->RequestTrustedBiddingSignals(
auction_->url_loader_factory_,
auction_->auction_worklet_manager_->GetFrameTreeNodeID(),
auction_->devtools_auction_id_, auction_->main_frame_origin_,
auction_->ip_address_space_, interest_group.owner,
interest_group.name, interest_group.execution_mode,
bid_state.bidder->joining_origin,
*interest_group.trusted_bidding_signals_url,
*interest_group.trusted_bidding_signals_coordinator,
interest_group.trusted_bidding_signals_keys,
std::move(additional_params), GetTkvSignalsAsOptionalRef(),
partition_id);
return auction_worklet::mojom::TrustedSignalsCacheKey::New(
bid_state.bidding_signals_handle->compression_group_token(),
partition_id);
}
void FinishGenerateBidIfReady(BidState* bid_state) {
if (!auction_->config_promises_resolved_ ||
auction_->direct_from_seller_signals_header_ad_slot_pending_) {
return;
}
if (!bid_state->bid_finalizer.is_bound()) {
return;
}
CHECK(!bid_state->waiting_for_tkv_promise);
SubresourceUrlBuilder* url_builder =
auction_->SubresourceUrlBuilderIfReady();
if (url_builder) {
if (bid_state->handled_direct_from_seller_signals_in_begin_generate_bid) {
url_builder = nullptr;
} else {
bid_state->worklet_handle->AuthorizeSubresourceUrls(*url_builder);
}
}
bid_state->bid_finalizer->FinishGenerateBid(
auction_->config_->non_shared_params.auction_signals.value(),
GetPerBuyerSignals(*auction_->config_,
bid_state->bidder->interest_group.owner),
PerBuyerTimeout(owner_, *auction_->config_),
PerBuyerCurrency(owner_, *auction_->config_),
GetDirectFromSellerPerBuyerSignals(
url_builder, bid_state->bidder->interest_group.owner),
GetDirectFromSellerPerBuyerSignalsHeaderAdSlot(
*auction_->direct_from_seller_signals_header_ad_slot(),
bid_state->bidder->interest_group.owner),
GetDirectFromSellerAuctionSignals(url_builder),
GetDirectFromSellerAuctionSignalsHeaderAdSlot(
*auction_->direct_from_seller_signals_header_ad_slot()));
bid_state->bid_finalizer.reset();
}
void OnBiddingSignalsReceivedInternal(
BidState* state,
std::optional<double> new_priority,
base::OnceClosure resume_generate_bid_callback) {
DCHECK(!state->bidding_signals_received);
DCHECK_GT(num_outstanding_bids_, 0);
DCHECK_GT(num_outstanding_bidding_signals_received_calls_, 0);
DCHECK(resume_generate_bid_callback || *new_priority < 0);
state->bidding_signals_received = true;
--num_outstanding_bidding_signals_received_calls_;
bool bid_filtered = new_priority.has_value() && *new_priority < 0;
UMA_HISTOGRAM_BOOLEAN("Ads.InterestGroup.Auction.BidFiltered",
bid_filtered);
std::string previously_bid_uma_name =
bid_filtered
? "Ads.InterestGroup.Auction.FilteredInterestGroupPreviouslyBid"
: "Ads.InterestGroup.Auction.UnfilteredInterestGroupPreviouslyBid";
bool previously_bid = state->bidder->bidding_browser_signals->bid_count > 0;
base::UmaHistogramBoolean(previously_bid_uma_name, previously_bid);
if (bid_filtered) {
if (state->trace_id) {
TRACE_EVENT_INSTANT("fledge", "bid_filtered",
perfetto::Track(*state->trace_id));
}
bool other_bidders = (num_outstanding_bids_ > 1);
OnGenerateBidCompleteInternal(
state, {},
std::nullopt,
std::nullopt,
std::nullopt,
std::nullopt,
{},
{},
{},
{},
auction_worklet::mojom::RejectReason::kNotAvailable,
{});
if (!other_bidders) {
return;
}
if (!enable_bidding_signals_prioritization_) {
return;
}
} else {
if (new_priority.has_value()) {
state->calculated_priority = *new_priority;
}
if (!enable_bidding_signals_prioritization_) {
std::move(resume_generate_bid_callback).Run();
return;
}
state->resume_generate_bid_callback =
std::move(resume_generate_bid_callback);
}
DCHECK(enable_bidding_signals_prioritization_);
if (num_outstanding_bidding_signals_received_calls_ > 0) {
return;
}
for (size_t i = 0; i < bid_states_.size();) {
if (!bid_states_[i]->worklet_handle) {
DCHECK(!bid_states_[i]->generate_bid_client_receiver_id);
std::swap(bid_states_[i], bid_states_.back());
bid_states_.pop_back();
size_limit_ = std::min(size_limit_, bid_states_.size());
continue;
}
DCHECK(bid_states_[i]->resume_generate_bid_callback);
++i;
}
DCHECK_EQ(static_cast<size_t>(num_outstanding_bids_), bid_states_.size());
ApplySizeLimitAndSort();
num_outstanding_bids_ = bid_states_.size();
for (auto& pending_state : bid_states_) {
std::move(pending_state->resume_generate_bid_callback).Run();
}
}
void OnGenerateBidCompleteInternal(
BidState* state,
std::vector<auction_worklet::mojom::BidderWorkletBidPtr> mojo_bids,
std::optional<uint32_t> bidding_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
std::optional<double> set_priority,
base::flat_map<std::string,
auction_worklet::mojom::PrioritySignalsDoublePtr>
update_priority_signals_overrides,
PrivateAggregationRequests pa_requests,
PrivateAggregationRequests non_kanon_pa_requests,
RealTimeReportingContributions real_time_contributions,
auction_worklet::mojom::RejectReason reject_reason,
const std::vector<std::string>& errors) {
DCHECK(!state->made_bid);
DCHECK_GT(num_outstanding_bids_, 0);
DCHECK(!state->waiting_for_tkv_promise);
if (state->trace_id.has_value()) {
TRACE_EVENT_END("fledge", perfetto::Track(*state->trace_id));
}
const blink::InterestGroup& interest_group = state->bidder->interest_group;
if (set_priority.has_value()) {
auction_->interest_group_manager_->SetInterestGroupPriority(
blink::InterestGroupKey(interest_group.owner, interest_group.name),
set_priority.value());
}
if (!update_priority_signals_overrides.empty()) {
if (std::ranges::any_of(
update_priority_signals_overrides, [](const auto& pair) {
return pair.second && !std::isfinite(pair.second->value);
})) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Invalid priority signals overrides");
} else {
auction_->interest_group_manager_->UpdateInterestGroupPriorityOverrides(
blink::InterestGroupKey(interest_group.owner, interest_group.name),
std::move(update_priority_signals_overrides));
}
}
if (!ValidateBidderPrivateAggregationRequests(
generate_bid_client_receiver_set_, pa_requests,
non_kanon_pa_requests)) {
mojo_bids.clear();
pa_requests.clear();
non_kanon_pa_requests.clear();
}
auction_->MaybeLogPrivateAggregationWebFeatures(pa_requests);
if (!pa_requests.empty()) {
PrivateAggregationPhaseKey agg_key = {
interest_group.owner, PrivateAggregationPhase::kBidder,
interest_group.aggregation_coordinator_origin};
PrivateAggregationRequests& pa_requests_for_bidder =
state->private_aggregation_requests[std::move(agg_key)];
pa_requests_for_bidder.insert(pa_requests_for_bidder.end(),
std::move_iterator(pa_requests.begin()),
std::move_iterator(pa_requests.end()));
}
if (!non_kanon_pa_requests.empty()) {
PrivateAggregationRequests& non_kanon_pa_requests_for_bidder =
state->non_kanon_private_aggregation_requests;
non_kanon_pa_requests_for_bidder.insert(
non_kanon_pa_requests_for_bidder.end(),
std::move_iterator(non_kanon_pa_requests.begin()),
std::move_iterator(non_kanon_pa_requests.end()));
}
if (base::FeatureList::IsEnabled(
blink::features::kFledgeRealTimeReporting)) {
if (!std::ranges::all_of(real_time_contributions,
HasValidRealTimeBucket)) {
mojo_bids.clear();
real_time_contributions.clear();
generate_bid_client_receiver_set_.ReportBadMessage(
"Invalid real time reporting bucket");
} else if (!std::ranges::all_of(real_time_contributions,
HasValidRealTimePriorityWeight)) {
mojo_bids.clear();
real_time_contributions.clear();
generate_bid_client_receiver_set_.ReportBadMessage(
"Invalid real time reporting priority weight");
} else if (auction_->IsBuyerOptedInToRealTimeReporting(
interest_group.owner)) {
RealTimeReportingContributions& real_time_contributions_for_origin =
state->real_time_contributions[interest_group.owner];
if (!real_time_contributions.empty()) {
real_time_contributions_for_origin.insert(
real_time_contributions_for_origin.end(),
std::move_iterator(real_time_contributions.begin()),
std::move_iterator(real_time_contributions.end()));
}
}
}
auction_->errors_.insert(auction_->errors_.end(), errors.begin(),
errors.end());
std::vector<std::unique_ptr<Bid>> bids;
auto kanon_mode = auction_->kanon_mode_;
if (!IsValidMultiBid(kanon_mode, auction_->auction_metrics_recorder_,
mojo_bids, multi_bid_limit_)) {
mojo_bids.clear();
generate_bid_client_receiver_set_.ReportBadMessage(
"Too many bids or wrong roles");
}
if (reject_reason != auction_worklet::mojom::RejectReason::kNotAvailable &&
reject_reason !=
auction_worklet::mojom::RejectReason::kWrongGenerateBidCurrency &&
reject_reason !=
auction_worklet::mojom::RejectReason::kMultiBidLimitExceeded) {
mojo_bids.clear();
state->reject_reason =
auction_worklet::mojom::RejectReason::kNotAvailable;
generate_bid_client_receiver_set_.ReportBadMessage(
"Invalid bid reject_reason");
} else {
state->reject_reason = reject_reason;
}
if (mojo_bids.empty()) {
auction_->auction_metrics_recorder_->RecordInterestGroupWithNoBids();
} else if (mojo_bids.size() == 1) {
if (mojo_bids[0]->bid_role ==
auction_worklet::mojom::BidRole::kBothKAnonModes) {
auction_->auction_metrics_recorder_
->RecordInterestGroupWithSameBidForKAnonAndNonKAnon();
} else {
auction_->auction_metrics_recorder_
->RecordInterestGroupWithOnlyNonKAnonBid();
}
} else if (mojo_bids.size() == 2 &&
mojo_bids[0]->bid_role ==
auction_worklet::mojom::BidRole::kUnenforcedKAnon &&
mojo_bids[1]->bid_role ==
auction_worklet::mojom::BidRole::kEnforcedKAnon) {
auction_->auction_metrics_recorder_
->RecordInterestGroupWithSeparateBidsForKAnonAndNonKAnon();
} else {
auction_->auction_metrics_recorder_
->RecordInterestGroupWithOtherMultiBid();
}
state->bidder_debug_loss_report_url = debug_loss_report_url;
for (auto& mojo_bid : mojo_bids) {
auto bid = TryToCreateBid(std::move(mojo_bid), *state,
bidding_signals_data_version,
debug_loss_report_url, debug_win_report_url);
if (bid) {
bids.push_back(std::move(bid));
} else {
bids.clear();
state->bidder_debug_loss_report_url.reset();
break;
}
}
CloseBidStatePipes(*state);
if (state->trace_id.has_value()) {
state->EndTracing();
}
if (!bids.empty()) {
state->bidder_debug_win_report_url = debug_win_report_url;
state->made_bid = true;
for (auto& bid : bids) {
auction_->ScoreBidIfReady(std::move(bid));
}
}
--num_outstanding_bids_;
if (num_outstanding_bids_ == 0) {
DCHECK_EQ(num_outstanding_bidding_signals_received_calls_, 0);
ClosePipes();
auction_->OnScoringDependencyDone();
}
}
void MaybeStartCumulativeTimeoutTimer() {
DCHECK_GT(num_outstanding_bids_, 0);
if (!auction_->config_promises_resolved_ ||
auction_->direct_from_seller_signals_header_ad_slot_pending_ ||
!bidder_process_received_) {
return;
}
DCHECK(!cumulative_buyer_timeout_timer_.IsRunning());
cumulative_buyer_timeout_ =
PerBuyerCumulativeTimeout(owner_, *auction_->config_);
if (!cumulative_buyer_timeout_) {
return;
}
start_measuring_cumulative_time_ = base::TimeTicks::Now();
cumulative_buyer_timeout_timer_.Start(
FROM_HERE, *cumulative_buyer_timeout_,
base::BindOnce(&BuyerHelper::OnTimeout, base::Unretained(this)));
}
void OnTimeout() {
DCHECK_GT(num_outstanding_bids_, 0);
cumulative_buyer_timeout_triggered_ = true;
std::list<BidState*> pending_bids;
for (auto& bid_state : bid_states_) {
if (!bid_state->worklet_handle) {
continue;
}
if (bid_state->bidding_signals_received) {
pending_bids.push_front(bid_state.get());
} else {
pending_bids.push_back(bid_state.get());
}
}
auction_->auction_metrics_recorder_
->RecordBidsAbortedByBuyerCumulativeTimeout(pending_bids.size());
num_bids_affected_by_cumulative_timeout_ = pending_bids.size();
for (auto* pending_bid : pending_bids) {
pending_bid->affected_by_cumulative_timeout = true;
auction_->auction_metrics_recorder_->RecordBidForOneInterestGroupLatency(
base::TimeTicks::Now() - start_generating_bids_time_);
OnFatalError(
pending_bid, {base::StrCat(
{pending_bid->bidder->interest_group.bidding_url->spec(),
" perBuyerCumulativeTimeout exceeded during bid generation."})});
}
}
std::unique_ptr<InterestGroupAuction::Bid> TryToCreateBid(
auction_worklet::mojom::BidderWorkletBidPtr mojo_bid,
BidState& bid_state,
const std::optional<uint32_t>& bidding_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url) {
auction_->auction_metrics_recorder_->RecordGenerateSingleBidLatency(
mojo_bid->bid_duration);
if (!IsValidBid(mojo_bid->bid)) {
generate_bid_client_receiver_set_.ReportBadMessage("Invalid bid value");
return nullptr;
}
if (mojo_bid->bid_duration.is_negative()) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Invalid bid duration");
return nullptr;
}
if (!blink::VerifyAdCurrencyCode(
PerBuyerCurrency(owner_, *auction_->config_),
mojo_bid->bid_currency)) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Invalid bid currency");
return nullptr;
}
const blink::InterestGroup& interest_group =
bid_state.bidder->interest_group;
const blink::InterestGroup::Ad* matching_ad = FindMatchingAd(
interest_group.ads, bid_state.kanon_keys, interest_group,
mojo_bid->bid_role, mojo_bid->selected_buyer_and_seller_reporting_id,
false, mojo_bid->ad_descriptor);
if (!matching_ad) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Bid render ad must have a valid URL and size (if specified)");
return nullptr;
}
blink::AdDescriptor ad_descriptor(
mojo_bid->ad_descriptor.url,
matching_ad->size_group ? mojo_bid->ad_descriptor.size : std::nullopt);
std::vector<Bid::ComponentAdInfo> selected_ad_components;
if (mojo_bid->ad_component_descriptors) {
if (!interest_group.ad_components) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Unexpected non-null ad component list");
return nullptr;
}
if (mojo_bid->ad_component_descriptors->size() >
blink::MaxAdAuctionAdComponents()) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Too many ad component URLs");
return nullptr;
}
for (const blink::AdDescriptor& ad_component_descriptor :
*mojo_bid->ad_component_descriptors) {
const blink::InterestGroup::Ad* matching_ad_component = FindMatchingAd(
interest_group.ad_components, bid_state.kanon_keys, interest_group,
mojo_bid->bid_role,
std::nullopt,
true, ad_component_descriptor);
if (!matching_ad_component) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Bid ad component must have a valid URL and size (if specified)");
return nullptr;
}
selected_ad_components.emplace_back(
blink::AdDescriptor(ad_component_descriptor.url,
matching_ad_component->size_group
? ad_component_descriptor.size
: std::nullopt),
matching_ad_component);
}
}
if (debug_loss_report_url.has_value() &&
!IsUrlValid(debug_loss_report_url.value())) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Invalid bidder debugging loss report URL");
return nullptr;
}
if (debug_win_report_url.has_value() &&
!IsUrlValid(debug_win_report_url.value())) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Invalid bidder debugging win report URL");
return nullptr;
}
if (mojo_bid->selected_buyer_and_seller_reporting_id.has_value() &&
!IsSelectedReportingIdValid(
matching_ad->selectable_buyer_and_seller_reporting_ids,
*mojo_bid->selected_buyer_and_seller_reporting_id,
true)) {
generate_bid_client_receiver_set_.ReportBadMessage(
"Invalid selected buyer and seller reporting id");
return nullptr;
}
return std::make_unique<Bid>(
mojo_bid->bid_role, std::move(mojo_bid->ad), mojo_bid->bid,
std::move(mojo_bid->bid_currency), mojo_bid->ad_cost,
std::move(ad_descriptor), std::move(selected_ad_components),
std::move(mojo_bid->modeling_signals),
std::move(mojo_bid->aggregate_win_signals), mojo_bid->bid_duration,
bidding_signals_data_version, matching_ad,
std::move(mojo_bid->selected_buyer_and_seller_reporting_id), &bid_state,
auction_);
}
void CloseBidStatePipes(BidState& state) {
state.worklet_handle.reset();
if (state.generate_bid_client_receiver_id) {
generate_bid_client_receiver_set_.Remove(
*state.generate_bid_client_receiver_id);
state.generate_bid_client_receiver_id.reset();
state.bid_finalizer.reset();
}
state.bidding_signals_handle.reset();
}
const blink::AuctionConfig::MaybePromiseJson* GetTkvSignals() const {
return auction_->InterestGroupAuction::GetBuyerTKVSignals(owner_);
}
base::optional_ref<const std::string> GetTkvSignalsAsOptionalRef() const {
const blink::AuctionConfig::MaybePromiseJson* tkv_signals = GetTkvSignals();
if (!tkv_signals) {
return std::nullopt;
}
CHECK(!tkv_signals->is_promise());
return tkv_signals->value();
}
size_t size_limit_;
const raw_ptr<InterestGroupAuction> auction_;
const url::Origin owner_;
const uint16_t multi_bid_limit_;
std::vector<std::unique_ptr<BidState>> bid_states_;
mojo::AssociatedReceiverSet<auction_worklet::mojom::GenerateBidClient,
BidState*>
generate_bid_client_receiver_set_;
bool bidder_process_received_ = false;
base::OneShotTimer cumulative_buyer_timeout_timer_;
int num_outstanding_bidding_signals_received_calls_ = 0;
int num_outstanding_begin_generate_bid_calls_ = 0;
int num_outstanding_bids_ = 0;
size_t num_bids_affected_by_cumulative_timeout_ = 0;
base::TimeTicks start_generating_bids_time_;
PrivateAggregationParticipantData buyer_metrics_;
AuctionMetricsRecorder::LatencyAggregator code_fetch_time_;
int bidder_scripts_timed_out_ = 0;
base::TimeTicks start_measuring_cumulative_time_;
base::TimeTicks stop_measuring_cumulative_time_;
std::optional<base::TimeDelta> cumulative_buyer_timeout_;
bool cumulative_buyer_timeout_triggered_ = false;
bool enable_bidding_signals_prioritization_ = false;
base::WeakPtrFactory<BuyerHelper> weak_ptr_factory_{this};
};
InterestGroupAuction::InterestGroupAuction(
auction_worklet::mojom::KAnonymityBidMode kanon_mode,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const url::Origin& main_frame_origin,
network::mojom::IPAddressSpace ip_address_space,
const blink::AuctionConfig* config,
const InterestGroupAuction* parent,
AuctionMetricsRecorder* auction_metrics_recorder,
DwaAuctionMetricsManager* dwa_auction_metrics_manager,
AuctionWorkletManager* auction_worklet_manager,
AuctionNonceManager* auction_nonce_manager,
InterestGroupManagerImpl* interest_group_manager,
GetDataDecoderCallback get_data_decoder_callback,
base::Time auction_start_time,
IsInterestGroupApiAllowedCallback is_interest_group_api_allowed_callback,
base::RepeatingCallback<
void(const PrivateAggregationRequests& private_aggregation_requests)>
maybe_log_private_aggregation_web_features_callback)
: devtools_auction_id_(base::Token::CreateRandom().ToString()),
trace_id_(base::trace_event::GetNextGlobalTraceId()),
kanon_mode_(kanon_mode),
url_loader_factory_(std::move(url_loader_factory)),
main_frame_origin_(main_frame_origin),
ip_address_space_(ip_address_space),
auction_metrics_recorder_(auction_metrics_recorder),
dwa_auction_metrics_manager_(dwa_auction_metrics_manager),
auction_worklet_manager_(auction_worklet_manager),
auction_nonce_manager_(auction_nonce_manager),
interest_group_manager_(interest_group_manager),
config_(config),
config_promises_resolved_(config_->NumPromises() == 0),
parent_(parent),
negative_targeter_(std::make_unique<AdAuctionNegativeTargeter>()),
auction_start_time_(auction_start_time),
creation_time_(base::TimeTicks::Now()),
is_interest_group_api_allowed_callback_(
std::move(is_interest_group_api_allowed_callback)),
maybe_log_private_aggregation_web_features_callback_(
std::move(maybe_log_private_aggregation_web_features_callback)),
is_server_auction_(config->server_response.has_value()),
get_data_decoder_callback_(std::move(get_data_decoder_callback)) {
DCHECK(is_interest_group_api_allowed_callback_);
TRACE_EVENT_BEGIN("fledge", "auction", perfetto::Track(*trace_id_),
"decision_logic_url", config_->decision_logic_url);
get_data_decoder_callback_.Run(config->seller);
FrameTreeNodeId frame_tree_node_id =
auction_worklet_manager_->GetFrameTreeNodeID();
if (devtools_instrumentation::NeedInterestGroupAuctionEvents(
frame_tree_node_id)) {
devtools_instrumentation::OnInterestGroupAuctionEventOccurred(
frame_tree_node_id, auction_start_time_,
InterestGroupAuctionEventType::kStarted, devtools_auction_id_,
parent_ ? base::optional_ref<const std::string>(
parent->devtools_auction_id_)
: base::optional_ref<const std::string>(),
SerializeAuctionConfigForDevtools(*config_));
}
uint32_t child_pos = 0;
for (const auto& component_auction_config :
config->non_shared_params.component_auctions) {
DCHECK(!parent_);
DCHECK(!is_server_auction_);
component_auctions_.emplace(
child_pos, std::make_unique<InterestGroupAuction>(
kanon_mode_, url_loader_factory_, main_frame_origin,
ip_address_space, &component_auction_config,
this, auction_metrics_recorder_,
dwa_auction_metrics_manager_, auction_worklet_manager,
auction_nonce_manager, interest_group_manager,
get_data_decoder_callback_, auction_start_time_,
is_interest_group_api_allowed_callback_,
maybe_log_private_aggregation_web_features_callback_));
++child_pos;
}
if (!parent_) {
auction_metrics_recorder_->SetKAnonymityBidMode(kanon_mode);
auction_metrics_recorder_->SetNumConfigPromises(config_->NumPromises());
}
}
InterestGroupAuction::~InterestGroupAuction() {
if (trace_id_.has_value()) {
TRACE_EVENT_END("fledge", perfetto::Track(*trace_id_));
}
if (!final_auction_result_) {
if (received_abort_signal_) {
final_auction_result_ = AuctionResult::kAbortSignal;
} else {
final_auction_result_ = AuctionResult::kDocumentDestruction;
}
}
std::string uma_prefix = "Ads.InterestGroup.Auction.";
if (is_server_auction_) {
uma_prefix = "Ads.InterestGroup.ServerAuction.";
}
if (dwa_auction_metrics_) {
dwa_auction_metrics_->OnAuctionEnd(*final_auction_result_);
}
if (!parent_) {
base::UmaHistogramEnumeration(
uma_prefix + "Result",
*final_auction_result_ == AuctionResult::kAbortSignal ||
*final_auction_result_ == AuctionResult::kDocumentDestruction
? AuctionResult::kAborted
: *final_auction_result_);
base::UmaHistogramEnumeration(uma_prefix + "Result2",
*final_auction_result_);
if (HasNonKAnonWinner()) {
base::UmaHistogramBoolean(uma_prefix + "NonKAnonWinnerIsKAnon",
NonKAnonWinnerIsKAnon());
}
base::TimeTicks now = base::TimeTicks::Now();
switch (*final_auction_result_) {
case AuctionResult::kDocumentDestruction:
case AuctionResult::kAbortSignal:
base::UmaHistogramMediumTimes(uma_prefix + "AbortTime",
now - creation_time_);
break;
case AuctionResult::kNoBids:
case AuctionResult::kAllBidsRejected:
base::UmaHistogramMediumTimes(uma_prefix + "CompletedWithoutWinnerTime",
now - creation_time_);
if (is_server_auction_) {
base::UmaHistogramMediumTimes(
"Ads.InterestGroup.ServerAuction.EndToEndTimeNoWinner",
now - get_ad_auction_data_start_time_);
}
break;
case AuctionResult::kSuccess:
base::UmaHistogramMediumTimes(uma_prefix + "AuctionWithWinnerTime",
now - creation_time_);
if (is_server_auction_) {
base::UmaHistogramMediumTimes(
"Ads.InterestGroup.ServerAuction.EndToEndTime",
now - get_ad_auction_data_start_time_);
}
break;
default:
break;
}
auction_metrics_recorder_->OnAuctionEnd(*final_auction_result_);
}
}
void InterestGroupAuction::StartLoadInterestGroupsPhase(
AuctionPhaseCompletionCallback load_interest_groups_phase_callback) {
DCHECK(load_interest_groups_phase_callback);
DCHECK(buyer_helpers_.empty());
DCHECK(!load_interest_groups_phase_callback_);
DCHECK(!bidding_and_scoring_phase_callback_);
DCHECK(!final_auction_result_);
DCHECK_EQ(num_pending_loads_, 0u);
TRACE_EVENT_BEGIN("fledge", "load_groups_phase", perfetto::Track(*trace_id_));
load_interest_groups_phase_callback_ =
std::move(load_interest_groups_phase_callback);
if (dwa_auction_metrics_manager_) {
dwa_auction_metrics_ =
dwa_auction_metrics_manager_->CreateDwaAuctionMetrics();
}
if (dwa_auction_metrics_) {
dwa_auction_metrics_->SetSellerInfo(config_->seller);
}
if (!is_interest_group_api_allowed_callback_.Run(
ContentBrowserClient::InterestGroupApiOperation::kSell,
config_->seller) ||
BlockDueToDisallowedCrossOriginTrustedSellerSignals()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&InterestGroupAuction::OnStartLoadInterestGroupsPhaseComplete,
weak_ptr_factory_.GetWeakPtr(), AuctionResult::kSellerRejected));
return;
}
if (config_->non_shared_params.auction_nonce) {
if (!auction_nonce_manager_->ClaimAuctionNonceIfAvailable(
static_cast<AuctionNonce>(
*config_->non_shared_params.auction_nonce))) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&InterestGroupAuction::OnStartLoadInterestGroupsPhaseComplete,
weak_ptr_factory_.GetWeakPtr(),
AuctionResult::kInvalidAuctionNonce));
return;
}
}
for (auto component_auction = component_auctions_.begin();
component_auction != component_auctions_.end(); ++component_auction) {
if (component_auction->second->is_server_auction_) {
continue;
}
component_auction->second->StartLoadInterestGroupsPhase(
base::BindOnce(&InterestGroupAuction::OnComponentInterestGroupsRead,
weak_ptr_factory_.GetWeakPtr(), component_auction));
++num_pending_loads_;
}
if (config_->non_shared_params.interest_group_buyers) {
for (const auto& buyer :
*config_->non_shared_params.interest_group_buyers) {
if (!is_interest_group_api_allowed_callback_.Run(
ContentBrowserClient::InterestGroupApiOperation::kBuy, buyer)) {
continue;
}
interest_group_manager_->GetInterestGroupsForOwner(
devtools_auction_id_, buyer,
base::BindOnce(&InterestGroupAuction::OnInterestGroupRead,
weak_ptr_factory_.GetWeakPtr()));
++num_pending_loads_;
std::optional<url::Origin> unused_signals_origin;
if (interest_group_manager_->GetCachedOwnerAndSignalsOrigins(
buyer, unused_signals_origin)) {
auction_worklet_manager_->MaybeStartAnticipatoryProcess(
buyer, AuctionWorkletManager::WorkletType::kBidder);
}
}
}
if (num_pending_loads_ == 0) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&InterestGroupAuction::OnStartLoadInterestGroupsPhaseComplete,
weak_ptr_factory_.GetWeakPtr(), AuctionResult::kSuccess));
}
}
void InterestGroupAuction::StartBiddingAndScoringPhase(
std::optional<DebugReportLockoutAndCooldowns>
debug_report_lockout_and_cooldowns,
base::OnceClosure on_seller_process_assigned_callback,
AuctionPhaseCompletionCallback bidding_and_scoring_phase_callback) {
DCHECK(bidding_and_scoring_phase_callback);
DCHECK(!on_seller_process_assigned_callback_);
DCHECK(!load_interest_groups_phase_callback_);
DCHECK(!bidding_and_scoring_phase_callback_);
DCHECK(!final_auction_result_);
DCHECK(!non_kanon_enforced_auction_leader_.top_bid);
DCHECK(!kanon_enforced_auction_leader_.top_bid);
DCHECK_EQ(pending_component_seller_worklet_requests_, 0u);
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kBefore);
bidding_and_scoring_phase_state_ = PhaseState::kDuring;
TRACE_EVENT_BEGIN("fledge", "bidding_and_scoring_phase",
perfetto::Track(*trace_id_));
if (debug_report_lockout_and_cooldowns.has_value()) {
debug_report_lockout_and_cooldowns_ = *debug_report_lockout_and_cooldowns;
}
on_seller_process_assigned_callback_ =
std::move(on_seller_process_assigned_callback);
bidding_and_scoring_phase_callback_ =
std::move(bidding_and_scoring_phase_callback);
bidding_and_scoring_phase_start_time_ = base::TimeTicks::Now();
CHECK_EQ(num_scoring_dependencies_, 0);
num_scoring_dependencies_ =
buyer_helpers_.size() + component_auctions_.size();
if (!config_promises_resolved_) {
++num_scoring_dependencies_;
}
if (direct_from_seller_signals_header_ad_slot_pending_) {
++num_scoring_dependencies_;
}
DecodeAdditionalBidsIfReady();
if (component_auctions_.empty()) {
if (is_server_auction_) {
if (saved_response_) {
MaybeLoadDebugReportLockoutAndCooldowns();
return;
}
} else {
if (!buyer_helpers_.empty() || MayHaveAdditionalBids()) {
RequestSellerWorkletAsync();
}
}
} else {
size_t local_auctions = 0;
for (const auto& component_auction_info : component_auctions_) {
if (!component_auction_info.second->is_server_auction_) {
local_auctions++;
}
}
pending_component_seller_worklet_requests_ = local_auctions;
for (auto& component_auction_info : component_auctions_) {
InterestGroupAuction* component_auction =
component_auction_info.second.get();
base::OnceClosure component_on_seller_receiver_callback =
component_auction->is_server_auction_
? base::NullCallback()
: base::BindOnce(
&InterestGroupAuction::OnComponentSellerWorkletReceived,
base::Unretained(this));
component_auction->StartBiddingAndScoringPhase(
debug_report_lockout_and_cooldowns.has_value()
? std::optional<DebugReportLockoutAndCooldowns>(
*debug_report_lockout_and_cooldowns)
: std::nullopt,
std::move(component_on_seller_receiver_callback),
base::BindOnce(&InterestGroupAuction::OnComponentAuctionComplete,
base::Unretained(this), component_auction));
}
if (local_auctions == 0) {
RequestSellerWorkletAsync();
}
}
for (const auto& buyer_helper : buyer_helpers_) {
buyer_helper->SetForDebuggingOnlyInCooldownOrLockout(
IsOriginInDebugReportCooldownOrLockout(
buyer_helper->owner(), debug_report_lockout_and_cooldowns_,
base::Time::Now()));
buyer_helper->StartGeneratingBids();
}
MaybeCompleteBiddingAndScoringPhase();
}
void InterestGroupAuction::HandleComponentServerResponse(
uint32_t pos,
mojo_base::BigBuffer response,
base::RepeatingCallback<AdAuctionPageData*()>
ad_auction_page_data_callback) {
CHECK(!parent_);
auto it = component_auctions_.find(pos);
if (it == component_auctions_.end()) {
return;
}
InterestGroupAuction* component_auction = it->second.get();
component_auction->HandleServerResponse(
std::move(response), std::move(ad_auction_page_data_callback));
}
void InterestGroupAuction::HandleServerResponse(
mojo_base::BigBuffer response,
base::RepeatingCallback<AdAuctionPageData*()>
ad_auction_page_data_callback) {
if (!HandleServerResponseImpl(std::move(response),
std::move(ad_auction_page_data_callback))) {
DCHECK(saved_response_);
if (bidding_and_scoring_phase_state_ == PhaseState::kDuring) {
MaybeCompleteBiddingAndScoringPhase();
}
}
}
bool InterestGroupAuction::HandleServerResponseImpl(
mojo_base::BigBuffer response,
base::RepeatingCallback<AdAuctionPageData*()>
ad_auction_page_data_callback) {
bool authorized = false;
std::array<uint8_t, crypto::kSHA256Length> hash =
crypto::SHA256Hash(response);
AdAuctionPageData* ad_auction_page_data = ad_auction_page_data_callback.Run();
if (!ad_auction_page_data) {
saved_response_.emplace();
return false;
}
if (ad_auction_page_data->WitnessedAuctionResultForOrigin(
config_->seller,
std::string(reinterpret_cast<char*>(hash.data()), hash.size()))) {
authorized = true;
}
AdAuctionRequestContext* request_context =
ad_auction_page_data->GetContextForAdAuctionRequest(
ContextMapKey(config_->server_response->request_id, config_->seller));
if (!request_context) {
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kUnknownRequestId);
errors_.push_back(base::StringPrintf(
"runAdAuction(): No corresponding request for seller '%s' with ID '%s'",
config_->seller.Serialize(),
config_->server_response->request_id.AsLowercaseString()));
return false;
}
CHECK_EQ(request_context->seller, config_->seller);
get_ad_auction_data_start_time_ = request_context->start_time;
auto maybe_response =
quiche::ObliviousHttpResponse::CreateClientObliviousResponse(
std::string(reinterpret_cast<char*>(response.data()),
response.size()),
request_context->context,
kBiddingAndAuctionEncryptionResponseMediaType);
if (!maybe_response.ok()) {
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kDecryptionFailure);
errors_.push_back("runAdAuction(): Could not decrypt server response");
return false;
}
const std::string& plaintext_response = maybe_response->GetPlaintextData();
std::optional<base::span<const uint8_t>> compressed_response =
ExtractCompressedBiddingAndAuctionResponse(
base::as_byte_span(plaintext_response));
if (!compressed_response) {
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kUnframingFailure);
errors_.push_back("runAdAuction(): Could not parse response framing");
return false;
}
data_decoder::DataDecoder* data_decoder =
get_data_decoder_callback_.Run(config_->seller);
if (!data_decoder) {
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kDecoderShutdown);
errors_.push_back("runAdAuction(): Page in shutdown");
return false;
}
data_decoder->GzipUncompress(
std::move(compressed_response).value(),
base::BindOnce(
&InterestGroupAuction::OnDecompressedServerResponse,
weak_ptr_factory_.GetWeakPtr(), base::Unretained(request_context),
std::move(ad_auction_page_data_callback), authorized));
return true;
}
std::unique_ptr<InterestGroupAuctionReporter>
InterestGroupAuction::CreateReporter(
BrowserContext* browser_context,
PrivateAggregationManager* private_aggregation_manager,
AdAuctionPageDataCallback ad_auction_page_data_callback,
std::unique_ptr<blink::AuctionConfig> auction_config,
const url::Origin& main_frame_origin,
const url::Origin& frame_origin,
network::mojom::ClientSecurityStatePtr client_security_state,
blink::InterestGroupSet interest_groups_that_bid) {
DCHECK(!load_interest_groups_phase_callback_);
DCHECK(!bidding_and_scoring_phase_callback_);
DCHECK_EQ(*final_auction_result_, AuctionResult::kSuccess);
DCHECK(!parent_);
uint64_t trace_id = *trace_id_;
trace_id_.reset();
SubresourceUrlBuilderIfReady();
const LeaderInfo& leader = leader_info();
InterestGroupAuction::ScoredBid* winner = leader.top_bid.get();
InterestGroupAuctionReporter::WinningBidInfo winning_bid_info(
winner->bid->bid_state->bidder);
winning_bid_info.render_url = winner->bid->ad_descriptor.url;
winning_bid_info.bid_ad = winner->bid->bid_ad;
winning_bid_info.allowed_reporting_origins =
winner->bid->bid_ad->allowed_reporting_origins;
const InterestGroupAuction::Bid* bidder_bid =
winner->bid->auction->top_bid()->bid.get();
winning_bid_info.bid = bidder_bid->bid;
winning_bid_info.bid_currency = PerBuyerCurrency(
bidder_bid->interest_group->owner, *bidder_bid->auction->config_);
winning_bid_info.provided_as_additional_bid =
bidder_bid->bid_state->additional_bid_buyer.has_value();
winning_bid_info.ad_cost = bidder_bid->ad_cost;
winning_bid_info.modeling_signals = bidder_bid->modeling_signals;
winning_bid_info.aggregate_win_signals = bidder_bid->aggregate_win_signals;
winning_bid_info.bid_duration = winner->bid->bid_duration;
winning_bid_info.bidding_signals_data_version =
winner->bid->bidding_signals_data_version;
winning_bid_info.selected_buyer_and_seller_reporting_id =
winner->bid->selected_buyer_and_seller_reporting_id;
base::Value::Dict ad_metadata;
ad_metadata.Set("renderURL", winner->bid->ad_descriptor.url.spec());
if (winner->bid->bid_ad->metadata) {
ad_metadata.Set("metadata", winner->bid->bid_ad->metadata.value());
}
if (winner->bid->bid_ad->ad_render_id) {
ad_metadata.Set("adRenderId", winner->bid->bid_ad->ad_render_id.value());
}
winning_bid_info.ad_metadata =
base::WriteJson(ad_metadata).value_or(std::string());
InterestGroupAuctionReporter::SellerWinningBidInfo
top_level_seller_winning_bid_info;
top_level_seller_winning_bid_info.auction_config = config_;
DCHECK(subresource_url_builder_);
CHECK(direct_from_seller_signals_header_ad_slot_);
top_level_seller_winning_bid_info.subresource_url_builder =
std::move(subresource_url_builder_);
top_level_seller_winning_bid_info.direct_from_seller_signals_header_ad_slot =
std::move(direct_from_seller_signals_header_ad_slot_);
top_level_seller_winning_bid_info.bid = winner->bid->bid;
top_level_seller_winning_bid_info.rounded_bid =
InterestGroupAuctionReporter::RoundBidStochastically(
top_level_seller_winning_bid_info.bid);
if (winner->bid->auction == this) {
top_level_seller_winning_bid_info.bid_currency =
winning_bid_info.bid_currency;
} else {
InterestGroupAuction* component_auction = winner->bid->auction;
top_level_seller_winning_bid_info.bid_currency =
component_auction->config_->non_shared_params.seller_currency;
if (!top_level_seller_winning_bid_info.bid_currency) {
top_level_seller_winning_bid_info.bid_currency =
PerBuyerCurrency(component_auction->config_->seller, *config_);
}
}
top_level_seller_winning_bid_info.bid_in_seller_currency =
winner->bid_in_seller_currency.value_or(0.0);
top_level_seller_winning_bid_info.score = winner->score;
top_level_seller_winning_bid_info.highest_scoring_other_bid =
leader.highest_scoring_other_bid;
top_level_seller_winning_bid_info
.highest_scoring_other_bid_in_seller_currency =
leader.highest_scoring_other_bid_in_seller_currency;
top_level_seller_winning_bid_info.highest_scoring_other_bid_owner =
leader.highest_scoring_other_bid_owner;
top_level_seller_winning_bid_info.scoring_signals_data_version =
leader.top_bid->scoring_signals_data_version;
top_level_seller_winning_bid_info.trace_id = trace_id;
top_level_seller_winning_bid_info.saved_response = std::move(saved_response_);
std::optional<InterestGroupAuctionReporter::SellerWinningBidInfo>
component_seller_winning_bid_info;
if (winner->bid->auction != this) {
InterestGroupAuction* component_auction = winner->bid->auction;
component_auction->SubresourceUrlBuilderIfReady();
component_seller_winning_bid_info.emplace();
component_seller_winning_bid_info->auction_config =
component_auction->config_;
DCHECK(component_auction->subresource_url_builder_);
CHECK(component_auction->direct_from_seller_signals_header_ad_slot_);
component_seller_winning_bid_info->subresource_url_builder =
std::move(component_auction->subresource_url_builder_);
component_seller_winning_bid_info
->direct_from_seller_signals_header_ad_slot = std::move(
component_auction->direct_from_seller_signals_header_ad_slot_);
const LeaderInfo& component_leader = component_auction->leader_info();
component_seller_winning_bid_info->bid = component_leader.top_bid->bid->bid;
component_seller_winning_bid_info->rounded_bid =
InterestGroupAuctionReporter::RoundBidStochastically(
component_leader.top_bid->bid->bid);
component_seller_winning_bid_info->bid_currency =
winning_bid_info.bid_currency;
component_seller_winning_bid_info->bid_in_seller_currency =
component_leader.top_bid->bid_in_seller_currency.value_or(0.0);
component_seller_winning_bid_info->score = component_leader.top_bid->score;
component_seller_winning_bid_info->highest_scoring_other_bid =
component_leader.highest_scoring_other_bid;
component_seller_winning_bid_info
->highest_scoring_other_bid_in_seller_currency =
component_leader.highest_scoring_other_bid_in_seller_currency;
component_seller_winning_bid_info->highest_scoring_other_bid_owner =
component_leader.highest_scoring_other_bid_owner;
component_seller_winning_bid_info->scoring_signals_data_version =
component_leader.top_bid->scoring_signals_data_version;
component_seller_winning_bid_info->trace_id = *component_auction->trace_id_;
component_seller_winning_bid_info->component_auction_modified_bid_params =
component_leader.top_bid->component_auction_modified_bid_params
->Clone();
component_seller_winning_bid_info->saved_response =
std::move(component_auction->saved_response_);
}
CollectBiddingAndScoringPhaseReports();
auction_worklet::mojom::KAnonymityStatus kanon_status;
switch (kanon_mode_) {
case auction_worklet::mojom::KAnonymityBidMode::kSimulate:
if (NonKAnonWinnerIsKAnon()) {
kanon_status =
auction_worklet::mojom::KAnonymityStatus::kPassingNotEnforced;
} else {
kanon_status =
auction_worklet::mojom::KAnonymityStatus::kBelowThreshold;
}
break;
case auction_worklet::mojom::KAnonymityBidMode::kEnforce:
kanon_status =
auction_worklet::mojom::KAnonymityStatus::kPassingAndEnforced;
break;
case auction_worklet::mojom::KAnonymityBidMode::kNone:
if (base::FeatureList::IsEnabled(features::kFledgeQueryKAnonymity)) {
kanon_status =
IsBidKAnon(*winner->bid)
? auction_worklet::mojom::KAnonymityStatus::kPassingNotEnforced
: auction_worklet::mojom::KAnonymityStatus::kBelowThreshold;
} else {
kanon_status = auction_worklet::mojom::KAnonymityStatus::kUnknown;
}
break;
default:
NOTREACHED();
}
auto result = std::make_unique<InterestGroupAuctionReporter>(
interest_group_manager_, auction_worklet_manager_, browser_context,
private_aggregation_manager,
maybe_log_private_aggregation_web_features_callback_,
std::move(ad_auction_page_data_callback), std::move(auction_config),
devtools_auction_id_, main_frame_origin, frame_origin,
std::move(client_security_state), url_loader_factory_, kanon_status,
std::move(winning_bid_info), std::move(top_level_seller_winning_bid_info),
std::move(component_seller_winning_bid_info),
std::move(interest_groups_that_bid), TakeDebugWinReportUrls(),
TakeDebugLossReportUrls(), GetKAnonKeysToJoin(),
TakeReservedPrivateAggregationRequests(),
TakeNonReservedPrivateAggregationRequests(),
ComputePrivateAggregationParticipantData(),
TakeRealTimeReportingContributions());
winner->bid->interest_group = nullptr;
winner->bid->bid_ad = nullptr;
config_ = nullptr;
for (const auto& kv : component_auctions_) {
kv.second->config_ = nullptr;
}
return result;
}
void InterestGroupAuction::NotifyConfigPromisesResolved() {
DCHECK(!config_promises_resolved_);
DCHECK_EQ(0, config_->NumPromises());
config_promises_resolved_ = true;
auction_metrics_recorder_->OnConfigPromisesResolved();
FrameTreeNodeId frame_tree_node_id =
auction_worklet_manager_->GetFrameTreeNodeID();
if (devtools_instrumentation::NeedInterestGroupAuctionEvents(
frame_tree_node_id)) {
devtools_instrumentation::OnInterestGroupAuctionEventOccurred(
frame_tree_node_id, base::Time::Now(),
InterestGroupAuctionEventType::kConfigResolved, devtools_auction_id_,
parent_ ? base::optional_ref<const std::string>(
parent_->devtools_auction_id_)
: base::optional_ref<const std::string>(),
SerializeAuctionConfigForDevtools(*config_));
}
if (bidding_and_scoring_phase_state_ != PhaseState::kDuring) {
return;
}
for (const auto& buyer_helper : buyer_helpers_) {
buyer_helper->NotifyConfigPromisesResolved();
}
base::TimeTicks now = base::TimeTicks::Now();
for (auto& unscored_bid : unscored_bids_) {
unscored_bid->wait_promises =
now - unscored_bid->trace_wait_seller_deps_start;
if (!component_auctions_.empty()) {
auction_metrics_recorder_
->RecordTopLevelBidQueuedWaitingForConfigPromises(
unscored_bid->wait_promises);
} else {
auction_metrics_recorder_->RecordBidQueuedWaitingForConfigPromises(
unscored_bid->wait_promises);
}
}
DecodeAdditionalBidsIfReady();
ScoreQueuedBidsIfReady();
OnScoringDependencyDone();
}
void InterestGroupAuction::NotifyComponentConfigPromisesResolved(uint32_t pos) {
DCHECK(!parent_);
auto it = component_auctions_.find(pos);
if (it == component_auctions_.end()) {
return;
}
it->second->NotifyConfigPromisesResolved();
}
void InterestGroupAuction::NotifyBuyerTkvSignalsPromiseResolved(
const url::Origin& buyer,
std::optional<uint32_t> pos) {
if (pos.has_value()) {
DCHECK(!parent_);
auto it = component_auctions_.find(*pos);
if (it == component_auctions_.end()) {
return;
}
it->second->NotifyBuyerTkvSignalsPromiseResolved(buyer, std::nullopt);
return;
}
for (const auto& buyer_helper : buyer_helpers_) {
if (buyer_helper->owner() == buyer) {
buyer_helper->OnBuyerTkvPromiseResolved();
return;
}
}
}
void InterestGroupAuction::NotifyAdditionalBidsConfig(
AdAuctionPageData& auction_page_data) {
DCHECK_EQ(config_->non_shared_params.component_auctions.size(), 0u);
CHECK(config_->non_shared_params.auction_nonce.has_value());
CHECK(config_->non_shared_params.interest_group_buyers.has_value());
interest_group_buyers_ = base::flat_set<url::Origin>(
config_->non_shared_params.interest_group_buyers->begin(),
config_->non_shared_params.interest_group_buyers->end());
encoded_signed_additional_bids_ =
auction_page_data.TakeAuctionAdditionalBidsForOriginAndNonce(
config_->seller,
config_->non_shared_params.auction_nonce->AsLowercaseString());
}
void InterestGroupAuction::NotifyComponentAdditionalBidsConfig(
uint32_t pos,
AdAuctionPageData& auction_page_data) {
DCHECK(!parent_);
DCHECK_GT(config_->non_shared_params.component_auctions.size(), 0u);
auto it = component_auctions_.find(pos);
if (it == component_auctions_.end()) {
return;
}
it->second->NotifyAdditionalBidsConfig(auction_page_data);
}
void InterestGroupAuction::NotifyDirectFromSellerSignalsHeaderAdSlotConfig(
AdAuctionPageData& auction_page_data,
const std::optional<std::string>&
direct_from_seller_signals_header_ad_slot) {
CHECK(!direct_from_seller_signals_header_ad_slot_pending_);
if (!direct_from_seller_signals_header_ad_slot ||
bidding_and_scoring_phase_state_ == PhaseState::kAfter) {
return;
}
if (bidding_and_scoring_phase_state_ == PhaseState::kDuring) {
++num_scoring_dependencies_;
}
direct_from_seller_signals_header_ad_slot_pending_ = true;
auction_page_data.ParseAndFindAdAuctionSignals(
config_->seller, *direct_from_seller_signals_header_ad_slot,
base::BindOnce(
&InterestGroupAuction::OnDirectFromSellerSignalHeaderAdSlotResolved,
weak_ptr_factory_.GetWeakPtr(),
*direct_from_seller_signals_header_ad_slot));
}
void InterestGroupAuction::
NotifyComponentDirectFromSellerSignalsHeaderAdSlotConfig(
uint32_t pos,
AdAuctionPageData& auction_page_data,
const std::optional<std::string>&
direct_from_seller_signals_header_ad_slot) {
CHECK(!parent_);
auto it = component_auctions_.find(pos);
if (it == component_auctions_.end()) {
return;
}
it->second->NotifyDirectFromSellerSignalsHeaderAdSlotConfig(
auction_page_data, std::move(direct_from_seller_signals_header_ad_slot));
}
void InterestGroupAuction::ClosePipes() {
weak_ptr_factory_.InvalidateWeakPtrs();
score_ad_receivers_.Clear();
for (auto& buyer_helper : buyer_helpers_) {
buyer_helper->ClosePipes();
}
seller_worklet_handle_.reset();
for (const auto& bid_state : bid_states_for_additional_bids_) {
DCHECK(!bid_state->worklet_handle);
DCHECK(!bid_state->bid_finalizer);
}
for (auto& component_auction_info : component_auctions_) {
component_auction_info.second->ClosePipes();
}
}
size_t InterestGroupAuction::NumPotentialBidders() const {
size_t num_interest_groups = 0;
for (const auto& buyer_helper : buyer_helpers_) {
num_interest_groups += buyer_helper->num_potential_bidders();
}
for (const auto& component_auction_info : component_auctions_) {
num_interest_groups += component_auction_info.second->NumPotentialBidders();
}
return num_interest_groups;
}
void InterestGroupAuction::GetInterestGroupsThatBidAndReportBidCounts(
blink::InterestGroupSet& interest_groups) const {
if (!all_bids_scored_) {
return;
}
if (saved_response_) {
interest_groups.insert(saved_response_->bidding_groups.begin(),
saved_response_->bidding_groups.end());
for (const auto& ig_bid : interest_groups) {
interest_group_manager_->NotifyInterestGroupAccessed(
devtools_auction_id_,
InterestGroupManagerImpl::InterestGroupObserver::
InterestGroupObserver::kBid,
ig_bid.owner, ig_bid.name,
std::nullopt,
std::nullopt, std::nullopt);
}
return;
}
for (auto& buyer_helper : buyer_helpers_) {
buyer_helper->GetInterestGroupsThatBidAndReportBidCounts(interest_groups);
}
for (const auto& component_auction_info : component_auctions_) {
component_auction_info.second->GetInterestGroupsThatBidAndReportBidCounts(
interest_groups);
}
}
std::optional<blink::AdSize> InterestGroupAuction::RequestedAdSize() const {
return config_->non_shared_params.requested_size;
}
std::string_view GetRejectReasonString(
const auction_worklet::mojom::RejectReason reject_reason) {
std::string_view reject_reason_str;
switch (reject_reason) {
case auction_worklet::mojom::RejectReason::kNotAvailable:
reject_reason_str = "not-available";
break;
case auction_worklet::mojom::RejectReason::kInvalidBid:
reject_reason_str = "invalid-bid";
break;
case auction_worklet::mojom::RejectReason::kBidBelowAuctionFloor:
reject_reason_str = "bid-below-auction-floor";
break;
case auction_worklet::mojom::RejectReason::kPendingApprovalByExchange:
reject_reason_str = "pending-approval-by-exchange";
break;
case auction_worklet::mojom::RejectReason::kDisapprovedByExchange:
reject_reason_str = "disapproved-by-exchange";
break;
case auction_worklet::mojom::RejectReason::kBlockedByPublisher:
reject_reason_str = "blocked-by-publisher";
break;
case auction_worklet::mojom::RejectReason::kLanguageExclusions:
reject_reason_str = "language-exclusions";
break;
case auction_worklet::mojom::RejectReason::kCategoryExclusions:
reject_reason_str = "category-exclusions";
break;
case auction_worklet::mojom::RejectReason::kBelowKAnonThreshold:
reject_reason_str = "below-kanon-threshold";
break;
case auction_worklet::mojom::RejectReason::kWrongGenerateBidCurrency:
reject_reason_str = "wrong-generate-bid-currency";
break;
case auction_worklet::mojom::RejectReason::kWrongScoreAdCurrency:
reject_reason_str = "wrong-score-ad-currency";
break;
case auction_worklet::mojom::RejectReason::kMultiBidLimitExceeded:
reject_reason_str = "multi-bid-limit-exceeded";
break;
}
return reject_reason_str;
}
GURL InterestGroupAuction::FillPostAuctionSignals(
const GURL& url,
const PostAuctionSignals& signals,
const std::optional<PostAuctionSignals>& top_level_signals,
const std::optional<auction_worklet::mojom::RejectReason> reject_reason) {
if (!url.has_query()) {
return url;
}
std::string query_string = url.GetQuery();
base::ReplaceSubstringsAfterOffset(&query_string, 0, "${winningBid}",
base::NumberToString(signals.winning_bid));
base::ReplaceSubstringsAfterOffset(
&query_string, 0, "${winningBidCurrency}",
blink::PrintableAdCurrency(signals.winning_bid_currency));
base::ReplaceSubstringsAfterOffset(&query_string, 0, "${madeWinningBid}",
base::ToString(signals.made_winning_bid));
base::ReplaceSubstringsAfterOffset(
&query_string, 0, "${highestScoringOtherBid}",
base::NumberToString(signals.highest_scoring_other_bid));
base::ReplaceSubstringsAfterOffset(
&query_string, 0, "${highestScoringOtherBidCurrency}",
blink::PrintableAdCurrency(signals.highest_scoring_other_bid_currency));
base::ReplaceSubstringsAfterOffset(
&query_string, 0, "${madeHighestScoringOtherBid}",
base::ToString(signals.made_highest_scoring_other_bid));
if (top_level_signals.has_value()) {
base::ReplaceSubstringsAfterOffset(
&query_string, 0, "${topLevelWinningBid}",
base::NumberToString(top_level_signals->winning_bid));
base::ReplaceSubstringsAfterOffset(
&query_string, 0, "${topLevelWinningBidCurrency}",
blink::PrintableAdCurrency(top_level_signals->winning_bid_currency));
base::ReplaceSubstringsAfterOffset(
&query_string, 0, "${topLevelMadeWinningBid}",
base::ToString(top_level_signals->made_winning_bid));
}
if (reject_reason.has_value()) {
base::ReplaceSubstringsAfterOffset(
&query_string, 0, "${rejectReason}",
GetRejectReasonString(reject_reason.value()));
}
GURL::Replacements replacements;
replacements.SetQueryStr(query_string);
return url.ReplaceComponents(replacements);
}
bool InterestGroupAuction::ReportPaBuyersValueIfAllowed(
const blink::InterestGroup& interest_group,
blink::SellerCapabilities capability,
blink::AuctionConfig::NonSharedParams::BuyerReportType buyer_report_type,
int value) {
if (!CanReportPaBuyersValue(interest_group, capability, config_->seller)) {
return false;
}
std::optional<absl::uint128> bucket_base =
BucketBaseForReportPaBuyers(*config_, interest_group.owner);
if (!bucket_base) {
return false;
}
std::optional<
blink::AuctionConfig::NonSharedParams::AuctionReportBuyersConfig>
report_buyers_config =
ReportBuyersConfigForPaBuyers(buyer_report_type, *config_);
if (!report_buyers_config) {
return false;
}
std::optional<
blink::AuctionConfig::NonSharedParams::AuctionReportBuyerDebugModeConfig>
debug_mode_config =
config_->non_shared_params.auction_report_buyer_debug_mode_config;
blink::mojom::DebugModeDetailsPtr debug_mode_details;
if (debug_mode_config) {
blink::mojom::DebugKeyPtr debug_key = nullptr;
if (debug_mode_config->debug_key) {
CHECK(debug_mode_config->is_enabled);
debug_key = blink::mojom::DebugKey::New(*debug_mode_config->debug_key);
}
debug_mode_details = blink::mojom::DebugModeDetails::New(
debug_mode_config->is_enabled, std::move(debug_key));
} else {
debug_mode_details = blink::mojom::DebugModeDetails::New();
}
PrivateAggregationKey agg_key = {config_->seller,
config_->aggregation_coordinator_origin};
FinalizedPrivateAggregationRequests& destination_vector =
private_aggregation_requests_reserved_[std::move(agg_key)];
destination_vector.push_back(
auction_worklet::mojom::FinalizedPrivateAggregationRequest::New(
blink::mojom::AggregatableReportHistogramContribution::New(
*bucket_base + report_buyers_config->bucket,
base::saturated_cast<int32_t>(
std::max(0.0, value * report_buyers_config->scale)),
std::nullopt),
std::move(debug_mode_details),
std::nullopt));
return true;
}
bool InterestGroupAuction::HasNonKAnonWinner() const {
if (!final_auction_result_) {
return false;
}
switch (*final_auction_result_) {
case AuctionResult::kSuccess:
case AuctionResult::kNoBids:
case AuctionResult::kAllBidsRejected:
return top_non_kanon_enforced_bid() != nullptr;
case AuctionResult::kAbortSignal:
case AuctionResult::kDocumentDestruction:
case AuctionResult::kAborted:
case AuctionResult::kBadMojoMessage:
case AuctionResult::kNoInterestGroups:
case AuctionResult::kSellerWorkletLoadFailed:
case AuctionResult::kSellerWorkletCrashed:
case AuctionResult::kSellerRejected:
case AuctionResult::kComponentLostAuction:
case AuctionResult::kInvalidServerResponse:
case AuctionResult::kInvalidAuctionNonce:
return false;
}
}
bool InterestGroupAuction::NonKAnonWinnerIsKAnon() const {
return top_non_kanon_enforced_bid() &&
top_non_kanon_enforced_bid()
->bid->auction->top_non_kanon_enforced_bid()
->bid->bid_role ==
auction_worklet::mojom::BidRole::kBothKAnonModes;
}
bool InterestGroupAuction::HasInterestGroups() const {
if (!buyer_helpers_.empty()) {
return true;
}
if (is_server_auction_) {
return true;
}
for (const auto& kv : component_auctions_) {
if (!kv.second->buyer_helpers_.empty()) {
return true;
}
}
return false;
}
SubresourceUrlBuilder* InterestGroupAuction::SubresourceUrlBuilderIfReady() {
if (!subresource_url_builder_ &&
!config_->direct_from_seller_signals.is_promise()) {
subresource_url_builder_ = std::make_unique<SubresourceUrlBuilder>(
config_->direct_from_seller_signals.value());
}
return subresource_url_builder_.get();
}
void InterestGroupAuction::CollectBiddingAndScoringPhaseReports() {
CHECK(!bidding_and_scoring_phase_reports_collected_);
bidding_and_scoring_phase_reports_collected_ = true;
if (!all_bids_scored_) {
for (auto& component_auction_info : component_auctions_) {
component_auction_info.second
->bidding_and_scoring_phase_reports_collected_ = true;
}
return;
}
BidState* winner = nullptr;
const LeaderInfo& leader = leader_info();
if (final_auction_result_ == AuctionResult::kSuccess &&
leader.top_bid->bid->auction == this) {
winner = leader.top_bid->bid->bid_state;
}
BidState* non_kanon_winner = nullptr;
if (kanon_mode_ == auction_worklet::mojom::KAnonymityBidMode::kEnforce &&
HasNonKAnonWinner() && !NonKAnonWinnerIsKAnon()) {
non_kanon_winner = top_non_kanon_enforced_bid()->bid->bid_state;
}
PostAuctionSignals signals;
std::optional<PostAuctionSignals> top_level_signals;
std::optional<url::Origin> top_level_seller;
if (parent_) {
top_level_signals.emplace();
top_level_seller = parent_->config_->seller;
}
if (!leader.top_bid) {
DCHECK_EQ(leader.highest_scoring_other_bid, 0);
DCHECK(!leader.highest_scoring_other_bid_owner.has_value());
}
FillInSellerParticipantDataMetrics();
const BidState* non_top_level_seller_once_rep;
const PrivateAggregationParticipantData* non_top_level_seller_data;
const BidState* top_level_seller_once_rep;
const PrivateAggregationParticipantData* top_level_seller_data;
if (parent_) {
non_top_level_seller_once_rep = seller_reserved_once_rep_;
non_top_level_seller_data = &seller_metrics_;
top_level_seller_once_rep = parent_->seller_reserved_once_rep_;
top_level_seller_data = &parent_->seller_metrics_;
} else {
non_top_level_seller_once_rep = nullptr;
non_top_level_seller_data = nullptr;
top_level_seller_once_rep = seller_reserved_once_rep_;
top_level_seller_data = &seller_metrics_;
}
std::map<PrivateAggregationKey, FinalizedPrivateAggregationRequests>
private_aggregation_requests_reserved;
std::map<std::string, FinalizedPrivateAggregationRequests>
private_aggregation_requests_non_reserved;
std::map<url::Origin, InterestGroupAuction::RealTimeReportingContributions>
real_time_contributions;
for (const auto& buyer_helper : buyer_helpers_) {
ComputePostAuctionSignals(buyer_helper->owner(), signals,
top_level_signals);
buyer_helper->TakeDebugReportUrls(
winner, signals, top_level_signals, config_->seller, top_level_seller,
debug_win_report_urls_, debug_loss_report_urls_);
buyer_helper->TakePrivateAggregationRequests(
winner, non_kanon_winner, signals, top_level_signals,
non_top_level_seller_once_rep, top_level_seller_once_rep,
non_top_level_seller_data, top_level_seller_data,
private_aggregation_requests_reserved,
private_aggregation_requests_non_reserved);
buyer_helper->TakeRealTimeContributions(real_time_contributions);
}
PrivateAggregationReservedOnceReps additional_bid_reps;
PrivateAggregationAllParticipantsDataPtrs additional_bid_data;
additional_bid_reps[static_cast<size_t>(
PrivateAggregationPhase::kTopLevelSeller)] = top_level_seller_once_rep;
additional_bid_data[static_cast<size_t>(
PrivateAggregationPhase::kTopLevelSeller)] = top_level_seller_data;
additional_bid_reps[static_cast<size_t>(
PrivateAggregationPhase::kNonTopLevelSeller)] =
non_top_level_seller_once_rep;
additional_bid_data[static_cast<size_t>(
PrivateAggregationPhase::kNonTopLevelSeller)] = non_top_level_seller_data;
additional_bid_reps[static_cast<size_t>(PrivateAggregationPhase::kBidder)] =
nullptr;
additional_bid_data[static_cast<size_t>(PrivateAggregationPhase::kBidder)] =
nullptr;
for (std::unique_ptr<BidState>& bid_state : bid_states_for_additional_bids_) {
const url::Origin& owner = bid_state->additional_bid_buyer.value();
ComputePostAuctionSignals(owner, signals, top_level_signals);
TakePrivateAggregationRequestsForBidState(
bid_state, parent_ != nullptr, winner,
non_kanon_winner, additional_bid_reps, signals, top_level_signals,
additional_bid_data, private_aggregation_requests_reserved,
private_aggregation_requests_non_reserved);
TakeDebugReportUrlsForBidState(
bid_state, winner, signals, top_level_signals, owner, config_->seller,
top_level_seller, debug_report_lockout_and_cooldowns_,
new_debug_report_lockout_and_cooldowns_, debug_win_report_urls_,
debug_loss_report_urls_);
TakeRealTimeContributionsForBidState(*bid_state, real_time_contributions);
}
for (auto& [key, requests] : private_aggregation_requests_reserved) {
FinalizedPrivateAggregationRequests& destination_vector =
private_aggregation_requests_reserved_[key];
destination_vector.insert(destination_vector.end(),
std::move_iterator(requests.begin()),
std::move_iterator(requests.end()));
}
for (auto& [event_type, requests] :
private_aggregation_requests_non_reserved) {
FinalizedPrivateAggregationRequests& destination_vector =
private_aggregation_requests_non_reserved_[event_type];
destination_vector.insert(destination_vector.end(),
std::move_iterator(requests.begin()),
std::move_iterator(requests.end()));
}
for (auto& [origin, contributions] : real_time_contributions) {
InterestGroupAuction::RealTimeReportingContributions& destination_vector =
real_time_contributions_[origin];
destination_vector.insert(destination_vector.end(),
std::move_iterator(contributions.begin()),
std::move_iterator(contributions.end()));
}
for (auto& component_auction_info : component_auctions_) {
component_auction_info.second->CollectBiddingAndScoringPhaseReports();
}
if (new_debug_report_lockout_and_cooldowns_.lockout.has_value()) {
interest_group_manager_->RecordDebugReportLockout(
new_debug_report_lockout_and_cooldowns_.lockout->starting_time,
new_debug_report_lockout_and_cooldowns_.lockout->duration);
}
for (const auto& [origin, debug_report_cooldown] :
new_debug_report_lockout_and_cooldowns_.debug_report_cooldown_map) {
interest_group_manager_->RecordDebugReportCooldown(
origin, debug_report_cooldown.starting_time,
debug_report_cooldown.type);
}
}
std::vector<GURL> InterestGroupAuction::TakeDebugWinReportUrls() {
CHECK(bidding_and_scoring_phase_reports_collected_);
for (auto& component_auction_info : component_auctions_) {
std::vector<GURL> report_urls =
component_auction_info.second->TakeDebugWinReportUrls();
debug_win_report_urls_.insert(debug_win_report_urls_.end(),
std::move_iterator(report_urls.begin()),
std::move_iterator(report_urls.end()));
}
return std::move(debug_win_report_urls_);
}
std::vector<GURL> InterestGroupAuction::TakeDebugLossReportUrls() {
CHECK(bidding_and_scoring_phase_reports_collected_);
for (auto& component_auction_info : component_auctions_) {
std::vector<GURL> report_urls =
component_auction_info.second->TakeDebugLossReportUrls();
debug_loss_report_urls_.insert(debug_loss_report_urls_.end(),
std::move_iterator(report_urls.begin()),
std::move_iterator(report_urls.end()));
}
return std::move(debug_loss_report_urls_);
}
std::map<PrivateAggregationKey,
InterestGroupAuction::FinalizedPrivateAggregationRequests>
InterestGroupAuction::TakeReservedPrivateAggregationRequests() {
CHECK(bidding_and_scoring_phase_reports_collected_);
for (auto& component_auction_info : component_auctions_) {
std::map<PrivateAggregationKey, FinalizedPrivateAggregationRequests>
requests_map = component_auction_info.second
->TakeReservedPrivateAggregationRequests();
for (auto& [agg_key, requests] : requests_map) {
CHECK(!requests.empty());
FinalizedPrivateAggregationRequests& destination_vector =
private_aggregation_requests_reserved_[agg_key];
destination_vector.insert(destination_vector.end(),
std::move_iterator(requests.begin()),
std::move_iterator(requests.end()));
}
}
return std::move(private_aggregation_requests_reserved_);
}
std::map<std::string, InterestGroupAuction::FinalizedPrivateAggregationRequests>
InterestGroupAuction::TakeNonReservedPrivateAggregationRequests() {
CHECK(bidding_and_scoring_phase_reports_collected_);
for (auto& component_auction_info : component_auctions_) {
std::map<std::string, FinalizedPrivateAggregationRequests> requests_map =
component_auction_info.second
->TakeNonReservedPrivateAggregationRequests();
for (auto& [event_type, requests] : requests_map) {
CHECK(!requests.empty());
FinalizedPrivateAggregationRequests& destination_vector =
private_aggregation_requests_non_reserved_[event_type];
destination_vector.insert(destination_vector.end(),
std::move_iterator(requests.begin()),
std::move_iterator(requests.end()));
}
}
return std::move(private_aggregation_requests_non_reserved_);
}
InterestGroupAuctionReporter::PrivateAggregationAllParticipantsData
InterestGroupAuction::ComputePrivateAggregationParticipantData() {
InterestGroupAuctionReporter::PrivateAggregationAllParticipantsData
all_participant_data;
ScoredBid* winner = leader_info().top_bid.get();
BidState* winner_bid_state = winner->bid->bid_state;
if (!winner_bid_state->additional_bid_buyer) {
const url::Origin& bid_origin =
winner_bid_state->bidder->interest_group.owner;
for (const auto& buyer_helper : winner->bid->auction->buyer_helpers_) {
if (buyer_helper->owner() == bid_origin) {
all_participant_data[static_cast<size_t>(
PrivateAggregationPhase::kBidder)] = buyer_helper->buyer_metrics();
break;
}
}
}
all_participant_data[static_cast<size_t>(
PrivateAggregationPhase::kTopLevelSeller)] = seller_metrics_;
if (winner->bid->auction != this) {
all_participant_data[static_cast<size_t>(
PrivateAggregationPhase::kNonTopLevelSeller)] =
winner->bid->auction->seller_metrics_;
}
return all_participant_data;
}
std::map<url::Origin, InterestGroupAuction::RealTimeReportingContributions>
InterestGroupAuction::TakeRealTimeReportingContributions() {
CHECK(bidding_and_scoring_phase_reports_collected_);
for (auto& component_auction_info : component_auctions_) {
std::map<url::Origin, RealTimeReportingContributions> contributions_map =
component_auction_info.second->TakeRealTimeReportingContributions();
for (auto& [origin, contributions] : contributions_map) {
RealTimeReportingContributions& destination_vector =
real_time_contributions_[origin];
destination_vector.insert(destination_vector.end(),
std::move_iterator(contributions.begin()),
std::move_iterator(contributions.end()));
}
}
return std::move(real_time_contributions_);
}
std::vector<std::string> InterestGroupAuction::TakeErrors() {
for (auto& component_auction_info : component_auctions_) {
std::vector<std::string> errors =
component_auction_info.second->TakeErrors();
errors_.insert(errors_.begin(), errors.begin(), errors.end());
}
return std::move(errors_);
}
void InterestGroupAuction::TakePostAuctionUpdateOwners(
std::vector<url::Origin>& owners) {
for (const url::Origin& owner : post_auction_update_owners_) {
owners.emplace_back(std::move(owner));
}
for (auto& component_auction_info : component_auctions_) {
component_auction_info.second->TakePostAuctionUpdateOwners(owners);
}
}
bool InterestGroupAuction::ReportInterestGroupCount(
const blink::InterestGroup& interest_group,
size_t count) {
return ReportPaBuyersValueIfAllowed(
interest_group, blink::SellerCapabilities::kInterestGroupCounts,
blink::AuctionConfig::NonSharedParams::BuyerReportType::
kInterestGroupCount,
count);
}
bool InterestGroupAuction::ReportBidCount(
const blink::InterestGroup& interest_group,
size_t count) {
return ReportPaBuyersValueIfAllowed(
interest_group, blink::SellerCapabilities::kInterestGroupCounts,
blink::AuctionConfig::NonSharedParams::BuyerReportType::kBidCount, count);
}
void InterestGroupAuction::ReportTrustedSignalsFetchLatency(
const blink::InterestGroup& interest_group,
base::TimeDelta trusted_signals_fetch_latency) {
ReportPaBuyersValueIfAllowed(interest_group,
blink::SellerCapabilities::kLatencyStats,
blink::AuctionConfig::NonSharedParams::
BuyerReportType::kTotalSignalsFetchLatency,
trusted_signals_fetch_latency.InMilliseconds());
}
void InterestGroupAuction::ReportBiddingLatency(
const blink::InterestGroup& interest_group,
base::TimeDelta bidding_latency) {
ReportPaBuyersValueIfAllowed(interest_group,
blink::SellerCapabilities::kLatencyStats,
blink::AuctionConfig::NonSharedParams::
BuyerReportType::kTotalGenerateBidLatency,
bidding_latency.InMilliseconds());
}
base::flat_set<url::Origin> InterestGroupAuction::GetSellersAndBuyers() {
std::vector<url::Origin> origins;
origins.push_back(config_->seller);
for (const auto& buyer_helper : buyer_helpers_) {
origins.push_back(buyer_helper->owner());
}
for (const auto& component_auction_info : component_auctions_) {
InterestGroupAuction* component_auction =
component_auction_info.second.get();
origins.push_back(component_auction->config_->seller);
for (const auto& buyer_helper : component_auction->buyer_helpers_) {
origins.push_back(buyer_helper->owner());
}
}
return base::flat_set<url::Origin>(std::move(origins));
}
base::flat_set<std::string> InterestGroupAuction::GetKAnonKeysToJoin() const {
if (!HasNonKAnonWinner()) {
return {};
}
std::vector<const ScoredBid*> bids_to_include = {
top_non_kanon_enforced_bid()};
if (!NonKAnonWinnerIsKAnon()) {
bids_to_include.push_back(top_kanon_enforced_bid());
}
std::vector<std::string> k_anon_keys_to_join;
for (const ScoredBid* scored_bid : bids_to_include) {
if (!scored_bid) {
continue;
}
if (scored_bid->bid->bid_state->additional_bid_buyer.has_value()) {
continue;
}
DCHECK(scored_bid->bid);
const blink::InterestGroup& interest_group =
*scored_bid->bid->interest_group;
k_anon_keys_to_join.push_back(blink::HashedKAnonKeyForAdBid(
interest_group, scored_bid->bid->bid_ad->render_url()));
k_anon_keys_to_join.push_back(blink::HashedKAnonKeyForAdNameReporting(
interest_group, *scored_bid->bid->bid_ad,
scored_bid->bid->selected_buyer_and_seller_reporting_id));
for (auto& component_info : scored_bid->bid->selected_ad_components) {
const blink::AdDescriptor& ad_component_descriptor =
component_info.ad_descriptor;
k_anon_keys_to_join.push_back(
blink::HashedKAnonKeyForAdComponentBid(ad_component_descriptor));
}
}
return base::flat_set<std::string>(std::move(k_anon_keys_to_join));
}
void InterestGroupAuction::MaybeLogPrivateAggregationWebFeatures(
const std::vector<auction_worklet::mojom::PrivateAggregationRequestPtr>&
private_aggregation_requests) {
CHECK(maybe_log_private_aggregation_web_features_callback_);
maybe_log_private_aggregation_web_features_callback_.Run(
private_aggregation_requests);
}
bool InterestGroupAuction::
BlockDueToDisallowedCrossOriginTrustedSellerSignals() {
if (!config_->trusted_scoring_signals_url.has_value()) {
return false;
}
url::Origin trusted_scoring_signals_origin =
url::Origin::Create(config_->trusted_scoring_signals_url.value());
if (config_->seller.IsSameOriginWith(trusted_scoring_signals_origin)) {
return false;
}
if (is_interest_group_api_allowed_callback_.Run(
ContentBrowserClient::InterestGroupApiOperation::kSell,
trusted_scoring_signals_origin)) {
return false;
}
errors_.push_back(base::StringPrintf(
"runAdAuction() auction with seller '%s' failed because it lacks "
"attestation of cross-origin trusted signals origin '%s' or that origin "
"is disallowed by user preferences",
config_->seller.Serialize().c_str(),
trusted_scoring_signals_origin.Serialize().c_str()));
return true;
}
const InterestGroupAuction::LeaderInfo& InterestGroupAuction::leader_info()
const {
if (kanon_mode_ == auction_worklet::mojom::KAnonymityBidMode::kEnforce) {
return kanon_enforced_auction_leader_;
} else {
return non_kanon_enforced_auction_leader_;
}
}
InterestGroupAuction::ScoredBid*
InterestGroupAuction::top_kanon_enforced_bid() {
return kanon_enforced_auction_leader_.top_bid.get();
}
const InterestGroupAuction::ScoredBid*
InterestGroupAuction::top_kanon_enforced_bid() const {
return kanon_enforced_auction_leader_.top_bid.get();
}
InterestGroupAuction::ScoredBid*
InterestGroupAuction::top_non_kanon_enforced_bid() {
return non_kanon_enforced_auction_leader_.top_bid.get();
}
const InterestGroupAuction::ScoredBid*
InterestGroupAuction::top_non_kanon_enforced_bid() const {
return non_kanon_enforced_auction_leader_.top_bid.get();
}
void InterestGroupAuction::ComputePostAuctionSignals(
const url::Origin& bid_owner,
PostAuctionSignals& signals_out,
std::optional<PostAuctionSignals>& top_level_signals_out) {
DCHECK(!parent_ || top_level_signals_out.has_value());
const LeaderInfo& leader = leader_info();
if (leader.top_bid) {
PostAuctionSignals::FillWinningBidInfo(
bid_owner, leader.top_bid->bid->interest_group->owner,
leader.top_bid->bid->bid, leader.top_bid->bid_in_seller_currency,
config_->non_shared_params.seller_currency,
signals_out.made_winning_bid, signals_out.winning_bid,
signals_out.winning_bid_currency);
}
PostAuctionSignals::FillRelevantHighestScoringOtherBidInfo(
bid_owner, leader.highest_scoring_other_bid_owner,
leader.highest_scoring_other_bid,
leader.highest_scoring_other_bid_in_seller_currency,
config_->non_shared_params.seller_currency,
signals_out.made_highest_scoring_other_bid,
signals_out.highest_scoring_other_bid,
signals_out.highest_scoring_other_bid_currency);
if (parent_ && parent_->top_bid()) {
PostAuctionSignals::FillWinningBidInfo(
bid_owner, parent_->top_bid()->bid->interest_group->owner,
parent_->top_bid()->bid->bid,
parent_->top_bid()->bid_in_seller_currency,
parent_->config_->non_shared_params.seller_currency,
top_level_signals_out->made_winning_bid,
top_level_signals_out->winning_bid,
top_level_signals_out->winning_bid_currency);
}
}
void InterestGroupAuction::FillInSellerParticipantDataMetrics() {
if (code_fetch_time_.GetNumRecords() != 0) {
seller_metrics_.average_code_fetch_time = code_fetch_time_.GetMeanLatency();
}
seller_metrics_.percent_scripts_timeout =
PercentMetric(seller_scripts_timed_out_, seller_scripts_ran_);
}
uint16_t InterestGroupAuction::GetBuyerMultiBidLimit(const url::Origin& buyer) {
uint16_t val = config_->non_shared_params.all_buyers_multi_bid_limit;
auto it = config_->non_shared_params.per_buyer_multi_bid_limits.find(buyer);
if (it != config_->non_shared_params.per_buyer_multi_bid_limits.end()) {
val = it->second;
}
return std::max(val, uint16_t{1});
}
const blink::AuctionConfig::MaybePromiseJson*
InterestGroupAuction::GetBuyerTKVSignals(const url::Origin& owner) const {
if (!base::FeatureList::IsEnabled(
blink::features::kFledgeTrustedSignalsKVv2ContextualData)) {
return nullptr;
}
auto it = config_->non_shared_params.per_buyer_tkv_signals.find(owner);
if (it == config_->non_shared_params.per_buyer_tkv_signals.end()) {
return nullptr;
}
return &it->second;
}
base::optional_ref<const std::string>
InterestGroupAuction::GetSellerTKVSignals() const {
if (!base::FeatureList::IsEnabled(
blink::features::kFledgeTrustedSignalsKVv2ContextualData)) {
return std::nullopt;
}
const auto& seller_tkv_signals =
config_->non_shared_params.seller_tkv_signals;
CHECK(!seller_tkv_signals.is_promise());
return seller_tkv_signals.value();
}
std::optional<uint16_t> InterestGroupAuction::GetBuyerExperimentId(
const blink::AuctionConfig& config,
const url::Origin& buyer) {
auto it = config.per_buyer_experiment_group_ids.find(buyer);
if (it != config.per_buyer_experiment_group_ids.end()) {
return it->second;
}
return config.all_buyer_experiment_group_id;
}
std::string InterestGroupAuction::CreateTrustedBiddingSignalsSlotSizeParam(
const blink::AuctionConfig& config,
blink::InterestGroup::TrustedBiddingSignalsSlotSizeMode
trusted_bidding_signals_slot_size_mode) {
auto optional_pair = CreateTrustedBiddingSignalsSlotSizeParams(
config, trusted_bidding_signals_slot_size_mode);
if (!optional_pair) {
return std::string();
}
return base::StringPrintf("%s=%s", optional_pair->first,
optional_pair->second.c_str());
}
std::optional<std::string> InterestGroupAuction::GetPerBuyerSignals(
const blink::AuctionConfig& config,
const url::Origin& buyer) {
const auto& auction_config_per_buyer_signals =
config.non_shared_params.per_buyer_signals;
DCHECK(!auction_config_per_buyer_signals.is_promise());
if (auction_config_per_buyer_signals.value().has_value()) {
auto it = auction_config_per_buyer_signals.value()->find(buyer);
if (it != auction_config_per_buyer_signals.value()->end()) {
return it->second;
}
}
return std::nullopt;
}
std::optional<GURL> InterestGroupAuction::GetDirectFromSellerAuctionSignals(
const SubresourceUrlBuilder* subresource_url_builder) {
if (subresource_url_builder && subresource_url_builder->auction_signals()) {
return subresource_url_builder->auction_signals()->subresource_url;
}
return std::nullopt;
}
std::optional<std::string>
InterestGroupAuction::GetDirectFromSellerAuctionSignalsHeaderAdSlot(
const HeaderDirectFromSellerSignals::Result& signals) {
return signals.auction_signals();
}
std::optional<GURL> InterestGroupAuction::GetDirectFromSellerPerBuyerSignals(
const SubresourceUrlBuilder* subresource_url_builder,
const url::Origin& owner) {
if (!subresource_url_builder) {
return std::nullopt;
}
auto it = subresource_url_builder->per_buyer_signals().find(owner);
if (it == subresource_url_builder->per_buyer_signals().end()) {
return std::nullopt;
}
return it->second.subresource_url;
}
std::optional<std::string>
InterestGroupAuction::GetDirectFromSellerPerBuyerSignalsHeaderAdSlot(
const HeaderDirectFromSellerSignals::Result& signals,
const url::Origin& owner) {
auto it = signals.per_buyer_signals().find(owner);
if (it == signals.per_buyer_signals().end()) {
return std::nullopt;
}
return it->second;
}
std::optional<GURL> InterestGroupAuction::GetDirectFromSellerSellerSignals(
const SubresourceUrlBuilder* subresource_url_builder) {
if (subresource_url_builder && subresource_url_builder->seller_signals()) {
return subresource_url_builder->seller_signals()->subresource_url;
}
return std::nullopt;
}
std::optional<std::string>
InterestGroupAuction::GetDirectFromSellerSellerSignalsHeaderAdSlot(
const HeaderDirectFromSellerSignals::Result& signals) {
return signals.seller_signals();
}
const std::vector<blink::AuctionConfig::AdKeywordReplacement>&
InterestGroupAuction::GetDeprecatedRenderURLReplacements() {
return config_->non_shared_params.deprecated_render_url_replacements.value();
}
InterestGroupAuction::LeaderInfo::LeaderInfo() = default;
InterestGroupAuction::LeaderInfo::~LeaderInfo() = default;
InterestGroupAuction::ScoreAdClientData::ScoreAdClientData(
std::unique_ptr<Bid> bid,
std::unique_ptr<TrustedSignalsCacheImpl::Handle> cache_handle)
: bid(std::move(bid)), cache_handle(std::move(cache_handle)) {}
InterestGroupAuction::ScoreAdClientData::ScoreAdClientData(
ScoreAdClientData&&) = default;
InterestGroupAuction::ScoreAdClientData::~ScoreAdClientData() = default;
InterestGroupAuction::ScoreAdClientData&
InterestGroupAuction::ScoreAdClientData::operator=(ScoreAdClientData&&) =
default;
void InterestGroupAuction::OnInterestGroupRead(
scoped_refptr<StorageInterestGroups> read_interest_groups) {
++num_owners_loaded_;
if (read_interest_groups->size() == 0) {
OnOneLoadCompleted();
return;
}
std::vector<SingleStorageInterestGroup> interest_groups =
read_interest_groups->GetInterestGroups();
int positive_groups = 0;
int negative_groups = 0;
size_t storage_used = 0;
for (const SingleStorageInterestGroup& group : interest_groups) {
if (group->interest_group.IsNegativeInterestGroup()) {
++negative_groups;
} else {
++positive_groups;
}
storage_used += group->interest_group.EstimateSize();
}
for (const SingleStorageInterestGroup& group : interest_groups) {
if (ReportInterestGroupCount(group->interest_group,
read_interest_groups->size())) {
break;
}
}
post_auction_update_owners_.emplace_back(
interest_groups[0]->interest_group.owner);
for (const SingleStorageInterestGroup& bidder : interest_groups) {
if (bidder->interest_group.update_url.has_value()) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Ads.InterestGroup.Auction.GroupFreshness.WithDailyUpdates",
(base::Time::Now() - bidder->last_updated).InMinutes(),
kGroupFreshnessMin.InMinutes(), kGroupFreshnessMax.InMinutes(),
kGroupFreshnessBuckets);
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Ads.InterestGroup.Auction.GroupFreshness90d.WithDailyUpdates",
(base::Time::Now() - bidder->last_updated).InMinutes(),
kGroupFreshnessMin.InMinutes(), kGroupFreshnessMax90d.InMinutes(),
kGroupFreshnessBuckets);
} else {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Ads.InterestGroup.Auction.GroupFreshness.NoDailyUpdates",
(base::Time::Now() - bidder->last_updated).InMinutes(),
kGroupFreshnessMin.InMinutes(), kGroupFreshnessMax.InMinutes(),
kGroupFreshnessBuckets);
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Ads.InterestGroup.Auction.GroupFreshness90d.NoDailyUpdates",
(base::Time::Now() - bidder->last_updated).InMinutes(),
kGroupFreshnessMin.InMinutes(), kGroupFreshnessMax90d.InMinutes(),
kGroupFreshnessBuckets);
}
}
if (MayHaveAdditionalBids()) {
for (const SingleStorageInterestGroup& group : interest_groups) {
if (group->interest_group.additional_bid_key.has_value()) {
negative_targeter_->AddInterestGroupInfo(
group->interest_group.owner, group->interest_group.name,
group->joining_origin, *group->interest_group.additional_bid_key);
}
}
}
UpdateIgSizeMetrics(interest_groups);
std::erase_if(interest_groups, [](const SingleStorageInterestGroup& bidder) {
return !bidder->interest_group.bidding_url || !bidder->interest_group.ads ||
bidder->interest_group.ads->empty();
});
std::erase_if(interest_groups,
[this](const SingleStorageInterestGroup& bidder) {
return !GroupSatisfiesAllCapabilities(
bidder->interest_group,
config_->non_shared_params.required_seller_capabilities,
config_->seller);
});
if (interest_groups.empty()) {
OnOneLoadCompleted();
return;
}
++num_owners_with_interest_groups_;
auction_metrics_recorder_->ReportBuyer(
interest_groups[0]->interest_group.owner);
auto buyer_helper =
std::make_unique<BuyerHelper>(this, std::move(interest_groups));
buyer_helper->SetStorageMetrics(positive_groups, negative_groups,
storage_used);
if (buyer_helper->has_potential_bidder()) {
buyer_helpers_.emplace_back(std::move(buyer_helper));
interest_group_manager_->UpdateCachedOriginsIfEnabled(
interest_groups[0]->interest_group.owner);
} else {
buyer_helper.reset();
}
OnOneLoadCompleted();
}
void InterestGroupAuction::OnComponentInterestGroupsRead(
AuctionMap::iterator component_auction,
bool success) {
num_owners_loaded_ += component_auction->second->num_owners_loaded_;
num_owners_with_interest_groups_ +=
component_auction->second->num_owners_with_interest_groups_;
if (!success) {
component_auctions_.erase(component_auction);
}
OnOneLoadCompleted();
}
void InterestGroupAuction::OnOneLoadCompleted() {
DCHECK_GT(num_pending_loads_, 0u);
--num_pending_loads_;
if (num_pending_loads_ > 0) {
return;
}
if (!parent_) {
if (num_owners_loaded_ > 0) {
size_t num_interest_groups = NumPotentialBidders();
size_t num_sellers_with_bidders = 0;
size_t total_interest_group_bytes_for_metrics = 0u;
size_t total_ads_and_ad_components_bytes_for_metrics = 0u;
for (const auto& [unused, component] : component_auctions_) {
if (!component->buyer_helpers_.empty()) {
++num_sellers_with_bidders;
}
total_interest_group_bytes_for_metrics +=
component->interest_groups_bytes_for_metrics_;
total_ads_and_ad_components_bytes_for_metrics +=
component->ads_and_ad_components_bytes_for_metrics_;
}
base::UmaHistogramMemoryKB("Ads.InterestGroup.AtAuctionTotalSize.Groups",
total_interest_group_bytes_for_metrics / 1024);
base::UmaHistogramMemoryKB(
"Ads.InterestGroup.AtAuctionTotalSize.AdsAndAdComponents",
total_ads_and_ad_components_bytes_for_metrics / 1024);
if (num_interest_groups > 0) {
++num_sellers_with_bidders;
}
auction_metrics_recorder_->SetNumInterestGroups(num_interest_groups);
auction_metrics_recorder_->SetNumOwnersWithInterestGroups(
num_owners_with_interest_groups_);
auction_metrics_recorder_->SetNumSellersWithBidders(
num_sellers_with_bidders);
CHECK_GE(num_owners_loaded_, num_owners_with_interest_groups_);
auction_metrics_recorder_->SetNumOwnersWithoutInterestGroups(
num_owners_loaded_ - num_owners_with_interest_groups_);
}
}
if (MayHaveAdditionalBids()) {
auction_metrics_recorder_->RecordNegativeInterestGroups(
negative_targeter_->GetNumNegativeInterestGroups());
}
AuctionResult result = AuctionResult::kSuccess;
if (!config_->non_shared_params.component_auctions.empty() &&
component_auctions_.empty()) {
result = AuctionResult::kNoInterestGroups;
}
OnStartLoadInterestGroupsPhaseComplete(result);
}
void InterestGroupAuction::OnStartLoadInterestGroupsPhaseComplete(
AuctionResult auction_result) {
DCHECK(load_interest_groups_phase_callback_);
DCHECK(!final_auction_result_);
if (!parent_) {
auction_metrics_recorder_->OnLoadInterestGroupPhaseComplete();
}
TRACE_EVENT_END("fledge", perfetto::Track(*trace_id_));
if (!HasInterestGroups()) {
UMA_HISTOGRAM_TIMES("Ads.InterestGroup.Auction.LoadNoGroupsTime",
base::TimeTicks::Now() - creation_time_);
} else {
UMA_HISTOGRAM_TIMES("Ads.InterestGroup.Auction.LoadGroupsTime",
base::TimeTicks::Now() - creation_time_);
}
bool success = auction_result == AuctionResult::kSuccess;
if (!success) {
final_auction_result_ = auction_result;
}
std::move(load_interest_groups_phase_callback_).Run(success);
}
void InterestGroupAuction::OnComponentSellerWorkletReceived() {
DCHECK_GT(pending_component_seller_worklet_requests_, 0u);
--pending_component_seller_worklet_requests_;
if (pending_component_seller_worklet_requests_ == 0) {
RequestSellerWorklet();
}
}
void InterestGroupAuction::RequestSellerWorkletAsync() {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&InterestGroupAuction::RequestSellerWorklet,
weak_ptr_factory_.GetWeakPtr()));
}
void InterestGroupAuction::RequestSellerWorklet() {
if (bidding_and_scoring_phase_state_ != PhaseState::kDuring) {
return;
}
TRACE_EVENT_BEGIN("fledge", "request_seller_worklet",
perfetto::Track(*trace_id_));
auction_worklet_manager_->RequestSellerWorklet(
devtools_auction_id_, *config_->decision_logic_url,
config_->trusted_scoring_signals_url, config_->seller_experiment_group_id,
config_->non_shared_params.trusted_scoring_signals_coordinator,
config_->send_creative_scanning_metadata,
base::BindOnce(&InterestGroupAuction::OnSellerProcessAssigned,
base::Unretained(this)),
base::BindOnce(&InterestGroupAuction::OnSellerWorkletReceived,
base::Unretained(this)),
base::BindOnce(&InterestGroupAuction::OnSellerWorkletFatalError,
base::Unretained(this)),
seller_worklet_handle_, auction_metrics_recorder_);
}
void InterestGroupAuction::OnSellerProcessAssigned() {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
if (on_seller_process_assigned_callback_) {
std::move(on_seller_process_assigned_callback_).Run();
}
}
void InterestGroupAuction::OnSellerWorkletReceived() {
DCHECK(!seller_worklet_received_);
DCHECK(!on_seller_process_assigned_callback_);
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
TRACE_EVENT_END("fledge", perfetto::Track(*trace_id_));
seller_worklet_received_ = true;
base::TimeTicks now = base::TimeTicks::Now();
for (auto& unscored_bid : unscored_bids_) {
unscored_bid->wait_worklet =
now - unscored_bid->trace_wait_seller_deps_start;
if (!component_auctions_.empty()) {
auction_metrics_recorder_->RecordTopLevelBidQueuedWaitingForSellerWorklet(
unscored_bid->wait_worklet);
} else {
auction_metrics_recorder_->RecordBidQueuedWaitingForSellerWorklet(
unscored_bid->wait_worklet);
}
}
ScoreQueuedBidsIfReady();
}
void InterestGroupAuction::ScoreQueuedBidsIfReady() {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
if (!ReadyToScoreBids() || unscored_bids_.empty()) {
return;
}
auto unscored_bids = std::move(unscored_bids_);
for (auto& unscored_bid : unscored_bids) {
TRACE_EVENT_END("fledge",
perfetto::Track(unscored_bid->TraceIdForScoring()), "data",
[&](perfetto::TracedValue trace_context) {
auto dict = std::move(trace_context).WriteDictionary();
if (!unscored_bid->wait_worklet.is_zero()) {
dict.Add("wait_worklet_ms",
unscored_bid->wait_worklet.InMillisecondsF());
}
if (!unscored_bid->wait_promises.is_zero()) {
dict.Add("wait_promises_ms",
unscored_bid->wait_promises.InMillisecondsF());
}
});
ScoreBid(std::move(unscored_bid));
}
if (num_scoring_dependencies_ == 0) {
StartPendingScoringSignalsRequests();
}
DCHECK(unscored_bids_.empty());
}
void InterestGroupAuction::StartPendingScoringSignalsRequests() {
DCHECK(ReadyToScoreBids());
DCHECK_EQ(num_scoring_dependencies_, 0);
auto contexts = score_ad_receivers_.GetAllContexts();
for (const auto& context : contexts) {
if (context.second->cache_handle) {
context.second->cache_handle->StartFetch();
}
}
seller_worklet_handle_->GetSellerWorklet()->SendPendingSignalsRequests();
}
void InterestGroupAuction::HandleUpdateIfOlderThan(
const url::Origin& owner,
std::string_view name,
std::optional<base::TimeDelta> update_if_older_than) {
if (!base::FeatureList::IsEnabled(
features::kInterestGroupUpdateIfOlderThan)) {
return;
}
if (!update_if_older_than) {
return;
}
base::UmaHistogramCustomTimes(
"Ads.InterestGroup.UpdateIfOlderThan", *update_if_older_than,
kMinUpdateIfOlderThanHistogram, kMaxUpdateIfOlderThanHistogram,
kUpdateIfOlderThanBuckets);
if (*update_if_older_than < base::Minutes(10)) {
*update_if_older_than = base::Minutes(10);
}
interest_group_manager_->AllowUpdateIfOlderThan(
blink::InterestGroupKey(owner, std::string(name)), *update_if_older_than);
}
void InterestGroupAuction::HandleAdditionalBidError(AdditionalBidResult result,
std::string error) {
auction_metrics_recorder_->RecordAdditionalBidResult(result);
errors_.push_back(std::move(error));
OnScoringDependencyDone();
}
void InterestGroupAuction::DecodeAdditionalBidsIfReady() {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
if (encoded_signed_additional_bids_.empty()) {
return;
}
decode_additional_bids_start_time_ = base::TimeTicks::Now();
data_decoder::DataDecoder* data_decoder =
get_data_decoder_callback_.Run(config_->seller);
num_scoring_dependencies_ += encoded_signed_additional_bids_.size();
for (auto& encoded_signed_bid : encoded_signed_additional_bids_) {
std::string signed_additional_bid_data;
if (!base::Base64Decode(encoded_signed_bid.signed_additional_bid,
&signed_additional_bid_data,
base::Base64DecodePolicy::kForgiving)) {
HandleAdditionalBidError(
AdditionalBidResult::kRejectedDueToInvalidBase64,
"Unable to base64-decode a signed additional bid.");
continue;
}
if (!data_decoder) {
HandleAdditionalBidError(AdditionalBidResult::kRejectedDecoderShutDown,
"Page in shutdown.");
continue;
}
data_decoder->ParseJson(
signed_additional_bid_data,
base::BindOnce(&InterestGroupAuction::HandleDecodedSignedAdditionalBid,
weak_ptr_factory_.GetWeakPtr(),
std::move(encoded_signed_bid.seller_nonce)));
}
encoded_signed_additional_bids_.clear();
currently_decoding_additional_bids_ = true;
}
void InterestGroupAuction::HandleDecodedSignedAdditionalBid(
std::optional<std::string> seller_nonce,
data_decoder::DataDecoder::ValueOrError result) {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
if (!result.has_value()) {
HandleAdditionalBidError(
AdditionalBidResult::kRejectedDueToSignedBidJsonParseError,
"Unable to parse signed additional bid as JSON: " + result.error());
return;
}
auto maybe_signed_additional_bid =
DecodeSignedAdditionalBid(std::move(result).value());
if (!maybe_signed_additional_bid.has_value()) {
HandleAdditionalBidError(
AdditionalBidResult::kRejectedDueToSignedBidDecodeError,
"Unable to decode signed additional bid: " +
maybe_signed_additional_bid.error());
return;
}
auto valid_signatures = maybe_signed_additional_bid->VerifySignatures();
data_decoder::DataDecoder* data_decoder =
get_data_decoder_callback_.Run(config_->seller);
if (!data_decoder) {
HandleAdditionalBidError(AdditionalBidResult::kRejectedDecoderShutDown,
"Page in shutdown");
return;
}
data_decoder->ParseJson(
maybe_signed_additional_bid->additional_bid_json,
base::BindOnce(&InterestGroupAuction::HandleDecodedAdditionalBid,
weak_ptr_factory_.GetWeakPtr(),
std::move(maybe_signed_additional_bid->signatures),
std::move(valid_signatures), std::move(seller_nonce)));
}
void InterestGroupAuction::HandleDecodedAdditionalBid(
const std::vector<SignedAdditionalBidSignature>& signatures,
const std::vector<size_t>& valid_signatures,
std::optional<std::string> seller_nonce,
data_decoder::DataDecoder::ValueOrError result) {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
if (!result.has_value()) {
HandleAdditionalBidError(
AdditionalBidResult::kRejectedDueToJsonParseError,
"Unable to parse additional bid as JSON: " + result.error());
return;
}
base::expected<AdditionalBidDecodeResult, std::string> maybe_bid =
DecodeAdditionalBid(
this, result.value(),
config_->non_shared_params.auction_nonce.value(),
std::move(seller_nonce), interest_group_buyers_, config_->seller,
parent_
? base::optional_ref<const url::Origin>(parent_->config_->seller)
: base::optional_ref<const url::Origin>(std::nullopt));
if (!maybe_bid.has_value()) {
HandleAdditionalBidError(AdditionalBidResult::kRejectedDueToDecodeError,
std::move(maybe_bid).error());
return;
}
if (!is_interest_group_api_allowed_callback_.Run(
ContentBrowserClient::InterestGroupApiOperation::kBuy,
maybe_bid->bid_state->bidder->interest_group.owner)) {
HandleAdditionalBidError(
AdditionalBidResult::kRejectedDueToBuyerNotAllowed,
"Rejecting an additionalBid due to operation not allowed for buyer.");
return;
}
if (!blink::VerifyAdCurrencyCode(
PerBuyerCurrency(*maybe_bid->bid_state->additional_bid_buyer,
*config_),
maybe_bid->bid->bid_currency)) {
HandleAdditionalBidError(
AdditionalBidResult::kRejectedDueToCurrencyMismatch,
"Rejecting an additionalBid due to currency mismatch.");
return;
}
if (negative_targeter_->ShouldDropDueToNegativeTargeting(
*maybe_bid->bid_state->additional_bid_buyer,
maybe_bid->negative_target_joining_origin,
maybe_bid->negative_target_interest_group_names, signatures,
valid_signatures, config_->seller, *auction_metrics_recorder_,
errors_)) {
auction_metrics_recorder_->RecordAdditionalBidResult(
AdditionalBidResult::kNegativeTargeted);
auction_metrics_recorder_->RecordAdditionalBidDecodeLatency(
base::TimeTicks::Now() - decode_additional_bids_start_time_);
OnScoringDependencyDone();
return;
}
auction_metrics_recorder_->RecordAdditionalBidResult(
AdditionalBidResult::kSentForScoring);
auction_metrics_recorder_->RecordAdditionalBidDecodeLatency(
base::TimeTicks::Now() - decode_additional_bids_start_time_);
bid_states_for_additional_bids_.push_back(std::move(maybe_bid->bid_state));
ScoreBidIfReady(std::move(maybe_bid->bid));
OnScoringDependencyDone();
}
void InterestGroupAuction::OnSellerWorkletFatalError(
AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
AuctionResult result;
switch (fatal_error_type) {
case AuctionWorkletManager::FatalErrorType::kScriptLoadFailed:
result = AuctionResult::kSellerWorkletLoadFailed;
MaybeAddScriptFailureRealTimeContribution(false,
config_->seller);
break;
case AuctionWorkletManager::FatalErrorType::kWorkletCrash:
result = AuctionResult::kSellerWorkletCrashed;
break;
}
OnBiddingAndScoringComplete(result, errors);
}
bool InterestGroupAuction::IsBuyerOptedInToRealTimeReporting(
const url::Origin& owner) {
return config_->non_shared_params.per_buyer_real_time_reporting_types
.has_value() &&
base::Contains(
*config_->non_shared_params.per_buyer_real_time_reporting_types,
owner);
}
void InterestGroupAuction::MaybeAddScriptFailureRealTimeContribution(
bool is_buyer,
const url::Origin& origin) {
if (!base::FeatureList::IsEnabled(
blink::features::kFledgeRealTimeReporting)) {
return;
}
if (!real_time_reporting_num_buckets_.has_value()) {
real_time_reporting_num_buckets_ =
blink::features::kFledgeRealTimeReportingNumBuckets.Get();
}
if (!real_time_platform_contribution_priority_weight_.has_value()) {
real_time_platform_contribution_priority_weight_ =
blink::features::kFledgeRealTimeReportingPlatformContributionPriority
.Get();
}
if (is_buyer && IsBuyerOptedInToRealTimeReporting(origin)) {
real_time_contributions_[origin].push_back(
auction_worklet::mojom::RealTimeReportingContribution::New(
*real_time_reporting_num_buckets_ +
auction_worklet::RealTimeReportingPlatformError::
kBiddingScriptLoadFailure,
*real_time_platform_contribution_priority_weight_,
std::nullopt));
} else if (!is_buyer && config_->non_shared_params
.seller_real_time_reporting_type.has_value()) {
real_time_contributions_[origin].push_back(
auction_worklet::mojom::RealTimeReportingContribution::New(
*real_time_reporting_num_buckets_ +
auction_worklet::RealTimeReportingPlatformError::
kScoringScriptLoadFailure,
*real_time_platform_contribution_priority_weight_,
std::nullopt));
}
}
void InterestGroupAuction::OnComponentAuctionComplete(
InterestGroupAuction* component_auction,
bool success) {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
if (!component_auction->buyer_helpers_.empty() ||
!component_auction->bid_states_for_additional_bids_.empty()) {
auction_metrics_recorder_->RecordComponentAuctionLatency(
base::TimeTicks::Now() - bidding_and_scoring_phase_start_time_);
}
bool is_both = component_auction->NonKAnonWinnerIsKAnon();
ScoredBid* non_kanon_enforced_bid =
component_auction->top_non_kanon_enforced_bid();
if (non_kanon_enforced_bid) {
ScoreBidIfReady(CreateBidFromComponentAuctionWinner(
non_kanon_enforced_bid,
is_both ? auction_worklet::mojom::BidRole::kBothKAnonModes
: auction_worklet::mojom::BidRole::kUnenforcedKAnon));
}
ScoredBid* kanon_bid = component_auction->top_kanon_enforced_bid();
if (kanon_bid && !is_both) {
ScoreBidIfReady(CreateBidFromComponentAuctionWinner(
kanon_bid, auction_worklet::mojom::BidRole::kEnforcedKAnon));
}
OnScoringDependencyDone();
}
std::unique_ptr<InterestGroupAuction::Bid>
InterestGroupAuction::CreateBidFromComponentAuctionWinner(
const ScoredBid* scored_bid,
auction_worklet::mojom::BidRole bid_role) {
const Bid* component_bid = scored_bid->bid.get();
const auto* modified_bid_params =
scored_bid->component_auction_modified_bid_params.get();
DCHECK(modified_bid_params);
return std::make_unique<Bid>(
bid_role, modified_bid_params->ad,
modified_bid_params->bid.has_value() ? modified_bid_params->bid.value()
: component_bid->bid,
modified_bid_params->bid.has_value() ? modified_bid_params->bid_currency
: component_bid->bid_currency,
component_bid->ad_cost, component_bid->ad_descriptor,
component_bid->selected_ad_components, component_bid->modeling_signals,
component_bid->aggregate_win_signals, component_bid->bid_duration,
component_bid->bidding_signals_data_version, component_bid->bid_ad,
component_bid->selected_buyer_and_seller_reporting_id,
component_bid->bid_state, component_bid->auction);
}
void InterestGroupAuction::OnScoringDependencyDone() {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
--num_scoring_dependencies_;
if (num_scoring_dependencies_ == 0 && ReadyToScoreBids()) {
StartPendingScoringSignalsRequests();
}
if (num_scoring_dependencies_ == 0) {
currently_decoding_additional_bids_ = false;
}
MaybeCompleteBiddingAndScoringPhase();
}
void InterestGroupAuction::ScoreBidIfReady(std::unique_ptr<Bid> bid) {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
DCHECK(bid);
DCHECK(bid->bid_state->made_bid);
any_bid_made_ = true;
bid->BeginTracingForScoring();
if (IsBidRoleUsedForWinner(kanon_mode_, bid->bid_role)) {
InterestGroupManagerImpl::InterestGroupObserver::AccessType event_type =
InterestGroupManagerImpl::InterestGroupObserver::kBid;
if (!component_auctions_.empty()) {
event_type =
bid->bid_state->additional_bid_buyer
? InterestGroupManagerImpl::InterestGroupObserver::
kTopLevelAdditionalBid
: InterestGroupManagerImpl::InterestGroupObserver::kTopLevelBid;
} else if (bid->bid_state->additional_bid_buyer) {
event_type =
InterestGroupManagerImpl::InterestGroupObserver::kAdditionalBid;
}
interest_group_manager_->NotifyInterestGroupAccessed(
devtools_auction_id_, event_type,
bid->bid_state->bidder->interest_group.owner,
bid->bid_state->bidder->interest_group.name,
component_auctions_.empty() ? base::optional_ref<const url::Origin>()
: base::optional_ref<const url::Origin>(
bid->auction->config_->seller),
bid->bid,
bid->bid_currency
? base::optional_ref(bid->bid_currency->currency_code())
: base::optional_ref<const std::string>());
}
uint64_t bid_trace_id = bid->TraceIdForScoring();
if (!ReadyToScoreBids()) {
bid->trace_wait_seller_deps_start = base::TimeTicks::Now();
TRACE_EVENT_BEGIN("fledge", "wait_for_seller_deps",
perfetto::Track(bid_trace_id));
unscored_bids_.emplace_back(std::move(bid));
return;
}
ScoreBid(std::move(bid));
}
void InterestGroupAuction::ScoreBid(std::unique_ptr<Bid> bid) {
DCHECK(ReadyToScoreBids());
uint64_t bid_trace_id = bid->TraceIdForScoring();
TRACE_EVENT_BEGIN("fledge",
perfetto::DynamicString(ScoreAdTraceEventName(*bid)),
perfetto::Track(bid_trace_id), "decision_logic_url",
config_->decision_logic_url);
bid->seller_worklet_score_ad_start = base::TimeTicks::Now();
++bids_being_scored_;
mojo::PendingReceiver<auction_worklet::mojom::ScoreAdClient>
score_ad_receiver;
DCHECK_EQ(0, config_->NumPromises());
SubresourceUrlBuilder* url_builder = SubresourceUrlBuilderIfReady();
DCHECK(url_builder);
seller_worklet_handle_->AuthorizeSubresourceUrls(*url_builder);
auction_worklet::mojom::TrustedSignalsCacheKeyPtr cache_key;
std::unique_ptr<TrustedSignalsCacheImpl::Handle> cache_handle;
if (config_->trusted_scoring_signals_url &&
config_->non_shared_params.trusted_scoring_signals_coordinator &&
interest_group_manager_->trusted_signals_cache() &&
seller_worklet_handle_->TrustedScoringSignalsUrlAllowed()) {
int partition_id;
base::Value::Dict additional_params;
if (config_->seller_experiment_group_id) {
additional_params.Set(
"experimentGroupId",
base::NumberToString(*config_->seller_experiment_group_id));
}
cache_handle =
interest_group_manager_->trusted_signals_cache()
->RequestTrustedScoringSignals(
url_loader_factory_,
auction_worklet_manager_->GetFrameTreeNodeID(),
devtools_auction_id_, main_frame_origin_, ip_address_space_,
config_->seller, *config_->trusted_scoring_signals_url,
*config_->non_shared_params.trusted_scoring_signals_coordinator,
bid->interest_group->owner,
bid->bid_state->bidder->joining_origin, bid->ad_descriptor.url,
bid->GetAdComponentUrls(), std::move(additional_params),
GetSellerTKVSignals(), partition_id);
cache_key = auction_worklet::mojom::TrustedSignalsCacheKey::New(
cache_handle->compression_group_token(), partition_id);
}
bool allow_group_by_origin_mode = true;
if (bid->bid_state->additional_bid_buyer ||
!config_->non_shared_params.component_auctions.empty()) {
allow_group_by_origin_mode = false;
}
if (allow_group_by_origin_mode &&
!bid->bid_state->seller_group_by_origin_id.has_value() &&
base::FeatureList::IsEnabled(
blink::features::kFledgeSellerScriptExecutionMode)) {
bid->bid_state->seller_group_by_origin_id =
seller_worklet_handle_->GetGroupByOriginKeyMapper()
.LookupGroupByOriginId(bid->bid_state->bidder,
config_->non_shared_params.execution_mode);
}
size_t maybe_seller_group_by_origin_id =
bid->bid_state->seller_group_by_origin_id.value_or(0);
bool browser_signal_for_debugging_only_sampling = ShouldSampleDebugReport();
seller_worklet_handle_->GetSellerWorklet()->ScoreAd(
bid->ad_metadata, bid->bid, bid->bid_currency, config_->non_shared_params,
std::move(cache_key),
auction_worklet::mojom::CreativeInfoWithoutOwner::New(
bid->ad_descriptor, bid->bid_ad->creative_scanning_metadata),
bid->GetAdComponentCreativeInfo(),
GetDirectFromSellerSellerSignals(url_builder),
GetDirectFromSellerSellerSignalsHeaderAdSlot(
*direct_from_seller_signals_header_ad_slot_),
GetDirectFromSellerAuctionSignals(url_builder),
GetDirectFromSellerAuctionSignalsHeaderAdSlot(
*direct_from_seller_signals_header_ad_slot_),
GetOtherSellerParam(*bid),
parent_ ? PerBuyerCurrency(config_->seller, *parent_->config_)
: std::nullopt,
bid->interest_group->owner, bid->selected_buyer_and_seller_reporting_id,
bid->bid_ad->buyer_and_seller_reporting_id,
bid->bid_duration.InMilliseconds(),
IsOriginInDebugReportCooldownOrLockout(
config_->seller, debug_report_lockout_and_cooldowns_,
base::Time::Now()),
browser_signal_for_debugging_only_sampling, SellerTimeout(),
maybe_seller_group_by_origin_id, allow_group_by_origin_mode, bid_trace_id,
bid->bid_state->bidder->joining_origin,
score_ad_receiver.InitWithNewPipeAndPassRemote());
score_ad_receivers_.Add(
this, std::move(score_ad_receiver),
ScoreAdClientData(std::move(bid), std::move(cache_handle)));
}
bool InterestGroupAuction::ValidateScoreBidCompleteResult(
double score,
auction_worklet::mojom::ComponentAuctionModifiedBidParams*
component_auction_modified_bid_params,
std::optional<double> bid_in_seller_currency,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
const PrivateAggregationRequests& pa_requests,
const RealTimeReportingContributions& real_time_contributions) {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
if (debug_loss_report_url.has_value() &&
!IsUrlValid(debug_loss_report_url.value())) {
score_ad_receivers_.ReportBadMessage(
"Invalid seller debugging loss report URL");
return false;
}
if (debug_win_report_url.has_value() &&
!IsUrlValid(debug_win_report_url.value())) {
score_ad_receivers_.ReportBadMessage(
"Invalid seller debugging win report URL");
return false;
}
if (!std::isfinite(score)) {
score_ad_receivers_.ReportBadMessage("Invalid score");
return false;
}
if (score > 0) {
if ((parent_ == nullptr) !=
(component_auction_modified_bid_params == nullptr)) {
score_ad_receivers_.ReportBadMessage(
"Invalid component_auction_modified_bid_params");
return false;
}
if (component_auction_modified_bid_params &&
component_auction_modified_bid_params->bid.has_value()) {
if (!IsValidBid(component_auction_modified_bid_params->bid.value())) {
score_ad_receivers_.ReportBadMessage(
"Invalid component_auction_modified_bid_params bid");
return false;
}
if (!blink::VerifyAdCurrencyCode(
config_->non_shared_params.seller_currency,
component_auction_modified_bid_params->bid_currency) ||
!blink::VerifyAdCurrencyCode(
PerBuyerCurrency(config_->seller, *parent_->config_),
component_auction_modified_bid_params->bid_currency)) {
score_ad_receivers_.ReportBadMessage(
"Invalid component_auction_modified_bid_params bid_currency");
return false;
}
}
}
if (bid_in_seller_currency.has_value() &&
(!IsValidBid(*bid_in_seller_currency) ||
!config_->non_shared_params.seller_currency.has_value())) {
score_ad_receivers_.ReportBadMessage("Invalid bid_in_seller_currency");
return false;
}
if (!ValidatePrivateAggregationRequests(score_ad_receivers_, pa_requests)) {
return false;
}
if (!std::ranges::all_of(real_time_contributions, HasValidRealTimeBucket)) {
score_ad_receivers_.ReportBadMessage("Invalid real time reporting bucket");
return false;
}
if (!std::ranges::all_of(real_time_contributions,
HasValidRealTimePriorityWeight)) {
score_ad_receivers_.ReportBadMessage(
"Invalid real time reporting priority weight");
return false;
}
return true;
}
void InterestGroupAuction::OnScoreAdComplete(
double score,
auction_worklet::mojom::RejectReason reject_reason,
auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr
component_auction_modified_bid_params,
std::optional<double> bid_in_seller_currency,
std::optional<uint32_t> scoring_signals_data_version,
const std::optional<GURL>& debug_loss_report_url,
const std::optional<GURL>& debug_win_report_url,
PrivateAggregationRequests pa_requests,
RealTimeReportingContributions real_time_contributions,
auction_worklet::mojom::SellerTimingMetricsPtr score_ad_timing_metrics,
auction_worklet::mojom::ScoreAdDependencyLatenciesPtr
score_ad_dependency_latencies,
const std::vector<std::string>& errors) {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
DCHECK_GT(bids_being_scored_, 0);
if (!ValidateScoreBidCompleteResult(
score, component_auction_modified_bid_params.get(),
bid_in_seller_currency, debug_loss_report_url, debug_win_report_url,
pa_requests, real_time_contributions)) {
OnBiddingAndScoringComplete(AuctionResult::kBadMojoMessage);
return;
}
std::unique_ptr<Bid> bid =
std::move(score_ad_receivers_.current_context().bid);
score_ad_receivers_.Remove(score_ad_receivers_.current_receiver());
auction_metrics_recorder_->RecordScoreAdFlowLatency(
base::TimeTicks::Now() - bid->seller_worklet_score_ad_start);
auction_metrics_recorder_->RecordScoreAdLatency(
score_ad_timing_metrics->script_latency);
auction_metrics_recorder_->RecordScoreAdDependencyLatencies(
*score_ad_dependency_latencies);
if (score_ad_timing_metrics->js_fetch_latency.has_value()) {
code_fetch_time_.RecordLatency(*score_ad_timing_metrics->js_fetch_latency);
}
++seller_scripts_ran_;
if (score_ad_timing_metrics->script_timed_out) {
++seller_scripts_timed_out_;
}
TRACE_EVENT_END("fledge", perfetto::Track(bid->TraceIdForScoring()));
bid->EndTracingForScoring();
bid->bid_state->pa_timings(seller_phase()).script_run_time =
score_ad_timing_metrics->script_latency;
bid->bid_state->pa_timings(seller_phase()).signals_fetch_time =
score_ad_dependency_latencies->trusted_scoring_signals_latency.value_or(
base::TimeDelta());
--bids_being_scored_;
if (IsBidRoleUsedForWinner(kanon_mode_, bid->bid_role)) {
CHECK(std::ranges::none_of(
pa_requests,
[](const auction_worklet::mojom::PrivateAggregationRequestPtr&
request_ptr) { return request_ptr.is_null(); }));
MaybeLogPrivateAggregationWebFeatures(pa_requests);
if (!pa_requests.empty()) {
CHECK(config_);
PrivateAggregationPhaseKey agg_key = {
config_->seller, seller_phase(),
config_->aggregation_coordinator_origin};
PrivateAggregationRequests& pa_requests_for_seller =
bid->bid_state->private_aggregation_requests[std::move(agg_key)];
for (auction_worklet::mojom::PrivateAggregationRequestPtr& request :
pa_requests) {
if (request->contribution->is_for_event_contribution() &&
request->contribution->get_for_event_contribution()
->event_type->is_non_reserved()) {
continue;
}
pa_requests_for_seller.emplace_back(std::move(request));
}
}
++seller_reserved_once_rep_count_;
if (seller_reserved_once_rep_count_ == 1 ||
base::RandInt(1, seller_reserved_once_rep_count_) == 1) {
seller_reserved_once_rep_ = bid->bid_state.get();
}
if (base::FeatureList::IsEnabled(
blink::features::kFledgeRealTimeReporting)) {
if (config_->non_shared_params.seller_real_time_reporting_type
.has_value()) {
RealTimeReportingContributions& real_time_contributions_for_origin =
bid->bid_state->real_time_contributions[config_->seller];
if (!real_time_contributions.empty()) {
real_time_contributions_for_origin.insert(
real_time_contributions_for_origin.end(),
std::move_iterator(real_time_contributions.begin()),
std::move_iterator(real_time_contributions.end()));
}
}
}
if (bid->auction == this) {
bid->bid_state->seller_debug_loss_report_url =
std::move(debug_loss_report_url);
bid->bid_state->seller_debug_win_report_url =
std::move(debug_win_report_url);
if (score <= 0) {
bid->bid_state->reject_reason = reject_reason;
}
} else {
bid->bid_state->top_level_seller_debug_loss_report_url =
std::move(debug_loss_report_url);
bid->bid_state->top_level_seller_debug_win_report_url =
std::move(debug_win_report_url);
}
}
errors_.insert(errors_.end(), errors.begin(), errors.end());
if (score > 0) {
switch (bid->bid_role) {
case auction_worklet::mojom::BidRole::kUnenforcedKAnon:
UpdateAuctionLeaders(std::move(bid), score,
std::move(component_auction_modified_bid_params),
bid_in_seller_currency,
scoring_signals_data_version,
non_kanon_enforced_auction_leader_);
break;
case auction_worklet::mojom::BidRole::kEnforcedKAnon:
UpdateAuctionLeaders(std::move(bid), score,
std::move(component_auction_modified_bid_params),
bid_in_seller_currency,
scoring_signals_data_version,
kanon_enforced_auction_leader_);
break;
case auction_worklet::mojom::BidRole::kBothKAnonModes: {
auto bid_copy = std::make_unique<Bid>(*bid);
auto modified_bid_params_copy =
component_auction_modified_bid_params
? component_auction_modified_bid_params->Clone()
: auction_worklet::mojom::
ComponentAuctionModifiedBidParamsPtr();
UpdateAuctionLeaders(std::move(bid), score,
std::move(component_auction_modified_bid_params),
bid_in_seller_currency,
scoring_signals_data_version,
non_kanon_enforced_auction_leader_);
UpdateAuctionLeaders(
std::move(bid_copy), score, std::move(modified_bid_params_copy),
bid_in_seller_currency, scoring_signals_data_version,
kanon_enforced_auction_leader_);
}
}
}
bid.reset();
MaybeCompleteBiddingAndScoringPhase();
}
void InterestGroupAuction::UpdateAuctionLeaders(
std::unique_ptr<Bid> bid,
double score,
auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr
component_auction_modified_bid_params,
std::optional<double> bid_in_seller_currency,
std::optional<uint32_t> scoring_signals_data_version,
LeaderInfo& leader_info) {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
bool is_top_bid = false;
const url::Origin& owner = bid->interest_group->owner;
if (!leader_info.top_bid || score > leader_info.top_bid->score) {
is_top_bid = true;
if (leader_info.top_bid) {
OnNewHighestScoringOtherBid(
leader_info.top_bid->score, leader_info.top_bid->bid->bid,
leader_info.top_bid->bid_in_seller_currency,
&leader_info.top_bid->bid->interest_group->owner, leader_info);
}
leader_info.num_top_bids = 1;
leader_info.at_most_one_top_bid_owner = true;
} else if (score == leader_info.top_bid->score) {
++leader_info.num_top_bids;
if (1 == base::RandInt(1, leader_info.num_top_bids)) {
is_top_bid = true;
}
if (owner != leader_info.top_bid->bid->interest_group->owner) {
leader_info.at_most_one_top_bid_owner = false;
}
double new_highest_scoring_other_bid =
is_top_bid ? leader_info.top_bid->bid->bid : bid->bid;
std::optional<double> new_highest_scoring_other_bid_in_seller_currency =
is_top_bid ? leader_info.top_bid->bid_in_seller_currency
: bid_in_seller_currency;
OnNewHighestScoringOtherBid(
score, new_highest_scoring_other_bid,
new_highest_scoring_other_bid_in_seller_currency,
leader_info.at_most_one_top_bid_owner ? &bid->interest_group->owner
: nullptr,
leader_info);
} else if (score >= leader_info.second_highest_score) {
OnNewHighestScoringOtherBid(score, bid->bid, bid_in_seller_currency, &owner,
leader_info);
}
if (is_top_bid) {
leader_info.top_bid = std::make_unique<ScoredBid>(
score, std::move(scoring_signals_data_version), std::move(bid),
std::move(bid_in_seller_currency),
std::move(component_auction_modified_bid_params));
}
}
void InterestGroupAuction::OnNewHighestScoringOtherBid(
double score,
double bid_value,
std::optional<double> bid_in_seller_currency,
const url::Origin* owner,
LeaderInfo& leader_info) {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
if (score > leader_info.second_highest_score) {
leader_info.highest_scoring_other_bid = bid_value;
leader_info.highest_scoring_other_bid_in_seller_currency =
bid_in_seller_currency;
leader_info.num_second_highest_bids = 1;
if (!owner) {
leader_info.highest_scoring_other_bid_owner.reset();
} else {
leader_info.highest_scoring_other_bid_owner = *owner;
}
leader_info.second_highest_score = score;
return;
}
DCHECK_EQ(score, leader_info.second_highest_score);
if (!owner || *owner != leader_info.highest_scoring_other_bid_owner) {
leader_info.highest_scoring_other_bid_owner.reset();
}
++leader_info.num_second_highest_bids;
if (1 == base::RandInt(1, leader_info.num_second_highest_bids)) {
leader_info.highest_scoring_other_bid = bid_value;
leader_info.highest_scoring_other_bid_in_seller_currency =
bid_in_seller_currency;
}
}
std::optional<base::TimeDelta> InterestGroupAuction::SellerTimeout() {
if (config_->non_shared_params.seller_timeout.has_value()) {
return std::min(config_->non_shared_params.seller_timeout.value(),
kMaxPerBuyerTimeout);
}
return std::nullopt;
}
void InterestGroupAuction::MaybeCompleteBiddingAndScoringPhase() {
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
if (!IsBiddingAndScoringPhaseComplete()) {
return;
}
all_bids_scored_ = true;
AuctionResult result = AuctionResult::kSuccess;
if (is_server_auction_) {
result = saved_response_->result;
}
if (result == AuctionResult::kSuccess && !top_bid()) {
if (any_bid_made_) {
result = AuctionResult::kAllBidsRejected;
} else {
result = HasInterestGroups() ? AuctionResult::kNoBids
: AuctionResult::kNoInterestGroups;
}
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&InterestGroupAuction::OnBiddingAndScoringComplete,
weak_ptr_factory_.GetWeakPtr(), result,
std::vector<std::string>()));
}
void InterestGroupAuction::OnBiddingAndScoringComplete(
AuctionResult auction_result,
const std::vector<std::string>& errors) {
DCHECK(bidding_and_scoring_phase_callback_);
DCHECK(!final_auction_result_);
DCHECK_EQ(bidding_and_scoring_phase_state_, PhaseState::kDuring);
bidding_and_scoring_phase_state_ = PhaseState::kAfter;
TRACE_EVENT_END("fledge", perfetto::Track(*trace_id_));
errors_.insert(errors_.end(), errors.begin(), errors.end());
if (parent_) {
seller_worklet_handle_.reset();
}
if (on_seller_process_assigned_callback_) {
std::move(on_seller_process_assigned_callback_).Run();
}
bool success = auction_result == AuctionResult::kSuccess;
if (!success) {
ClosePipes();
final_auction_result_ = auction_result;
} else {
if (!parent_) {
final_auction_result_ = AuctionResult::kSuccess;
if (top_bid()) {
top_bid()->bid->auction->final_auction_result_ =
AuctionResult::kSuccess;
}
}
}
for (auto& component_auction_info : component_auctions_) {
InterestGroupAuction* component_auction =
component_auction_info.second.get();
ScoredBid* winner = top_bid();
if (winner && winner->bid->auction == component_auction) {
continue;
}
if (component_auction->final_auction_result_) {
continue;
}
component_auction->final_auction_result_ =
AuctionResult::kComponentLostAuction;
}
std::move(bidding_and_scoring_phase_callback_).Run(success);
}
auction_worklet::mojom::ComponentAuctionOtherSellerPtr
InterestGroupAuction::GetOtherSellerParam(const Bid& bid) const {
auction_worklet::mojom::ComponentAuctionOtherSellerPtr
browser_signals_other_seller;
if (parent_) {
browser_signals_other_seller =
auction_worklet::mojom::ComponentAuctionOtherSeller::NewTopLevelSeller(
parent_->config_->seller);
} else if (bid.auction != this) {
browser_signals_other_seller =
auction_worklet::mojom::ComponentAuctionOtherSeller::NewComponentSeller(
bid.auction->config_->seller);
}
return browser_signals_other_seller;
}
AuctionWorkletManager::WorkletKey InterestGroupAuction::BidderWorkletKey(
BidState& bid_state) {
DCHECK(!bid_state.worklet_handle);
DCHECK(!bid_state.additional_bid_buyer);
const blink::InterestGroup& interest_group = bid_state.bidder->interest_group;
std::optional<uint16_t> experiment_group_id =
GetBuyerExperimentId(*config_, interest_group.owner);
return AuctionWorkletManager::BidderWorkletKey(
interest_group.bidding_url.value_or(GURL()),
interest_group.bidding_wasm_helper_url,
interest_group.trusted_bidding_signals_url,
false, experiment_group_id,
GetTrustedBiddingSignalsSlotSizeParam(
interest_group.trusted_bidding_signals_slot_size_mode),
interest_group.trusted_bidding_signals_coordinator,
std::nullopt);
}
const std::string& InterestGroupAuction::GetTrustedBiddingSignalsSlotSizeParam(
blink::InterestGroup::TrustedBiddingSignalsSlotSizeMode
trusted_bidding_signals_slot_size_mode) {
auto it = trusted_bidding_signals_size_mode_strings_.find(
trusted_bidding_signals_slot_size_mode);
if (it != trusted_bidding_signals_size_mode_strings_.end()) {
return it->second;
}
auto inserted = trusted_bidding_signals_size_mode_strings_.insert(
std::pair(trusted_bidding_signals_slot_size_mode,
CreateTrustedBiddingSignalsSlotSizeParam(
*config_, trusted_bidding_signals_slot_size_mode)));
DCHECK(inserted.second);
return inserted.first->second;
}
void InterestGroupAuction::OnDecompressedServerResponse(
AdAuctionRequestContext* request_context,
base::RepeatingCallback<AdAuctionPageData*()> ad_auction_page_data_callback,
bool authorized,
base::expected<mojo_base::BigBuffer, std::string> result) {
data_decoder::DataDecoder* data_decoder =
get_data_decoder_callback_.Run(config_->seller);
if (!result.has_value() || !data_decoder) {
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
data_decoder ? InvalidServerResponseReason::kDecompressFailure
: InvalidServerResponseReason::kDecoderShutdown);
errors_.push_back("runAdAuction(): Could not decompress server response");
if (bidding_and_scoring_phase_state_ == PhaseState::kDuring) {
MaybeCompleteBiddingAndScoringPhase();
}
return;
}
data_decoder->ParseCbor(
result.value(),
base::BindOnce(
&InterestGroupAuction::OnParsedServerResponse,
weak_ptr_factory_.GetWeakPtr(),
base::Unretained(request_context),
std::move(ad_auction_page_data_callback), authorized));
}
void InterestGroupAuction::OnParsedServerResponse(
AdAuctionRequestContext* request_context,
base::RepeatingCallback<AdAuctionPageData*()> ad_auction_page_data_callback,
bool authorized,
data_decoder::DataDecoder::ValueOrError result) {
if (!OnParsedServerResponseImpl(request_context,
std::move(ad_auction_page_data_callback),
authorized, std::move(result))) {
DCHECK(saved_response_);
if (bidding_and_scoring_phase_state_ == PhaseState::kDuring) {
MaybeCompleteBiddingAndScoringPhase();
}
}
}
bool InterestGroupAuction::OnParsedServerResponseImpl(
AdAuctionRequestContext* request_context,
base::RepeatingCallback<AdAuctionPageData*()> ad_auction_page_data_callback,
bool authorized,
data_decoder::DataDecoder::ValueOrError result) {
if (!result.has_value()) {
errors_.push_back("runAdAuction(): Could not parse server response");
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kCBORParseFailure);
return false;
}
std::optional<BiddingAndAuctionResponse> response =
BiddingAndAuctionResponse::TryParse(
std::move(result).value(), request_context->group_names,
request_context->group_pagg_coordinators);
if (!response) {
errors_.push_back(
"runAdAuction(): Could not parse server response structure");
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kUnexpectedResponseStructure);
return false;
}
AdAuctionPageData* ad_auction_page_data = ad_auction_page_data_callback.Run();
if (!ad_auction_page_data) {
saved_response_.emplace();
return false;
}
if (!authorized &&
(!response->nonce ||
!ad_auction_page_data->WitnessedAuctionResultNonceForOrigin(
config_->seller, *response->nonce))) {
saved_response_.emplace();
base::UmaHistogramEnumeration(kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kNotWitnessed);
errors_.push_back(
base::StrCat({"runAdAuction(): Server response was not witnessed from ",
config_->seller.Serialize()}));
return false;
}
if (config_->non_shared_params.interest_group_buyers.has_value()) {
const std::vector<url::Origin>& buyers =
config_->non_shared_params.interest_group_buyers.value();
post_auction_update_owners_.insert(post_auction_update_owners_.end(),
buyers.begin(), buyers.end());
}
if (response->error) {
errors_.push_back(base::StrCat({"runAdAuction(): ", *response->error}));
}
if (response->is_chaff) {
saved_response_.emplace();
saved_response_->result = AuctionResult::kNoBids;
return false;
}
if (parent_) {
if (!response->top_level_seller) {
errors_.push_back(
"runAdAuction(): got server response for top-level auction in "
"multi-level auction.");
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kNotMultilevel);
return false;
} else if (parent_->config_->seller != response->top_level_seller.value()) {
errors_.push_back(base::StrCat(
{"runAdAuction(): got server response for toplevel seller `",
response->top_level_seller->Serialize(),
"`, expected top level seller `", config_->seller.Serialize(),
"`."}));
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kTopLevelSellerMismatch);
return false;
}
} else if (response->top_level_seller) {
errors_.push_back(
"runAdAuction(): got server response for multi-level auction in "
"single-level auction.");
saved_response_.emplace();
base::UmaHistogramEnumeration(kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kNotSingleLevel);
return false;
}
if (response->bid && !IsValidBid(*response->bid)) {
errors_.push_back(base::StrCat({"runAdAuction(): Invalid bid value ",
base::NumberToString(*response->bid)}));
saved_response_.emplace();
base::UmaHistogramEnumeration(kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kInvalidBid);
return false;
}
any_bid_made_ = !response->bidding_groups.empty();
if (!response->ad_render_url.is_valid()) {
OnLoadedWinningGroup(std::move(response).value(),
std::nullopt);
} else {
blink::InterestGroupKey winning_group(response->interest_group_owner,
response->interest_group_name);
if (!base::Contains(response->bidding_groups, winning_group)) {
errors_.push_back("runAdAuction(): Winning group must be a bidder");
saved_response_.emplace();
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kWinningGroupNotBidder);
return false;
}
interest_group_manager_->GetInterestGroup(
winning_group,
base::BindOnce(&InterestGroupAuction::OnLoadedWinningGroup,
weak_ptr_factory_.GetWeakPtr(),
std::move(response).value()));
}
return true;
}
void InterestGroupAuction::OnLoadedWinningGroup(
BiddingAndAuctionResponse response,
std::optional<SingleStorageInterestGroup> maybe_group) {
if (response.k_anon_ghost_winner) {
blink::InterestGroupKey group =
response.k_anon_ghost_winner->interest_group;
interest_group_manager_->GetInterestGroup(
group, base::BindOnce(&InterestGroupAuction::OnLoadedGhostWinnerGroup,
weak_ptr_factory_.GetWeakPtr(),
std::move(response), std::move(maybe_group)));
} else {
OnLoadedGhostWinnerGroup(std::move(response), std::move(maybe_group),
std::nullopt);
}
}
void InterestGroupAuction::OnLoadedGhostWinnerGroup(
BiddingAndAuctionResponse response,
std::optional<SingleStorageInterestGroup> maybe_group,
std::optional<SingleStorageInterestGroup> maybe_ghost_group) {
OnLoadedGhostWinnerGroupImpl(std::move(response), std::move(maybe_group),
std::move(maybe_ghost_group));
DCHECK(saved_response_);
MaybeLoadDebugReportLockoutAndCooldowns();
}
void InterestGroupAuction::OnLoadedGhostWinnerGroupImpl(
BiddingAndAuctionResponse response,
std::optional<SingleStorageInterestGroup> maybe_group,
std::optional<SingleStorageInterestGroup> maybe_ghost_group) {
std::vector<SingleStorageInterestGroup> groups;
if (response.ad_render_url.is_valid()) {
response.result = AuctionResult::kSuccess;
} else {
response.result = AuctionResult::kAllBidsRejected;
}
if (maybe_group) {
if (maybe_group.value()->interest_group.bidding_url) {
groups.push_back(std::move(*maybe_group));
} else {
response.result = AuctionResult::kInvalidServerResponse;
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kMissingWinningGroupBidURL);
errors_.emplace_back(
"runAdAuction(): Winning group doesn't have a bidding URL");
}
} else if (response.ad_render_url.is_valid()) {
response.ad_render_url = GURL();
response.result = AuctionResult::kInvalidServerResponse;
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kMissingWinningGroup);
errors_.emplace_back(
"runAdAuction(): Could not load winning interest group");
}
if (maybe_ghost_group) {
if (response.k_anon_ghost_winner &&
!response.k_anon_ghost_winner->ghost_winner) {
std::optional<BiddingAndAuctionResponse::GhostWinnerForTopLevelAuction>
maybe_ghost_winner = ConstructGhostWinnerFromGroupAndCandidate(
(*maybe_ghost_group)->interest_group,
response.k_anon_ghost_winner->candidate);
if (!maybe_ghost_winner) {
errors_.emplace_back(
"runAdAuction(): Failed to reconstruct ghost winner");
response.k_anon_ghost_winner.reset();
} else {
response.k_anon_ghost_winner->ghost_winner =
std::move(maybe_ghost_winner);
}
}
if (groups.size() == 0 || (*maybe_ghost_group)->interest_group.owner ==
groups[0]->interest_group.owner) {
groups.push_back(std::move(*maybe_ghost_group));
}
}
for (const auto& [group_key, update_if_older_than] :
response.triggered_updates) {
HandleUpdateIfOlderThan(group_key.owner, group_key.name,
update_if_older_than);
}
if (groups.size() > 0) {
auto buyer_helper = std::make_unique<BuyerHelper>(this, std::move(groups));
buyer_helpers_.emplace_back(std::move(buyer_helper));
}
if (maybe_ghost_group) {
std::vector<SingleStorageInterestGroup> ghost_groups;
ghost_groups.push_back(std::move(*maybe_ghost_group));
auto ghost_buyer_helper =
std::make_unique<BuyerHelper>(this, std::move(ghost_groups));
buyer_helpers_.emplace_back(std::move(ghost_buyer_helper));
}
saved_response_ = std::move(response);
}
void InterestGroupAuction::MaybeLoadDebugReportLockoutAndCooldowns() {
if (saved_response_->result != AuctionResult::kInvalidServerResponse &&
base::FeatureList::IsEnabled(
blink::features::kFledgeSampleDebugReports) &&
!server_auction_debug_report_lockout_loaded_) {
base::flat_set<url::Origin> origins =
saved_response_->debugging_only_report_origins;
interest_group_manager_->GetDebugReportLockoutAndCooldowns(
std::move(origins),
base::BindOnce(
&InterestGroupAuction::OnLoadDebugReportLockoutAndCooldownsComplete,
weak_ptr_factory_.GetWeakPtr()));
} else {
OnLoadDebugReportLockoutAndCooldownsComplete(
std::nullopt);
}
}
void InterestGroupAuction::OnLoadDebugReportLockoutAndCooldownsComplete(
std::optional<DebugReportLockoutAndCooldowns>
debug_report_lockout_and_cooldowns) {
if (!server_auction_debug_report_lockout_loaded_) {
debug_report_lockout_and_cooldowns_ =
std::move(debug_report_lockout_and_cooldowns);
server_auction_debug_report_lockout_loaded_ = true;
}
if (saved_response_ &&
bidding_and_scoring_phase_state_ == PhaseState::kDuring &&
!started_creating_bid_from_response_) {
started_creating_bid_from_response_ = true;
CreateBidFromServerResponse();
num_scoring_dependencies_ = 0;
MaybeCompleteBiddingAndScoringPhase();
}
}
void InterestGroupAuction::CreateBidFromServerResponse() {
DCHECK(saved_response_);
DCHECK_EQ(PhaseState::kDuring, bidding_and_scoring_phase_state_);
if (parent_ && !saved_response_->bid) {
saved_response_->result = AuctionResult::kInvalidServerResponse;
errors_.emplace_back("runAdAuction(): Missing bid value");
return;
}
std::unique_ptr<Bid> kanon_bid, non_kanon_bid;
auction_worklet::mojom::ComponentAuctionModifiedBidParamsPtr
kanon_modified_bid_params,
non_kanon_modified_bid_params;
bool server_supports_kanon =
saved_response_->k_anon_join_candidate.has_value() ||
saved_response_->k_anon_ghost_winner.has_value();
bool enable_kanon =
server_supports_kanon &&
kanon_mode_ == auction_worklet::mojom::KAnonymityBidMode::kEnforce &&
base::FeatureList::IsEnabled(features::kEnableBandAKAnonEnforcement);
if (parent_) {
if (!blink::VerifyAdCurrencyCode(config_->non_shared_params.seller_currency,
saved_response_->bid_currency)) {
saved_response_->ad_render_url = GURL();
saved_response_->result = AuctionResult::kInvalidServerResponse;
base::UmaHistogramEnumeration(kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kBadCurrency);
errors_.emplace_back(
"runAdAuction(): currency didn't match auction config");
} else if (!blink::VerifyAdCurrencyCode(
PerBuyerCurrency(config_->seller, *parent_->config_),
saved_response_->bid_currency)) {
saved_response_->ad_render_url = GURL();
saved_response_->result = AuctionResult::kInvalidServerResponse;
base::UmaHistogramEnumeration(kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kBadCurrency);
errors_.emplace_back(
"runAdAuction(): currency didn't match top-level per-buyer currency");
} else {
kanon_modified_bid_params =
auction_worklet::mojom::ComponentAuctionModifiedBidParams::New();
kanon_modified_bid_params->ad =
saved_response_->ad_metadata.value_or("null");
}
if (enable_kanon) {
non_kanon_modified_bid_params =
auction_worklet::mojom::ComponentAuctionModifiedBidParams::New();
if (saved_response_->k_anon_ghost_winner &&
saved_response_->k_anon_ghost_winner->ghost_winner &&
saved_response_->k_anon_ghost_winner->ghost_winner->bid_currency) {
if (!blink::VerifyAdCurrencyCode(
config_->non_shared_params.seller_currency,
saved_response_->k_anon_ghost_winner->ghost_winner
->bid_currency)) {
saved_response_->k_anon_ghost_winner.reset();
errors_.emplace_back(
"runAdAuction(): currency didn't match auction config for ghost "
"winner");
} else if (!blink::VerifyAdCurrencyCode(
PerBuyerCurrency(config_->seller, *parent_->config_),
saved_response_->k_anon_ghost_winner->ghost_winner
->bid_currency)) {
saved_response_->k_anon_ghost_winner.reset();
errors_.emplace_back(
"runAdAuction(): currency didn't match top-level per-buyer "
"currency for ghost winner");
}
non_kanon_modified_bid_params->ad =
saved_response_->k_anon_ghost_winner->ghost_winner->ad_metadata
.value_or("null");
}
} else {
non_kanon_modified_bid_params = kanon_modified_bid_params.Clone();
}
}
if (enable_kanon) {
if (saved_response_->ad_render_url.is_valid()) {
kanon_bid = CreatePrimaryBidFromServerResponse(
auction_worklet::mojom::BidRole::kEnforcedKAnon);
if (!kanon_bid) {
saved_response_->result = AuctionResult::kInvalidServerResponse;
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kConstructBidFailure);
errors_.emplace_back(
"runAdAuction(): Couldn't reconstruct winning bid");
}
}
if (saved_response_->k_anon_ghost_winner) {
non_kanon_bid = CreateGhostBidFromServerResponse();
if (!non_kanon_bid) {
errors_.push_back("runAdAuction(): Could not reconstruct ghost bid");
}
} else if (kanon_bid) {
kanon_bid->bid_role = auction_worklet::mojom::BidRole::kBothKAnonModes;
non_kanon_bid = std::make_unique<Bid>(*kanon_bid);
non_kanon_bid->bid_role =
auction_worklet::mojom::BidRole::kBothKAnonModes;
non_kanon_modified_bid_params = kanon_modified_bid_params.Clone();
}
} else if (saved_response_->ad_render_url.is_valid()) {
kanon_bid = CreatePrimaryBidFromServerResponse(
auction_worklet::mojom::BidRole::kUnenforcedKAnon);
if (kanon_bid) {
non_kanon_bid = std::make_unique<Bid>(*kanon_bid);
non_kanon_bid->bid_role =
auction_worklet::mojom::BidRole::kBothKAnonModes;
kanon_bid->bid_role = auction_worklet::mojom::BidRole::kBothKAnonModes;
} else {
saved_response_->result = AuctionResult::kInvalidServerResponse;
base::UmaHistogramEnumeration(
kInvalidServerResponseReasonUMAName,
InvalidServerResponseReason::kConstructBidFailure);
errors_.emplace_back("runAdAuction(): Couldn't reconstruct winning bid");
}
}
if (kanon_bid) {
UpdateAuctionLeaders(std::move(kanon_bid),
saved_response_->score.value_or(0.00001),
std::move(kanon_modified_bid_params),
std::nullopt,
std::nullopt,
kanon_enforced_auction_leader_);
}
if (non_kanon_bid) {
UpdateAuctionLeaders(std::move(non_kanon_bid),
saved_response_->score.value_or(0.00001),
std::move(non_kanon_modified_bid_params),
std::nullopt,
std::nullopt,
non_kanon_enforced_auction_leader_);
}
if (buyer_helpers_.empty() &&
saved_response_->result != AuctionResult::kInvalidServerResponse) {
TakeServerFilteredDebugReportUrls(
saved_response_->server_filtered_debugging_only_reports,
debug_report_lockout_and_cooldowns_,
new_debug_report_lockout_and_cooldowns_, debug_loss_report_urls_);
saved_response_->server_filtered_debugging_only_reports.clear();
for (auto& [key, requests] :
saved_response_->server_filtered_pagg_requests_reserved) {
FinalizedPrivateAggregationRequests& destination_vector =
private_aggregation_requests_reserved_[key];
destination_vector.insert(destination_vector.end(),
std::move_iterator(requests.begin()),
std::move_iterator(requests.end()));
}
}
}
std::unique_ptr<InterestGroupAuction::Bid>
InterestGroupAuction::CreatePrimaryBidFromServerResponse(
auction_worklet::mojom::BidRole bid_role) {
DCHECK(saved_response_->ad_render_url.is_valid());
CHECK(!buyer_helpers_.empty());
blink::InterestGroupKey winning_group(saved_response_->interest_group_owner,
saved_response_->interest_group_name);
std::vector<blink::AdDescriptor> ad_components;
std::ranges::transform(
saved_response_->ad_components, std::back_inserter(ad_components),
[](const GURL& url) { return blink::AdDescriptor(url); });
return buyer_helpers_[0]->TryToCreateBidFromServerResponse(
winning_group, bid_role, saved_response_->bid.value_or(0.0001),
saved_response_->bid_currency, saved_response_->ad_metadata,
saved_response_->buyer_reporting_id,
saved_response_->buyer_and_seller_reporting_id,
saved_response_->selected_buyer_and_seller_reporting_id,
saved_response_->k_anon_join_candidate,
blink::AdDescriptor(saved_response_->ad_render_url),
std::move(ad_components),
std::move(saved_response_->component_win_pagg_requests),
std::move(saved_response_->server_filtered_pagg_requests_reserved),
std::move(saved_response_->server_filtered_pagg_requests_non_reserved),
{},
std::move(saved_response_->component_win_debugging_only_reports),
std::move(saved_response_->server_filtered_debugging_only_reports));
}
std::unique_ptr<InterestGroupAuction::Bid>
InterestGroupAuction::CreateGhostBidFromServerResponse() {
DCHECK(saved_response_->k_anon_ghost_winner);
const auto& buyer_helper = std::ranges::find_if(
buyer_helpers_, [this](std::unique_ptr<BuyerHelper>& helper) {
return helper->owner() ==
saved_response_->k_anon_ghost_winner->interest_group.owner;
});
if (buyer_helper == buyer_helpers_.end()) {
return nullptr;
}
BuyerHelper* helper = buyer_helper->get();
CHECK(saved_response_->k_anon_ghost_winner->ghost_winner);
std::vector<blink::AdDescriptor> ghost_ad_components;
std::ranges::transform(
saved_response_->k_anon_ghost_winner->ghost_winner->ad_components,
std::back_inserter(ghost_ad_components),
[](const GURL& url) { return blink::AdDescriptor(url); });
return helper->TryToCreateBidFromServerResponse(
saved_response_->k_anon_ghost_winner->interest_group,
auction_worklet::mojom::BidRole::kUnenforcedKAnon,
saved_response_->k_anon_ghost_winner->ghost_winner->modified_bid,
saved_response_->k_anon_ghost_winner->ghost_winner->bid_currency,
saved_response_->k_anon_ghost_winner->ghost_winner->ad_metadata,
saved_response_->k_anon_ghost_winner->ghost_winner->buyer_reporting_id,
saved_response_->k_anon_ghost_winner->ghost_winner
->buyer_and_seller_reporting_id,
saved_response_->k_anon_ghost_winner->ghost_winner
->selected_buyer_and_seller_reporting_id,
saved_response_->k_anon_ghost_winner->candidate,
blink::AdDescriptor(
saved_response_->k_anon_ghost_winner->ghost_winner->ad_render_url),
std::move(ghost_ad_components),
{},
{},
{},
std::move(saved_response_->k_anon_ghost_winner
->non_kanon_private_aggregation_requests),
{},
{});
}
void InterestGroupAuction::OnDirectFromSellerSignalHeaderAdSlotResolved(
std::string ad_slot,
scoped_refptr<HeaderDirectFromSellerSignals::Result> signals) {
DCHECK_NE(bidding_and_scoring_phase_state_, PhaseState::kAfter);
CHECK(direct_from_seller_signals_header_ad_slot_pending_);
CHECK(direct_from_seller_signals_header_ad_slot_);
if (signals) {
direct_from_seller_signals_header_ad_slot_ = std::move(signals);
} else {
errors_.push_back(DirectFromSellerSignalsHeaderAdSlotNoMatchError(ad_slot));
}
direct_from_seller_signals_header_ad_slot_pending_ = false;
if (bidding_and_scoring_phase_state_ == PhaseState::kDuring) {
for (const auto& buyer_helper : buyer_helpers_) {
buyer_helper->NotifyConfigDependencyResolved();
}
ScoreQueuedBidsIfReady();
OnScoringDependencyDone();
}
}
void InterestGroupAuction::UpdateIgSizeMetrics(
const std::vector<SingleStorageInterestGroup>& interest_groups) {
for (const SingleStorageInterestGroup& group : interest_groups) {
interest_groups_bytes_for_metrics_ += group->interest_group.EstimateSize();
if (group->interest_group.ads) {
for (const blink::InterestGroup::Ad& ad : *group->interest_group.ads) {
ads_and_ad_components_bytes_for_metrics_ += ad.EstimateSize();
}
}
if (group->interest_group.ad_components) {
for (const blink::InterestGroup::Ad& ad_components :
*group->interest_group.ad_components) {
ads_and_ad_components_bytes_for_metrics_ +=
ad_components.EstimateSize();
}
}
}
}
}