Skip to content

Commit

Permalink
[attribution_reporting] Support aggregatable reports in simulator
Browse files Browse the repository at this point in the history
Change-Id: I59c70933f7527c916248064b6d48f058db2fa369
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3560724
Reviewed-by: Alex Turner <alexmt@chromium.org>
Reviewed-by: Andrew Paseltiner <apaseltiner@chromium.org>
Commit-Queue: Nan Lin <linnan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#988240}
  • Loading branch information
linnan-github authored and Chromium LUCI CQ committed Apr 2, 2022
1 parent 12697e7 commit 8de22bc
Show file tree
Hide file tree
Showing 17 changed files with 308 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "content/common/content_export.h"

namespace content {

// Enables the Aggregation Service. See crbug.com/1207974.
extern const base::Feature kPrivacySandboxAggregationService;
extern const base::FeatureParam<std::string>
extern CONTENT_EXPORT const base::Feature kPrivacySandboxAggregationService;
extern CONTENT_EXPORT const base::FeatureParam<std::string>
kPrivacySandboxAggregationServiceTrustedServerUrlParam;

} // namespace content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "base/values.h"
#include "content/browser/aggregation_service/aggregatable_report_assembler.h"
#include "content/browser/aggregation_service/aggregation_service_storage_sql.h"
#include "content/browser/aggregation_service/public_key.h"
#include "content/browser/storage_partition_impl.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
Expand Down Expand Up @@ -114,4 +115,11 @@ void AggregationServiceImpl::ClearData(base::Time delete_begin,
.Then(std::move(done));
}

void AggregationServiceImpl::SetPublicKeysForTesting(
const GURL& url,
const PublicKeyset& keyset) {
key_storage_.AsyncCall(&AggregationServiceKeyStorage::SetPublicKeys)
.WithArgs(url, keyset);
}

} // namespace content
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@
#include "content/common/content_export.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

class GURL;

namespace base {
class Clock;
class FilePath;
} // namespace base

namespace content {

struct PublicKeyset;
class StoragePartitionImpl;

// UI thread class that manages the lifetime of the underlying storage. Owned by
Expand Down Expand Up @@ -69,6 +72,9 @@ class CONTENT_EXPORT AggregationServiceImpl
const base::SequenceBound<AggregationServiceKeyStorage>& GetKeyStorage()
override;

// Sets the public keys for `url` in storage to allow testing without network.
void SetPublicKeysForTesting(const GURL& url, const PublicKeyset& keyset);

private:
AggregationServiceImpl(bool run_in_memory,
const base::FilePath& user_data_directory,
Expand Down
7 changes: 7 additions & 0 deletions content/public/test/attribution_simulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ struct AttributionSimulationOptions {

AttributionReportTimeFormat report_time_format =
AttributionReportTimeFormat::kSecondsSinceUnixEpoch;

// If true, removes the `shared_info`, `aggregation_service_payloads` and
// `source_registration_time` fields from aggregatable reports before output.
//
// These fields normally encode a random GUID or the absolute time and
// therefore are sources of nondeterminism in the output.
bool remove_assembled_report = false;
};

// Simulates the Attribution Reporting API for a single user on sources and
Expand Down
193 changes: 143 additions & 50 deletions content/test/attribution_simulator_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
Expand All @@ -20,6 +21,9 @@
#include "base/time/time.h"
#include "base/time/time_to_iso8601.h"
#include "base/values.h"
#include "content/browser/aggregation_service/aggregation_service_features.h"
#include "content/browser/aggregation_service/aggregation_service_impl.h"
#include "content/browser/aggregation_service/aggregation_service_test_utils.h"
#include "content/browser/attribution_reporting/attribution_cookie_checker.h"
#include "content/browser/attribution_reporting/attribution_default_random_generator.h"
#include "content/browser/attribution_reporting/attribution_insecure_random_generator.h"
Expand Down Expand Up @@ -84,15 +88,21 @@ class AlwaysSetCookieChecker : public AttributionCookieChecker {

class SentReportAccumulator : public AttributionReportSender {
public:
SentReportAccumulator(base::Value::ListStorage& reports,
base::Value::ListStorage& debug_reports,
SentReportAccumulator(base::Value::ListStorage& event_level_reports,
base::Value::ListStorage& debug_event_level_reports,
base::Value::ListStorage& aggregatable_reports,
base::Value::ListStorage& debug_aggregatable_reports,
bool remove_report_ids,
AttributionReportTimeFormat report_time_format)
AttributionReportTimeFormat report_time_format,
bool remove_assembled_report)
: time_origin_(base::Time::Now()),
remove_report_ids_(remove_report_ids),
report_time_format_(report_time_format),
reports_(reports),
debug_reports_(debug_reports) {}
remove_assembled_report_(remove_assembled_report),
event_level_reports_(event_level_reports),
debug_event_level_reports_(debug_event_level_reports),
aggregatable_reports_(aggregatable_reports),
debug_aggregatable_reports_(debug_aggregatable_reports) {}

~SentReportAccumulator() override = default;

Expand All @@ -107,16 +117,18 @@ class SentReportAccumulator : public AttributionReportSender {
void SendReport(AttributionReport report,
bool is_debug_report,
ReportSentCallback sent_callback) override {
// TODO(linnan): Support aggregatable reports in the simulator.
if (!absl::holds_alternative<AttributionReport::EventLevelData>(
report.data())) {
return;
}

base::Value report_body = report.ReportBody();
if (remove_report_ids_)
report_body.RemoveKey("report_id");

if (remove_assembled_report_ &&
absl::holds_alternative<AttributionReport::AggregatableAttributionData>(
report.data())) {
report_body.RemoveKey("shared_info");
report_body.RemoveKey("aggregation_service_payloads");
report_body.RemoveKey("source_registration_time");
}

base::DictionaryValue value;
value.SetKey("report", std::move(report_body));
value.SetStringKey("report_url", report.ReportURL(is_debug_report).spec());
Expand All @@ -134,17 +146,45 @@ class SentReportAccumulator : public AttributionReportSender {
break;
}

base::Value::ListStorage* reports;

base::DictionaryValue test_info;
test_info.SetBoolKey("randomized_trigger",
report.attribution_info().source.attribution_logic() ==
StoredSource::AttributionLogic::kFalsely);
value.SetKey("test_info", std::move(test_info));
if (absl::holds_alternative<AttributionReport::EventLevelData>(
report.data())) {
test_info.SetBoolKey(
"randomized_trigger",
report.attribution_info().source.attribution_logic() ==
StoredSource::AttributionLogic::kFalsely);

if (is_debug_report) {
debug_reports_.push_back(std::move(value));
reports =
is_debug_report ? &debug_event_level_reports_ : &event_level_reports_;
} else {
reports_.push_back(std::move(value));
auto* aggregatable_data =
absl::get_if<AttributionReport::AggregatableAttributionData>(
&report.data());
DCHECK(aggregatable_data);
auto list = std::make_unique<base::ListValue>();
for (const auto& contribution : aggregatable_data->contributions) {
auto dict = std::make_unique<base::DictionaryValue>();
// TODO(linnan): Replacing with 128-bit value string.
dict->SetString(
"key_high_bits",
base::NumberToString(absl::Uint128High64(contribution.key())));
dict->SetString(
"key_low_bits",
base::NumberToString(absl::Uint128Low64(contribution.key())));
dict->SetString("value", base::NumberToString(contribution.value()));

list->Append(std::move(dict));
}
test_info.SetList("histograms", std::move(list));

reports = is_debug_report ? &debug_aggregatable_reports_
: &aggregatable_reports_;
}
value.SetKey("test_info", std::move(test_info));

reports->push_back(std::move(value));

std::move(sent_callback)
.Run(std::move(report), SendResult(SendResult::Status::kSent,
Expand All @@ -154,8 +194,11 @@ class SentReportAccumulator : public AttributionReportSender {
const base::Time time_origin_;
const bool remove_report_ids_;
const AttributionReportTimeFormat report_time_format_;
base::Value::ListStorage& reports_;
base::Value::ListStorage& debug_reports_;
const bool remove_assembled_report_;
base::Value::ListStorage& event_level_reports_;
base::Value::ListStorage& debug_event_level_reports_;
base::Value::ListStorage& aggregatable_reports_;
base::Value::ListStorage& debug_aggregatable_reports_;
};

// Registers sources and triggers in the `AttributionManagerImpl` and records
Expand Down Expand Up @@ -227,13 +270,13 @@ class AttributionEventHandler : public AttributionObserver {

// TODO(linnan): Support aggregatable reports in the simulator.

std::stringstream reason;
std::stringstream event_level_reason;
switch (result.event_level_status()) {
case AttributionTrigger::EventLevelResult::kSuccess:
case AttributionTrigger::EventLevelResult::kSuccessDroppedLowerPriority:
// TODO(apaseltiner): Consider surfacing reports dropped due to
// prioritization.
return;
break;
case AttributionTrigger::EventLevelResult::kInternalError:
case AttributionTrigger::EventLevelResult::
kNoCapacityForConversionDestination:
Expand All @@ -244,12 +287,45 @@ class AttributionEventHandler : public AttributionObserver {
case AttributionTrigger::EventLevelResult::kDroppedForNoise:
case AttributionTrigger::EventLevelResult::kExcessiveReportingOrigins:
case AttributionTrigger::EventLevelResult::kNoMatchingSourceFilterData:
reason << result.event_level_status();
event_level_reason << result.event_level_status();
break;
}

std::stringstream aggregatable_reason;
switch (result.aggregatable_status()) {
case AttributionTrigger::AggregatableResult::kSuccess:
case AttributionTrigger::AggregatableResult::kNotRegistered:
break;
case AttributionTrigger::AggregatableResult::kInternalError:
case AttributionTrigger::AggregatableResult::
kNoCapacityForConversionDestination:
case AttributionTrigger::AggregatableResult::kNoMatchingImpressions:
case AttributionTrigger::AggregatableResult::kExcessiveAttributions:
case AttributionTrigger::AggregatableResult::kExcessiveReportingOrigins:
case AttributionTrigger::AggregatableResult::kInsufficientBudget:
case AttributionTrigger::AggregatableResult::kNoMatchingSourceFilterData:
case AttributionTrigger::AggregatableResult::kNoHistograms:
aggregatable_reason << result.aggregatable_status();
break;
}

std::string event_level_reason_str = event_level_reason.str();
std::string aggregatable_reason_str = aggregatable_reason.str();

if (event_level_reason_str.empty() && aggregatable_reason_str.empty())
return;

base::DictionaryValue dict;
dict.SetStringKey("reason", reason.str());
if (!event_level_reason_str.empty()) {
dict.SetStringKey("event_level_reason",
std::move(event_level_reason_str));
}

if (!aggregatable_reason_str.empty()) {
dict.SetStringKey("aggregatable_reason",
std::move(aggregatable_reason_str));
}

dict.SetKey("trigger", std::move(input_value));

rejected_triggers_.push_back(std::move(dict));
Expand Down Expand Up @@ -299,8 +375,13 @@ base::Value RunAttributionSimulation(
rng = std::make_unique<AttributionDefaultRandomGenerator>();
}

base::Value::ListStorage reports;
base::Value::ListStorage debug_reports;
base::Value::ListStorage event_level_reports;
base::Value::ListStorage debug_event_level_reports;
base::Value::ListStorage aggregatable_reports;
base::Value::ListStorage debug_aggregatable_reports;

auto* storage_partition = static_cast<StoragePartitionImpl*>(
browser_context.GetDefaultStoragePartition());

auto manager = AttributionManagerImpl::CreateForTesting(
user_data_directory,
Expand All @@ -309,46 +390,58 @@ base::Value RunAttributionSimulation(
options.noise_mode, options.delay_mode, std::move(rng),
options.randomized_response_rates),
std::make_unique<AlwaysSetCookieChecker>(),
std::make_unique<SentReportAccumulator>(reports, debug_reports,
options.remove_report_ids,
options.report_time_format),
static_cast<StoragePartitionImpl*>(
browser_context.GetDefaultStoragePartition()));
std::make_unique<SentReportAccumulator>(
event_level_reports, debug_event_level_reports, aggregatable_reports,
debug_aggregatable_reports, options.remove_report_ids,
options.report_time_format, options.remove_assembled_report),
storage_partition);

base::Value::ListStorage rejected_sources;
base::Value::ListStorage rejected_triggers;
AttributionEventHandler handler(manager.get(), rejected_sources,
rejected_triggers);

storage_partition->GetAggregationService()->SetPublicKeysForTesting(
GURL(kPrivacySandboxAggregationServiceTrustedServerUrlParam.Get()),
PublicKeyset({aggregation_service::GenerateKey().public_key},
/*fetch_time=*/base::Time::Now(),
/*expiry_time=*/base::Time::Max()));

for (auto& event : *events) {
task_environment.FastForwardBy(GetEventTime(event) - base::Time::Now());
handler.Handle(std::move(event));
}

absl::optional<base::Time> last_report_time;
std::vector<AttributionReport> pending_reports =
GetAttributionReportsForTesting(manager.get(),
/*max_report_time=*/base::Time::Max());

base::RunLoop loop;
manager->GetPendingReportsForInternalUse(
AttributionReport::ReportType::kEventLevel,
base::BindLambdaForTesting([&](std::vector<AttributionReport> reports) {
if (!reports.empty()) {
last_report_time = base::ranges::max(reports, /*comp=*/{},
&AttributionReport::report_time)
.report_time();
}
if (!pending_reports.empty()) {
base::Time last_report_time =
base::ranges::max(pending_reports, /*comp=*/{},
&AttributionReport::report_time)
.report_time();
task_environment.FastForwardBy(last_report_time - base::Time::Now());
}

loop.Quit();
}));
base::Value output(base::Value::Type::DICTIONARY);
output.SetKey("event_level_reports",
base::Value(std::move(event_level_reports)));

loop.Run();
if (last_report_time.has_value())
task_environment.FastForwardBy(*last_report_time - base::Time::Now());
if (!debug_event_level_reports.empty()) {
output.SetKey("debug_event_level_reports",
base::Value(std::move(debug_event_level_reports)));
}

base::Value output(base::Value::Type::DICTIONARY);
output.SetKey("reports", base::Value(std::move(reports)));
if (!aggregatable_reports.empty()) {
output.SetKey("aggregatable_reports",
base::Value(std::move(aggregatable_reports)));
}

if (!debug_reports.empty())
output.SetKey("debug_reports", base::Value(std::move(debug_reports)));
if (!debug_aggregatable_reports.empty()) {
output.SetKey("debug_aggregatable_reports",
base::Value(std::move(debug_aggregatable_reports)));
}

if (!rejected_sources.empty())
output.SetKey("rejected_sources", base::Value(std::move(rejected_sources)));
Expand Down
1 change: 1 addition & 0 deletions content/test/attribution_simulator_impl_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ TEST_P(AttributionSimulatorImplTest, HasExpectedOutput) {
.delay_mode = AttributionDelayMode::kDefault,
.remove_report_ids = true,
.report_time_format = AttributionReportTimeFormat::kSecondsSinceUnixEpoch,
.remove_assembled_report = true,
};

const base::FilePath options_path = OptionsPath(input_path);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"reports": [
"event_level_reports": [
{
"report": {
"attribution_destination": "https://d.test",
Expand Down

0 comments on commit 8de22bc

Please sign in to comment.