0

[B&A] Wire up forDebuggingOnly reports received from B&A.

Takes fDO reports from B&A servers, and collect/send them on Chrome
side. Also updates lockout/cooldown with server's fDO reports.

Bug: b/356872513

Change-Id: Icbb4af8c6ff901bb08cbbff2b4b9dbea52370986
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5769675
Commit-Queue: Qingxin Wu <qingxinwu@google.com>
Reviewed-by: Russ Hamilton <behamilton@google.com>
Cr-Commit-Position: refs/heads/main@{#1355525}
This commit is contained in:
Qingxin Wu
2024-09-14 01:22:18 +00:00
committed by Chromium LUCI CQ
parent 593d13bf1c
commit 35532fa118
6 changed files with 1387 additions and 149 deletions

File diff suppressed because it is too large Load Diff

@ -484,6 +484,7 @@ void BiddingAndAuctionResponse::TryParseForDebuggingOnlyReports(
if (!report_dict) {
continue;
}
output.debugging_only_report_origins.emplace(ad_tech_origin);
TryParseSingleDebugReport(ad_tech_origin, *report_dict, output);
}
}
@ -495,11 +496,8 @@ void BiddingAndAuctionResponse::TryParseSingleDebugReport(
const url::Origin& ad_tech_origin,
const base::Value::Dict& report_dict,
BiddingAndAuctionResponse& output) {
std::optional<bool> maybe_is_win_report = report_dict.FindBool("isWinReport");
bool is_win_report = maybe_is_win_report.has_value() && *maybe_is_win_report;
std::optional<bool> maybe_component_win =
report_dict.FindBool("componentWin");
const std::string* maybe_url_str = report_dict.FindString("url");
if (maybe_url_str) {
GURL reporting_url(*maybe_url_str);
@ -511,22 +509,30 @@ void BiddingAndAuctionResponse::TryParseSingleDebugReport(
output.server_filtered_debugging_only_reports[ad_tech_origin]
.emplace_back(reporting_url);
} else {
output
.component_winner_debugging_only_reports[std::make_pair(
ad_tech_origin, is_win_report)]
.emplace_back(reporting_url);
std::optional<bool> maybe_is_win_report =
report_dict.FindBool("isWinReport");
bool is_win_report =
maybe_is_win_report.has_value() && *maybe_is_win_report;
std::optional<bool> maybe_seller_report =
report_dict.FindBool("isSellerReport");
bool is_seller_report =
maybe_seller_report.has_value() && *maybe_seller_report;
output.component_win_debugging_only_reports[DebugReportKey(
is_seller_report, is_win_report)] = reporting_url;
}
} else {
// "url" field is allowed to be not set in debugReports, for cases like
// forDebuggingOnly APIs were called but server side sampling filtered them
// out. There's still an entry for this in debugReports to tell Chrome to
// set cooldown for the ad tech origin.
// Insert an entry to corresponding maps for `ad_tech_origin`.
// Component auction winner's reports need to be filtered on client side, so
// their urls will always be set if corresponding forDebuggingOnly API is
// called. Insert an entry to corresponding maps for `ad_tech_origin`.
if (!maybe_component_win.has_value() || !*maybe_component_win) {
output.server_filtered_debugging_only_reports[ad_tech_origin];
} else {
output.component_winner_debugging_only_reports[std::make_pair(
ad_tech_origin, is_win_report)];
if (!output.server_filtered_debugging_only_reports.contains(
ad_tech_origin)) {
output.server_filtered_debugging_only_reports[ad_tech_origin] = {};
}
}
}
}

@ -96,6 +96,19 @@ struct CONTENT_EXPORT BiddingAndAuctionResponse {
base::flat_map<std::string, GURL> beacon_urls;
};
struct CONTENT_EXPORT DebugReportKey {
bool is_seller_report;
bool is_win_report;
bool operator<(const DebugReportKey& other) const {
if (is_seller_report != other.is_seller_report) {
return is_seller_report < other.is_seller_report;
} else {
return is_win_report < other.is_win_report;
}
}
};
// This is not part of the message from the server, but is a convenient place
// to store the outcome if we finish parsing the response before the component
// auctions start the bidding phase.
@ -138,15 +151,17 @@ struct CONTENT_EXPORT BiddingAndAuctionResponse {
server_filtered_pagg_requests_non_reserved;
// forDebuggingOnly reports from component winning buyer/seller. These need to
// be further filtered based on the final auction result. Keyed by a pair of
// origin that the report came from and a bool of whether it's win or loss
// report.
std::map<std::pair<url::Origin, bool>, std::vector<GURL>>
component_winner_debugging_only_reports;
// be further filtered based on the final auction result.
std::map<DebugReportKey, std::optional<GURL>>
component_win_debugging_only_reports;
// forDebuggingOnly reports that have been filtered by the server.
std::map<url::Origin, std::vector<GURL>>
server_filtered_debugging_only_reports;
// Ad tech origins that have forDebuggingOnly reports from server. This is
// used to get these origin's cooldown status.
base::flat_set<url::Origin> debugging_only_report_origins;
};
} // namespace content

@ -134,16 +134,20 @@ base::Value::Dict CreateResponseDictWithPAggResponse(
}
base::Value::Dict CreateResponseDictWithDebugReports(
std::optional<bool> maybe_is_win_report,
std::optional<bool> maybe_component_win) {
std::optional<bool> maybe_component_win,
std::optional<bool> maybe_is_seller_report,
std::optional<bool> maybe_is_win_report) {
base::Value::Dict report;
report.Set("url", kDebugReportingURL);
if (maybe_is_win_report.has_value()) {
report.Set("isWinReport", *maybe_is_win_report);
}
if (maybe_component_win.has_value()) {
report.Set("componentWin", *maybe_component_win);
}
if (maybe_is_seller_report.has_value()) {
report.Set("isSellerReport", *maybe_is_seller_report);
}
if (maybe_is_win_report.has_value()) {
report.Set("isWinReport", *maybe_is_win_report);
}
return CreateValidResponseDict().Set(
"debugReports",
@ -1247,15 +1251,13 @@ TEST(BiddingAndAuctionResponseTest, ForDebuggingOnlyReports) {
ASSERT_TRUE(result);
EXPECT_THAT(*result, EqualsBiddingAndAuctionResponse(std::ref(output)));
EXPECT_EQ(2u, result->component_winner_debugging_only_reports.size());
EXPECT_THAT(result->component_winner_debugging_only_reports[std::make_pair(
url::Origin::Create(GURL(kOwnerOrigin)), true)],
testing::UnorderedElementsAre(
GURL("https://component-win.win-debug-report.com")));
EXPECT_THAT(result->component_winner_debugging_only_reports[std::make_pair(
url::Origin::Create(GURL(kOwnerOrigin)), false)],
testing::UnorderedElementsAre(
GURL("https://component-win.loss-debug-report.com")));
EXPECT_EQ(2u, result->component_win_debugging_only_reports.size());
EXPECT_THAT(result->component_win_debugging_only_reports
[BiddingAndAuctionResponse::DebugReportKey(false, true)],
GURL("https://component-win.win-debug-report.com"));
EXPECT_THAT(result->component_win_debugging_only_reports
[BiddingAndAuctionResponse::DebugReportKey(false, false)],
GURL("https://component-win.loss-debug-report.com"));
EXPECT_EQ(1u, result->server_filtered_debugging_only_reports.size());
EXPECT_THAT(
@ -1315,21 +1317,26 @@ TEST(BiddingAndAuctionResponseTest, ForDebuggingOnlyReportsIgnoreErrors) {
ASSERT_TRUE(result);
EXPECT_THAT(*result, EqualsBiddingAndAuctionResponse(std::ref(output)));
EXPECT_TRUE(result->component_winner_debugging_only_reports.empty());
EXPECT_TRUE(result->component_win_debugging_only_reports.empty());
EXPECT_TRUE(result->server_filtered_debugging_only_reports.empty());
}
}
TEST(BiddingAndAuctionResponseTest, ForDebuggingOnlyReportsComponentWinner) {
BiddingAndAuctionResponse output = CreateExpectedValidResponse();
static const std::optional<bool> kTestCases[] = {
true,
false,
std::nullopt,
static const struct {
std::optional<bool> is_seller_report;
std::optional<bool> is_win_report;
} kTestCases[] = {
{true, true}, {true, false}, {true, std::nullopt},
{false, true}, {false, false}, {false, std::nullopt},
{std::nullopt, true}, {std::nullopt, false}, {std::nullopt, std::nullopt},
};
for (const auto& test_case : kTestCases) {
base::Value::Dict response = CreateResponseDictWithDebugReports(
test_case, /*maybe_component_win=*/true);
/*maybe_component_win=*/true, test_case.is_seller_report,
test_case.is_win_report);
SCOPED_TRACE(response.DebugString());
std::optional<BiddingAndAuctionResponse> result =
BiddingAndAuctionResponse::TryParse(base::Value(response.Clone()),
@ -1337,11 +1344,15 @@ TEST(BiddingAndAuctionResponseTest, ForDebuggingOnlyReportsComponentWinner) {
/*group_pagg_coordinators=*/{});
ASSERT_TRUE(result);
EXPECT_THAT(*result, EqualsBiddingAndAuctionResponse(std::ref(output)));
EXPECT_EQ(1u, result->component_winner_debugging_only_reports.size());
bool is_win_report = test_case.has_value() && *test_case;
EXPECT_THAT(result->component_winner_debugging_only_reports[std::make_pair(
url::Origin::Create(GURL(kOwnerOrigin)), is_win_report)],
testing::UnorderedElementsAre(kDebugReportingURL));
EXPECT_EQ(1u, result->component_win_debugging_only_reports.size());
bool is_seller_report =
test_case.is_seller_report.has_value() && *test_case.is_seller_report;
bool is_win_report =
test_case.is_win_report.has_value() && *test_case.is_win_report;
EXPECT_THAT(result->component_win_debugging_only_reports
[BiddingAndAuctionResponse::DebugReportKey(is_seller_report,
is_win_report)],
kDebugReportingURL);
EXPECT_TRUE(result->server_filtered_debugging_only_reports.empty());
}
}
@ -1355,7 +1366,8 @@ TEST(BiddingAndAuctionResponseTest, ForDebuggingOnlyReportsServerFiltered) {
};
for (const auto& test_case : kTestCases) {
base::Value::Dict response = CreateResponseDictWithDebugReports(
test_case, /*maybe_component_win=*/false);
/*maybe_component_win=*/false, /*maybe_is_seller_report=*/std::nullopt,
/*maybe_is_win_report=*/test_case);
SCOPED_TRACE(response.DebugString());
std::optional<BiddingAndAuctionResponse> result =
BiddingAndAuctionResponse::TryParse(base::Value(response.Clone()),
@ -1363,7 +1375,7 @@ TEST(BiddingAndAuctionResponseTest, ForDebuggingOnlyReportsServerFiltered) {
/*group_pagg_coordinators=*/{});
ASSERT_TRUE(result);
EXPECT_THAT(*result, EqualsBiddingAndAuctionResponse(std::ref(output)));
EXPECT_TRUE(result->component_winner_debugging_only_reports.empty());
EXPECT_TRUE(result->component_win_debugging_only_reports.empty());
EXPECT_EQ(1u, result->server_filtered_debugging_only_reports.size());
EXPECT_THAT(
result->server_filtered_debugging_only_reports[url::Origin::Create(

@ -57,6 +57,7 @@
#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/header_direct_from_seller_signals.h"
@ -641,38 +642,14 @@ bool IsOriginInDebugReportCooldownOrLockout(
return false;
}
// Samples forDebuggingOnly reports with a given sampling rate.
bool SampleDebugReport(
void UpdateDebugReportCooldown(
const url::Origin& origin,
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns) {
bool can_send_debug_report = false;
int sampling_random_max =
blink::features::kFledgeDebugReportSamplingRandomMax.Get();
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns,
base::Time now_nearest_next_hour) {
int restricted_cooldown_random_max =
blink::features::kFledgeDebugReportSamplingRestrictedCooldownRandomMax
.Get();
CHECK_GE(sampling_random_max, 0);
CHECK_GE(restricted_cooldown_random_max, 0);
base::Time now_nearest_next_hour = base::Time::FromDeltaSinceWindowsEpoch(
base::Time::Now().ToDeltaSinceWindowsEpoch().CeilToMultiple(
base::Hours(1)));
// Only allow sending debug reports 1/(sampling_max_rand+1) chance. Treat
// INT_MAX `sampling_random_max` as 0 chance.
int sampling_rand = base::RandInt(0, sampling_random_max);
if (sampling_random_max != INT_MAX && sampling_rand == 0) {
can_send_debug_report = true;
// Only set lockout when lockout length kFledgeDebugReportLockout is not
// zero.
if (blink::features::kFledgeDebugReportLockout.Get() !=
base::Milliseconds(0)) {
new_debug_report_lockout_and_cooldowns.last_report_sent_time =
now_nearest_next_hour;
}
}
base::UmaHistogramBoolean(
"Ads.InterestGroup.Auction.ForDebuggingOnlyReportAllowedAfterSampling",
can_send_debug_report);
// Give a restricted cooldown in 1/(restricted_cooldown_random_max+1)
// chance. Treat INT_MAX `restricted_cooldown_random_max` as 0 chance.
int cooldown_rand = base::RandInt(0, restricted_cooldown_random_max);
@ -693,6 +670,45 @@ bool SampleDebugReport(
new_debug_report_lockout_and_cooldowns.debug_report_cooldown_map[origin] =
DebugReportCooldown(now_nearest_next_hour, cooldown_type);
}
}
// Samples forDebuggingOnly reports with a given sampling rate.
// `is_from_server_response` is true if it's a report from B&A response, which
// was already sampled by B&A server.
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)));
// Only allow sending debug reports 1/(sampling_max_rand+1) chance. Treat
// INT_MAX `sampling_random_max` as 0 chance.
int sampling_rand = base::RandInt(0, sampling_random_max);
// Don't do sampling if the report is from B&A response, which has already
// been sampled on server side.
if (is_from_server_response ||
(sampling_random_max != INT_MAX && sampling_rand == 0)) {
can_send_debug_report = true;
// Only set lockout when lockout length kFledgeDebugReportLockout is not
// zero.
if (blink::features::kFledgeDebugReportLockout.Get() !=
base::Milliseconds(0)) {
new_debug_report_lockout_and_cooldowns.last_report_sent_time =
now_nearest_next_hour;
}
}
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;
}
@ -700,8 +716,11 @@ bool SampleDebugReport(
// Returns whether to keep the debug report or not. Returns true if flag
// kFledgeSampleDebugReports is disabled, or sampling allows sending the report,
// or kFledgeEnableFilteringDebugReportStartingFrom is zero.
// `is_from_server_response` is true if it's a report from B&A response, which
// was already sampled by B&A server.
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) {
@ -718,7 +737,8 @@ bool KeepDebugReport(
origin, new_debug_report_lockout_and_cooldowns, now)) {
// `SampleDebugReport()` may modify the lockout and cooldown state.
can_send_debug_report =
SampleDebugReport(origin, new_debug_report_lockout_and_cooldowns);
SampleDebugReport(origin, is_from_server_response,
new_debug_report_lockout_and_cooldowns);
}
bool filter_enabled =
blink::features::kFledgeEnableFilteringDebugReportStartingFrom.Get() !=
@ -726,6 +746,110 @@ bool KeepDebugReport(
return !filter_enabled || can_send_debug_report;
}
// Helper function of TakeDebugReportUrlsForBidState(). Adds debug reporting
// URLs for `winner` to `debug_win_report_urls`, if there are any.
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)) {
// `top_level_signals` is passed as parameter `signals` for top-level
// seller.
debug_win_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(winner->top_level_seller_debug_win_report_url).value(),
top_level_signals.value()));
}
}
}
// Helper function of TakeDebugReportUrlsForBidState(). Adds debug reporting
// URLs for losing `bid_state` to `debug_loss_report_urls`, if there are any.
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)) {
// Losing and rejected bidders should not get highest_scoring_other_bid
// and made_highest_scoring_other_bid signals. (And also the currency bit
// for those).
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, /*highest_scoring_other_bid=*/0.0,
/*highest_scoring_other_bid_currency=*/std::nullopt,
/*made_highest_scoring_other_bid=*/false),
/*top_level_signals=*/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)) {
// TODO(qingxinwu): Add reject reason to seller debug loss report as well.
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)) {
// `top_level_signals` is passed as parameter `signals` for top-level
// seller.
debug_loss_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(bid_state->top_level_seller_debug_loss_report_url)
.value(),
top_level_signals.value()));
}
}
}
// Adds debug reporting URLs for `bid_state` to `debug_win_report_urls` and
// `debug_loss_report_urls`, if there are any, filling in report URL template
// parameters as needed. The URLs are moved away from `bid_state`.
@ -764,78 +888,51 @@ void TakeDebugReportUrlsForBidState(
DebugReportLockoutAndCooldowns& new_debug_report_lockout_and_cooldowns,
std::vector<GURL>& debug_win_report_urls,
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)));
// TODO(qingxinwu): Give bidder's and seller's debug report the same chance to
// be kept after sampling. Bidder's debug report is sampled before seller's,
// giving bidder's report a higher chance to be kept (especially when the
// sample rate is high), which seems unfair to the seller.
if (bid_state.get() == winner) {
if (winner->bidder_debug_win_report_url.has_value() &&
KeepDebugReport(bidder, 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, 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, debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
// `top_level_signals` is passed as parameter `signals` for top-level
// seller.
debug_win_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(winner->top_level_seller_debug_win_report_url)
.value(),
top_level_signals.value()));
}
}
return;
}
if (bid_state->bidder_debug_loss_report_url.has_value() &&
KeepDebugReport(bidder, debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
// Losing and rejected bidders should not get highest_scoring_other_bid
// and made_highest_scoring_other_bid signals. (And also the currency bit
// for those).
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, /*highest_scoring_other_bid=*/0.0,
/*highest_scoring_other_bid_currency=*/std::nullopt,
/*made_highest_scoring_other_bid=*/false),
/*top_level_signals=*/std::nullopt, bid_state->reject_reason));
}
if (bid_state->seller_debug_loss_report_url.has_value() &&
KeepDebugReport(seller, debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
// TODO(qingxinwu): Add reject reason to seller debug loss report as well.
debug_loss_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(bid_state->seller_debug_loss_report_url).value(), signals,
top_level_signals));
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);
}
if (bid_state->top_level_seller_debug_loss_report_url.has_value()) {
CHECK(top_level_seller.has_value());
if (KeepDebugReport(*top_level_seller, debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
// `top_level_signals` is passed as parameter `signals` for top-level
// seller.
debug_loss_report_urls.emplace_back(
InterestGroupAuction::FillPostAuctionSignals(
std::move(bid_state->top_level_seller_debug_loss_report_url)
.value(),
top_level_signals.value()));
// For server filtered fDO reports from a B&A auction.
for (const auto& [origin, reportUrls] :
bid_state->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) {
// Server filtered debug reports have been sampled on B&A servers already,
// so do not run sampling on client again for these reports.
if (KeepDebugReport(origin, /*is_from_server_response=*/true,
debug_report_lockout_and_cooldowns,
new_debug_report_lockout_and_cooldowns)) {
// For server filtered ones, post auction signals should have been
// filled on the server side. And as a result, for server filtered ones,
// there's no difference if they go to loss or win lists, since the
// difference was post auction signals.
debug_loss_report_urls.emplace_back(report);
}
}
}
}
@ -1630,10 +1727,15 @@ class InterestGroupAuction::BuyerHelper
std::map<PrivateAggregationKey, PrivateAggregationRequests>&
server_filtered_pagg_requests_reserved,
std::map<std::string, PrivateAggregationRequests>&
server_filtered_pagg_requests_non_reserved) {
server_filtered_pagg_requests_non_reserved,
std::map<BiddingAndAuctionResponse::DebugReportKey, std::optional<GURL>>&
component_win_debugging_only_reports,
std::map<url::Origin, std::vector<GURL>>&
server_filtered_debugging_only_reports) {
CHECK_EQ(1u, bid_states_.size());
BidState* bid_state = bid_states_[0].get();
bid_state->made_bid = true;
bid_state->is_from_server_response = true;
auction_worklet::mojom::KAnonymityBidMode kanon_mode =
auction_->kanon_mode();
@ -1686,6 +1788,27 @@ class InterestGroupAuction::BuyerHelper
bid_state->server_filtered_pagg_requests_non_reserved =
std::move(server_filtered_pagg_requests_non_reserved);
// 4. forDebuggingOnly reports.
for (auto& [key, maybeReportUrl] : component_win_debugging_only_reports) {
// From component auction. So cannot be top level seller debug reports.
if (key.is_seller_report) {
if (key.is_win_report) {
bid_state->seller_debug_win_report_url = maybeReportUrl;
} else {
bid_state->seller_debug_loss_report_url = maybeReportUrl;
}
} else {
if (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,
/*ad_cost=*/std::nullopt, std::move(ad_descriptor),
@ -2821,9 +2944,7 @@ void InterestGroupAuction::StartBiddingAndScoringPhase(
if (component_auctions_.empty()) {
if (is_server_auction_) {
if (saved_response_) {
CreateBidFromServerResponse();
num_scoring_dependencies_ = 0;
MaybeCompleteBiddingAndScoringPhase();
MaybeLoadDebugReportLockoutAndCooldowns();
return;
}
} else {
@ -5427,7 +5548,7 @@ void InterestGroupAuction::MaybeCompleteBiddingAndScoringPhase() {
}
}
// This needs to be asynchronous since it can happens inside
// This needs to be asynchronous since it can happen inside
// StartBiddingAndScoringPhase if there is actually nothing to do, and we
// don't want to delete the auction while we're in process of starting it.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
@ -5730,10 +5851,7 @@ void InterestGroupAuction::OnLoadedWinningGroup(
OnLoadedWinningGroupImpl(std::move(response), std::move(maybe_group));
DCHECK(saved_response_);
if (bidding_and_scoring_phase_state_ == PhaseState::kDuring) {
CreateBidFromServerResponse();
MaybeCompleteBiddingAndScoringPhase();
}
MaybeLoadDebugReportLockoutAndCooldowns();
}
void InterestGroupAuction::OnLoadedWinningGroupImpl(
@ -5769,6 +5887,48 @@ void InterestGroupAuction::OnLoadedWinningGroupImpl(
saved_response_ = std::move(response);
}
void InterestGroupAuction::MaybeLoadDebugReportLockoutAndCooldowns() {
if (base::FeatureList::IsEnabled(
blink::features::kBiddingAndScoringDebugReportingAPI) &&
base::FeatureList::IsEnabled(
blink::features::kFledgeSampleDebugReports) &&
!server_auction_debug_report_lockout_loaded_) {
// All ad tech origins that have debug reports.
base::flat_set<url::Origin> origins =
saved_response_->debugging_only_report_origins;
// Use a weak pointer here so that
// &InterestGroupAuction::OnLoadDebugReportLockoutAndCooldownsComplete is
// cancelled when |weak_ptr_factory_| is destroyed.
interest_group_manager_->GetDebugReportLockoutAndCooldowns(
std::move(origins),
base::BindOnce(
&InterestGroupAuction::OnLoadDebugReportLockoutAndCooldownsComplete,
weak_ptr_factory_.GetWeakPtr()));
} else {
OnLoadDebugReportLockoutAndCooldownsComplete(
/*debug_report_lockout_and_cooldowns=*/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_);
@ -5800,7 +5960,9 @@ void InterestGroupAuction::CreateBidFromServerResponse() {
/*ad_component_descriptors=*/std::move(ad_components),
saved_response_->component_win_pagg_requests,
saved_response_->server_filtered_pagg_requests_reserved,
saved_response_->server_filtered_pagg_requests_non_reserved);
saved_response_->server_filtered_pagg_requests_non_reserved,
saved_response_->component_win_debugging_only_reports,
saved_response_->server_filtered_debugging_only_reports);
if (!bid) {
saved_response_.emplace();

@ -298,6 +298,14 @@ class CONTENT_EXPORT InterestGroupAuction
std::optional<GURL> top_level_seller_debug_win_report_url;
std::optional<GURL> top_level_seller_debug_loss_report_url;
// True if the bid is created from parsing B&A server response.
bool is_from_server_response = false;
// forDebuggingOnly reports that have been filtered (also sampled) by the
// B&A server.
std::map<url::Origin, std::vector<GURL>>
server_filtered_debugging_only_reports;
// Requests made to Private aggregation API in generateBid() and scoreAd().
// Keyed by reporting origin of the associated requests, i.e., buyer origin
// for generateBid() and seller origin for scoreAd(), an enum that
@ -310,9 +318,11 @@ class CONTENT_EXPORT InterestGroupAuction
// non-k-anonymous enforced bid when k-anonymity enforcement is active.
PrivateAggregationRequests non_kanon_private_aggregation_requests;
// Private aggregation requests from B&A response that have been filtered by
// B&A server. These can be simply be forwarded without further filtering on
// Chrome side.
std::map<PrivateAggregationKey, PrivateAggregationRequests>
server_filtered_pagg_requests_reserved;
std::map<std::string, PrivateAggregationRequests>
server_filtered_pagg_requests_non_reserved;
@ -1220,6 +1230,12 @@ class CONTENT_EXPORT InterestGroupAuction
BiddingAndAuctionResponse response,
std::optional<SingleStorageInterestGroup> maybe_group);
void MaybeLoadDebugReportLockoutAndCooldowns();
void OnLoadDebugReportLockoutAndCooldownsComplete(
std::optional<DebugReportLockoutAndCooldowns>
debug_report_lockout_and_cooldowns);
void CreateBidFromServerResponse();
// Completion callback for AdAuctionPageData::ParseAndFindAdAuctionSignals().
@ -1334,6 +1350,9 @@ class CONTENT_EXPORT InterestGroupAuction
enum class PhaseState { kBefore, kDuring, kAfter };
PhaseState bidding_and_scoring_phase_state_ = PhaseState::kBefore;
// True if creating bid from server response has started.
bool started_creating_bid_from_response_ = false;
// Number of things that are pending that are needed to score everything.
// This includes bidders that are still attempting to generate bids ---
// both BuyerHelpers and component auctions. BuyerHelpers may generate
@ -1358,10 +1377,15 @@ class CONTENT_EXPORT InterestGroupAuction
bool any_bid_made_ = false;
// Lockout and cooldowns for sending forDebuggingOnly reports. It's read from
// DB when the auction started.
// DB when the auction started for local auctions, or after B&A server
// response is parsed for server auctions.
std::optional<DebugReportLockoutAndCooldowns>
debug_report_lockout_and_cooldowns_;
// True if lockout and cooldowns are loaded for the server auction, to avoid
// reading it more than once.
bool server_auction_debug_report_lockout_loaded_ = false;
// New lockout and cooldowns for sending forDebuggingOnly reports. It's
// generated from this auction and updated during collecting debug reports.
// Used to decide whether forDebuggingOnly API is in lockout or cooldown