blob: 8cc28619dd6164ff2b63c9b08348b4a2c1e82974 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/aggregation_service/aggregatable_report.h"
#include <stddef.h>
#include <stdint.h>
#include <array>
#include <limits>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/containers/span.h"
#include "base/json/json_writer.h"
#include "base/numerics/byte_conversions.h"
#include "base/strings/abseil_string_number_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "base/values.h"
#include "components/aggregation_service/aggregation_coordinator_utils.h"
#include "components/cbor/reader.h"
#include "components/cbor/values.h"
#include "content/browser/aggregation_service/aggregatable_report.h"
#include "content/browser/aggregation_service/aggregation_service_features.h"
#include "content/browser/aggregation_service/aggregation_service_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/numeric/int128.h"
#include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom.h"
#include "third_party/distributed_point_functions/shim/buildflags.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
testing::AssertionResult CborMapContainsKeyAndType(
const cbor::Value::MapValue& map,
const std::string& key,
cbor::Value::Type value_type) {
const auto it = map.find(cbor::Value(key));
if (it == map.end()) {
return testing::AssertionFailure()
<< "Expected key cbor::Value(\"" << key << "\") to be in map";
}
if (it->second.type() != value_type) {
return testing::AssertionFailure()
<< "Expected value to have type " << static_cast<int>(value_type)
<< ", actual: " << static_cast<int>(it->second.type());
}
return testing::AssertionSuccess();
}
std::vector<blink::mojom::AggregatableReportHistogramContribution>
PadContributions(
std::vector<blink::mojom::AggregatableReportHistogramContribution>
contributions,
int max_contributions_allowed) {
EXPECT_LE(static_cast<int>(contributions.size()), max_contributions_allowed);
for (int i = contributions.size(); i < max_contributions_allowed; ++i) {
contributions.emplace_back(/*bucket=*/0, /*value=*/0,
/*filtering_id=*/std::nullopt);
}
return contributions;
}
// Tests that the report has the expected format, matches the provided details,
// and is decryptable by the provided keys. Note that
// `expected_payload_contents` is not expected to have its contributions already
// padded.
void VerifyReport(
const std::optional<AggregatableReport>& report,
const AggregationServicePayloadContents& expected_payload_contents,
const AggregatableReportSharedInfo& expected_shared_info,
size_t expected_num_processing_urls,
const std::optional<uint64_t>& expected_debug_key,
const base::flat_map<std::string, std::string>& expected_additional_fields,
const std::vector<aggregation_service::TestHpkeKey>& encryption_keys,
bool should_pad_contributions) {
ASSERT_TRUE(report.has_value());
std::string expected_serialized_shared_info =
expected_shared_info.SerializeAsJson();
EXPECT_EQ(report->shared_info(), expected_serialized_shared_info);
EXPECT_EQ(report->debug_key(), expected_debug_key);
EXPECT_EQ(report->additional_fields(), expected_additional_fields);
const std::vector<AggregatableReport::AggregationServicePayload>& payloads =
report->payloads();
ASSERT_EQ(payloads.size(), expected_num_processing_urls);
ASSERT_EQ(encryption_keys.size(), expected_num_processing_urls);
std::vector<blink::mojom::AggregatableReportHistogramContribution>
expected_contributions;
if (should_pad_contributions) {
expected_contributions =
PadContributions(expected_payload_contents.contributions,
expected_payload_contents.max_contributions_allowed);
} else {
expected_contributions = expected_payload_contents.contributions;
}
for (size_t i = 0; i < expected_num_processing_urls; ++i) {
EXPECT_EQ(payloads[i].key_id, encryption_keys[i].key_id());
std::vector<uint8_t> decrypted_payload =
aggregation_service::DecryptPayloadWithHpke(
payloads[i].payload, encryption_keys[i].full_hpke_key(),
expected_serialized_shared_info);
ASSERT_FALSE(decrypted_payload.empty());
if (expected_shared_info.debug_mode ==
AggregatableReportSharedInfo::DebugMode::kEnabled) {
ASSERT_TRUE(payloads[i].debug_cleartext_payload.has_value());
EXPECT_EQ(payloads[i].debug_cleartext_payload.value(), decrypted_payload);
} else {
EXPECT_FALSE(payloads[i].debug_cleartext_payload.has_value());
}
std::optional<cbor::Value> deserialized_payload =
cbor::Reader::Read(decrypted_payload);
ASSERT_TRUE(deserialized_payload.has_value());
ASSERT_TRUE(deserialized_payload->is_map());
const cbor::Value::MapValue& payload_map = deserialized_payload->GetMap();
EXPECT_EQ(payload_map.size(), 2UL);
ASSERT_TRUE(CborMapContainsKeyAndType(payload_map, "operation",
cbor::Value::Type::STRING));
EXPECT_EQ(payload_map.at(cbor::Value("operation")).GetString(),
"histogram");
switch (expected_payload_contents.aggregation_mode) {
case blink::mojom::AggregationServiceMode::kTeeBased: {
ASSERT_TRUE(CborMapContainsKeyAndType(payload_map, "data",
cbor::Value::Type::ARRAY));
const cbor::Value::ArrayValue& data_array =
payload_map.at(cbor::Value("data")).GetArray();
ASSERT_EQ(data_array.size(), expected_contributions.size());
for (size_t j = 0; j < data_array.size(); ++j) {
ASSERT_TRUE(data_array[j].is_map());
const cbor::Value::MapValue& data_map = data_array[j].GetMap();
ASSERT_TRUE(CborMapContainsKeyAndType(
data_map, "bucket", cbor::Value::Type::BYTE_STRING));
const cbor::Value::BinaryValue& bucket_byte_string =
data_map.at(cbor::Value("bucket")).GetBytestring();
EXPECT_EQ(bucket_byte_string.size(), 16u); // 16 bytes = 128 bits
// TODO(crbug.com/40215445): Replace with
// `base::numerics::U128FromBigEndian()` when available.
absl::uint128 bucket;
base::HexStringToUInt128(base::HexEncode(bucket_byte_string),
&bucket);
EXPECT_EQ(bucket, expected_contributions[j].bucket);
ASSERT_TRUE(CborMapContainsKeyAndType(
data_map, "value", cbor::Value::Type::BYTE_STRING));
const cbor::Value::BinaryValue& value_byte_string =
data_map.at(cbor::Value("value")).GetBytestring();
EXPECT_EQ(value_byte_string.size(), 4u); // 4 bytes = 32 bits
uint32_t value = base::numerics::U32FromBigEndian(
base::as_byte_span(value_byte_string).first<4u>());
EXPECT_EQ(int64_t{value}, expected_contributions[j].value);
ASSERT_EQ(
CborMapContainsKeyAndType(data_map, "id",
cbor::Value::Type::BYTE_STRING),
expected_payload_contents.filtering_id_max_bytes.has_value());
if (expected_payload_contents.filtering_id_max_bytes.has_value()) {
size_t filtering_id_max_bytes =
expected_payload_contents.filtering_id_max_bytes.value();
const cbor::Value::BinaryValue& filtering_id_byte_string =
data_map.at(cbor::Value("id")).GetBytestring();
ASSERT_EQ(filtering_id_byte_string.size(), filtering_id_max_bytes);
std::array<uint8_t, 8u> padded_filtering_id_bytestring;
padded_filtering_id_bytestring.fill(0);
base::as_writable_byte_span(padded_filtering_id_bytestring)
.last(filtering_id_max_bytes)
.copy_from(filtering_id_byte_string);
CHECK_LE(expected_payload_contents.filtering_id_max_bytes.value(),
8u);
uint64_t filtering_id = base::numerics::U64FromBigEndian(
base::make_span(padded_filtering_id_bytestring));
EXPECT_EQ(filtering_id,
expected_contributions[j].filtering_id.value_or(0));
}
}
EXPECT_FALSE(payload_map.contains(cbor::Value("dpf_key")));
break;
}
case blink::mojom::AggregationServiceMode::kExperimentalPoplar: {
EXPECT_TRUE(CborMapContainsKeyAndType(payload_map, "dpf_key",
cbor::Value::Type::BYTE_STRING));
// TODO(crbug.com/40193482): Test the payload details (e.g. dpf key) in
// more depth against a minimal helper server implementation.
EXPECT_FALSE(payload_map.contains(cbor::Value("data")));
break;
}
}
}
}
class AggregatableReportTest : public ::testing::TestWithParam<bool> {
public:
void SetUp() override {
if (GetParam()) {
scoped_feature_list_.InitAndEnableFeature(
kPrivacySandboxAggregationServiceReportPadding);
} else {
scoped_feature_list_.InitAndDisableFeature(
kPrivacySandboxAggregationServiceReportPadding);
}
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
::aggregation_service::ScopedAggregationCoordinatorAllowlistForTesting
scoped_coordinator_allowlist_{
{url::Origin::Create(GURL("https://a.test")),
url::Origin::Create(GURL("https://b.test"))}};
};
TEST_P(AggregatableReportTest,
ValidExperimentalPoplarRequest_ValidReportReturned) {
AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
blink::mojom::AggregationServiceMode::kExperimentalPoplar);
AggregationServicePayloadContents expected_payload_contents =
request.payload_contents();
AggregatableReportSharedInfo expected_shared_info =
request.shared_info().Clone();
[[maybe_unused]] size_t expected_num_processing_urls =
request.processing_urls().size();
std::vector<aggregation_service::TestHpkeKey> hpke_keys;
hpke_keys.emplace_back("id123");
hpke_keys.emplace_back("456abc");
std::optional<AggregatableReport> report =
AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
std::move(request),
{hpke_keys[0].GetPublicKey(), hpke_keys[1].GetPublicKey()});
#if BUILDFLAG(USE_DISTRIBUTED_POINT_FUNCTIONS)
ASSERT_NO_FATAL_FAILURE(
VerifyReport(report, expected_payload_contents, expected_shared_info,
expected_num_processing_urls,
/*expected_debug_key=*/std::nullopt,
/*expected_additional_fields=*/{}, std::move(hpke_keys),
/*should_pad_contributions=*/GetParam()));
#else
EXPECT_FALSE(report.has_value());
#endif
}
TEST_P(AggregatableReportTest, ValidTeeBasedRequest_ValidReportReturned) {
AggregatableReportRequest request = aggregation_service::CreateExampleRequest(
blink::mojom::AggregationServiceMode::kTeeBased);
AggregationServicePayloadContents expected_payload_contents =
request.payload_contents();
AggregatableReportSharedInfo expected_shared_info =
request.shared_info().Clone();
size_t expected_num_processing_urls = request.processing_urls().size();
std::vector<aggregation_service::TestHpkeKey> hpke_keys;
hpke_keys.emplace_back("id123");
std::optional<AggregatableReport> report =
AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
std::move(request), {hpke_keys[0].GetPublicKey()});
ASSERT_NO_FATAL_FAILURE(
VerifyReport(report, expected_payload_contents, expected_shared_info,
expected_num_processing_urls,
/*expected_debug_key=*/std::nullopt,
/*expected_additional_fields=*/{}, std::move(hpke_keys),
/*should_pad_contributions=*/GetParam()));
}
TEST_P(AggregatableReportTest,
ValidMultipleContributionsRequest_ValidReportReturned) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest(
blink::mojom::AggregationServiceMode::kTeeBased);
AggregationServicePayloadContents expected_payload_contents =
example_request.payload_contents();
expected_payload_contents.contributions = {
blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/123,
/*value=*/456, /*filtering_id=*/std::nullopt),
blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/7890,
/*value=*/1234, /*filtering_id=*/std::nullopt)};
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(expected_payload_contents,
example_request.shared_info().Clone());
ASSERT_TRUE(request.has_value());
AggregatableReportSharedInfo expected_shared_info =
request->shared_info().Clone();
size_t expected_num_processing_urls = request->processing_urls().size();
std::vector<aggregation_service::TestHpkeKey> hpke_keys;
hpke_keys.emplace_back("id123");
std::optional<AggregatableReport> report =
AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
std::move(*request), {hpke_keys[0].GetPublicKey()});
ASSERT_NO_FATAL_FAILURE(
VerifyReport(report, expected_payload_contents, expected_shared_info,
expected_num_processing_urls,
/*expected_debug_key=*/std::nullopt,
/*expected_additional_fields=*/{}, std::move(hpke_keys),
/*should_pad_contributions=*/GetParam()));
}
TEST_P(AggregatableReportTest,
ValidNoContributionsRequest_ValidReportReturned) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest(
blink::mojom::AggregationServiceMode::kTeeBased);
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.contributions.clear();
AggregationServicePayloadContents expected_payload_contents =
payload_contents;
if (!GetParam()) {
// A null contribution should be added automatically.
expected_payload_contents.contributions = {
blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/0,
/*value=*/0, /*filtering_id=*/std::nullopt)};
}
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone());
ASSERT_TRUE(request.has_value());
AggregatableReportSharedInfo expected_shared_info =
request->shared_info().Clone();
size_t expected_num_processing_urls = request->processing_urls().size();
std::vector<aggregation_service::TestHpkeKey> hpke_keys;
hpke_keys.emplace_back("id123");
std::optional<AggregatableReport> report =
AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
std::move(*request), {hpke_keys[0].GetPublicKey()});
ASSERT_NO_FATAL_FAILURE(
VerifyReport(report, expected_payload_contents, expected_shared_info,
expected_num_processing_urls,
/*expected_debug_key=*/std::nullopt,
/*expected_additional_fields=*/{}, std::move(hpke_keys),
/*should_pad_contributions=*/GetParam()));
}
TEST_P(AggregatableReportTest,
ValidDebugModeEnabledRequest_ValidReportReturned) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.debug_mode =
AggregatableReportSharedInfo::DebugMode::kEnabled;
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(example_request.payload_contents(),
expected_shared_info.Clone());
ASSERT_TRUE(request.has_value());
AggregationServicePayloadContents expected_payload_contents =
request->payload_contents();
size_t expected_num_processing_urls = request->processing_urls().size();
std::vector<aggregation_service::TestHpkeKey> hpke_keys;
hpke_keys.emplace_back("id123");
std::optional<AggregatableReport> report =
AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
std::move(request.value()), {hpke_keys[0].GetPublicKey()});
ASSERT_NO_FATAL_FAILURE(
VerifyReport(report, expected_payload_contents, expected_shared_info,
expected_num_processing_urls,
/*expected_debug_key=*/std::nullopt,
/*expected_additional_fields=*/{}, std::move(hpke_keys),
/*should_pad_contributions=*/GetParam()));
}
TEST_P(AggregatableReportTest,
ValidDebugKeyPresentRequest_ValidReportReturned) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo expected_shared_info =
example_request.shared_info().Clone();
expected_shared_info.debug_mode =
AggregatableReportSharedInfo::DebugMode::kEnabled;
// Use a large value to check that higher order bits are serialized too.
uint64_t expected_debug_key = std::numeric_limits<uint64_t>::max() - 1;
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(
example_request.payload_contents(), expected_shared_info.Clone(),
/*reporting_path=*/std::string(), expected_debug_key);
ASSERT_TRUE(request.has_value());
AggregationServicePayloadContents expected_payload_contents =
request->payload_contents();
size_t expected_num_processing_urls = request->processing_urls().size();
std::vector<aggregation_service::TestHpkeKey> hpke_keys;
hpke_keys.emplace_back("id123");
std::optional<AggregatableReport> report =
AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
std::move(request.value()), {hpke_keys[0].GetPublicKey()});
ASSERT_NO_FATAL_FAILURE(
VerifyReport(report, expected_payload_contents, expected_shared_info,
expected_num_processing_urls, expected_debug_key,
/*expected_additional_fields=*/{}, std::move(hpke_keys),
/*should_pad_contributions=*/GetParam()));
}
TEST_P(AggregatableReportTest, AdditionalFieldsPresent_ValidReportReturned) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
base::flat_map<std::string, std::string> expected_additional_fields = {
{"additional_key", "example_value"}, {"second", "field"}, {"", ""}};
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(example_request.payload_contents(),
example_request.shared_info().Clone(),
/*reporting_path=*/std::string(),
/*debug_key=*/std::nullopt,
expected_additional_fields);
ASSERT_TRUE(request.has_value());
AggregationServicePayloadContents expected_payload_contents =
request->payload_contents();
size_t expected_num_processing_urls = request->processing_urls().size();
std::vector<aggregation_service::TestHpkeKey> hpke_keys;
hpke_keys.emplace_back("id123");
std::optional<AggregatableReport> report =
AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
std::move(request.value()), {hpke_keys[0].GetPublicKey()});
ASSERT_NO_FATAL_FAILURE(VerifyReport(
report, expected_payload_contents, example_request.shared_info(),
expected_num_processing_urls, /*expected_debug_key=*/std::nullopt,
expected_additional_fields, std::move(hpke_keys),
/*should_pad_contributions=*/GetParam()));
}
class AggregatableReportFilteringIdTest : public AggregatableReportTest {
public:
void SetUp() override {
AggregatableReportTest::SetUp();
scoped_feature_list_.InitAndEnableFeature(
kPrivacySandboxAggregationServiceFilteringIds);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_P(AggregatableReportFilteringIdTest,
FilteringIdMaxBytesSpecified_ValidReportReturned) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.filtering_id_max_bytes = 1;
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone());
ASSERT_TRUE(request.has_value());
size_t expected_num_processing_urls = request->processing_urls().size();
std::vector<aggregation_service::TestHpkeKey> hpke_keys;
hpke_keys.emplace_back("id123");
std::optional<AggregatableReport> report =
AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
std::move(request.value()), {hpke_keys[0].GetPublicKey()});
ASSERT_NO_FATAL_FAILURE(VerifyReport(
report, payload_contents, example_request.shared_info(),
expected_num_processing_urls, /*expected_debug_key=*/std::nullopt,
/*expected_additional_fields=*/{}, std::move(hpke_keys),
/*should_pad_contributions=*/GetParam()));
}
TEST_P(AggregatableReportFilteringIdTest,
FilteringIdsSpecified_ValidReportReturned) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.contributions.clear();
payload_contents.contributions.emplace_back(/*bucket=*/123, /*value=*/456,
/*filtering_id=*/std::nullopt);
payload_contents.contributions.emplace_back(/*bucket=*/234, /*value=*/567,
/*filtering_id=*/0);
payload_contents.contributions.emplace_back(
/*bucket=*/345, /*value=*/678, /*filtering_id=*/(1ULL << (5 * 8)) - 1);
payload_contents.filtering_id_max_bytes = 5;
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone());
ASSERT_TRUE(request.has_value());
size_t expected_num_processing_urls = request->processing_urls().size();
std::vector<aggregation_service::TestHpkeKey> hpke_keys;
hpke_keys.emplace_back("id123");
std::optional<AggregatableReport> report =
AggregatableReport::Provider().CreateFromRequestAndPublicKeys(
std::move(request.value()), {hpke_keys[0].GetPublicKey()});
ASSERT_NO_FATAL_FAILURE(VerifyReport(
report, payload_contents, example_request.shared_info(),
expected_num_processing_urls, /*expected_debug_key=*/std::nullopt,
/*expected_additional_fields=*/{}, std::move(hpke_keys),
/*should_pad_contributions=*/GetParam()));
}
TEST_P(AggregatableReportTest,
RequestCreatedWithNonPositiveValue_FailsIfNegative) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
AggregatableReportSharedInfo shared_info =
example_request.shared_info().Clone();
AggregationServicePayloadContents zero_value_payload_contents =
payload_contents;
zero_value_payload_contents.contributions[0].value = 0;
std::optional<AggregatableReportRequest> zero_value_request =
AggregatableReportRequest::Create(zero_value_payload_contents,
shared_info.Clone());
EXPECT_TRUE(zero_value_request.has_value());
AggregationServicePayloadContents negative_value_payload_contents =
payload_contents;
negative_value_payload_contents.contributions[0].value = -1;
std::optional<AggregatableReportRequest> negative_value_request =
AggregatableReportRequest::Create(negative_value_payload_contents,
shared_info.Clone());
EXPECT_FALSE(negative_value_request.has_value());
}
TEST_P(AggregatableReportTest, RequestCreatedWithInvalidReportId_Failed) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo shared_info =
example_request.shared_info().Clone();
shared_info.report_id = base::Uuid();
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(example_request.payload_contents(),
std::move(shared_info));
EXPECT_FALSE(request.has_value());
}
TEST_P(AggregatableReportTest, TeeBasedRequestCreatedWithZeroContributions) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest(
blink::mojom::AggregationServiceMode::kTeeBased);
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.contributions.clear();
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone());
EXPECT_TRUE(request.has_value());
}
TEST_P(AggregatableReportTest,
ExperimentalPoplarRequestNotCreatedWithZeroContributions) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest(
blink::mojom::AggregationServiceMode::kExperimentalPoplar);
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.contributions.clear();
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone());
EXPECT_FALSE(request.has_value());
}
TEST_P(AggregatableReportTest, RequestCreatedWithTooManyContributions) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest(
blink::mojom::AggregationServiceMode::kExperimentalPoplar);
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.contributions = {
blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/123,
/*value=*/456, /*filtering_id=*/std::nullopt),
blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/7890,
/*value=*/1234, /*filtering_id=*/std::nullopt)};
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone());
ASSERT_FALSE(request.has_value());
}
TEST_P(AggregatableReportTest,
RequestCreatedWithDebugKeyButDebugModeDisabled_Failed) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(example_request.payload_contents(),
example_request.shared_info().Clone(),
/*reporting_path=*/std::string(),
/*debug_key=*/1234);
EXPECT_FALSE(request.has_value());
}
TEST_P(AggregatableReportTest, GetAsJsonOnePayload_ValidJsonReturned) {
std::vector<AggregatableReport::AggregationServicePayload> payloads;
payloads.emplace_back(/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key_1",
/*debug_cleartext_payload=*/std::nullopt);
AggregatableReport report(std::move(payloads), "example_shared_info",
/*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt);
std::string report_json_string;
base::JSONWriter::Write(base::Value(report.GetAsJson()), &report_json_string);
const char kExpectedJsonString[] =
R"({)"
R"("aggregation_coordinator_origin":"https://a.test",)"
R"("aggregation_service_payloads":[)"
R"({"key_id":"key_1","payload":"ABCD1234"})"
R"(],)"
R"("shared_info":"example_shared_info")"
R"(})";
EXPECT_EQ(report_json_string, kExpectedJsonString);
}
TEST_P(AggregatableReportTest, GetAsJsonTwoPayloads_ValidJsonReturned) {
std::vector<AggregatableReport::AggregationServicePayload> payloads;
payloads.emplace_back(/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key_1",
/*debug_cleartext_payload=*/std::nullopt);
payloads.emplace_back(/*payload=*/kEFGH5678AsBytes,
/*key_id=*/"key_2",
/*debug_cleartext_payload=*/std::nullopt);
AggregatableReport report(std::move(payloads), "example_shared_info",
/*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt);
std::string report_json_string;
base::JSONWriter::Write(base::Value(report.GetAsJson()), &report_json_string);
const char kExpectedJsonString[] =
R"({)"
R"("aggregation_coordinator_origin":"https://a.test",)"
R"("aggregation_service_payloads":[)"
R"({"key_id":"key_1","payload":"ABCD1234"},)"
R"({"key_id":"key_2","payload":"EFGH5678"})"
R"(],)"
R"("shared_info":"example_shared_info")"
R"(})";
EXPECT_EQ(report_json_string, kExpectedJsonString);
}
TEST_P(AggregatableReportTest,
GetAsJsonDebugCleartextPayload_ValidJsonReturned) {
std::vector<AggregatableReport::AggregationServicePayload> payloads;
payloads.emplace_back(/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key_1",
/*debug_cleartext_payload=*/kEFGH5678AsBytes);
AggregatableReport report(std::move(payloads), "example_shared_info",
/*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt);
std::string report_json_string;
base::JSONWriter::Write(base::Value(report.GetAsJson()), &report_json_string);
const char kExpectedJsonString[] =
R"({)"
R"("aggregation_coordinator_origin":"https://a.test",)"
R"("aggregation_service_payloads":[{)"
R"("debug_cleartext_payload":"EFGH5678",)"
R"("key_id":"key_1",)"
R"("payload":"ABCD1234")"
R"(}],)"
R"("shared_info":"example_shared_info")"
R"(})";
EXPECT_EQ(report_json_string, kExpectedJsonString);
}
TEST_P(AggregatableReportTest, GetAsJsonDebugKey_ValidJsonReturned) {
std::vector<AggregatableReport::AggregationServicePayload> payloads;
payloads.emplace_back(/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key_1",
/*debug_cleartext_payload=*/kEFGH5678AsBytes);
AggregatableReport report(std::move(payloads), "example_shared_info",
/*debug_key=*/1234, /*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt);
std::string report_json_string;
base::JSONWriter::Write(base::Value(report.GetAsJson()), &report_json_string);
const char kExpectedJsonString[] =
R"({)"
R"("aggregation_coordinator_origin":"https://a.test",)"
R"("aggregation_service_payloads":[{)"
R"("debug_cleartext_payload":"EFGH5678",)"
R"("key_id":"key_1",)"
R"("payload":"ABCD1234")"
R"(}],)"
R"("debug_key":"1234",)"
R"("shared_info":"example_shared_info")"
R"(})";
EXPECT_EQ(report_json_string, kExpectedJsonString);
}
TEST_P(AggregatableReportTest, GetAsJsonAdditionalFields_ValidJsonReturned) {
std::vector<AggregatableReport::AggregationServicePayload> payloads;
payloads.emplace_back(/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key_1",
/*debug_cleartext_payload=*/std::nullopt);
AggregatableReport report(
std::move(payloads), "example_shared_info",
/*debug_key=*/std::nullopt, /*additional_fields=*/
{{"additional_key", "example_value"}, {"second", "field"}, {"", ""}},
/*aggregation_coordinator_origin=*/std::nullopt);
std::string report_json_string;
base::JSONWriter::Write(base::Value(report.GetAsJson()), &report_json_string);
const char kExpectedJsonString[] =
R"({)"
R"("":"",)"
R"("additional_key":"example_value",)"
R"("aggregation_coordinator_origin":"https://a.test",)"
R"("aggregation_service_payloads":[{)"
R"("key_id":"key_1",)"
R"("payload":"ABCD1234")"
R"(}],)"
R"("second":"field",)"
R"("shared_info":"example_shared_info")"
R"(})";
EXPECT_EQ(report_json_string, kExpectedJsonString);
}
TEST_P(AggregatableReportTest,
SharedInfoDebugModeDisabled_SerializeAsJsonReturnsExpectedString) {
AggregatableReportSharedInfo shared_info(
base::Time::FromMillisecondsSinceUnixEpoch(1234567890123),
/*report_id=*/
base::Uuid::ParseLowercase("21abd97f-73e8-4b88-9389-a9fee6abda5e"),
url::Origin::Create(GURL("https://reporting.example")),
AggregatableReportSharedInfo::DebugMode::kDisabled, base::Value::Dict(),
/*api_version=*/"1.0",
/*api_identifier=*/"example-api");
const char kExpectedString[] =
R"({)"
R"("api":"example-api",)"
R"("report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",)"
R"("reporting_origin":"https://reporting.example",)"
R"("scheduled_report_time":"1234567890",)"
R"("version":"1.0")"
R"(})";
EXPECT_EQ(shared_info.SerializeAsJson(), kExpectedString);
}
TEST_P(AggregatableReportTest,
SharedInfoDebugModeEnabled_SerializeAsJsonReturnsExpectedString) {
AggregatableReportSharedInfo shared_info(
base::Time::FromMillisecondsSinceUnixEpoch(1234567890123),
/*report_id=*/
base::Uuid::ParseLowercase("21abd97f-73e8-4b88-9389-a9fee6abda5e"),
url::Origin::Create(GURL("https://reporting.example")),
AggregatableReportSharedInfo::DebugMode::kEnabled, base::Value::Dict(),
/*api_version=*/"1.0",
/*api_identifier=*/"example-api");
const char kExpectedString[] =
R"({)"
R"("api":"example-api",)"
R"("debug_mode":"enabled",)"
R"("report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",)"
R"("reporting_origin":"https://reporting.example",)"
R"("scheduled_report_time":"1234567890",)"
R"("version":"1.0")"
R"(})";
EXPECT_EQ(shared_info.SerializeAsJson(), kExpectedString);
}
TEST_P(AggregatableReportTest, SharedInfoAdditionalFields) {
base::Value::Dict additional_fields;
additional_fields.Set("foo", "1");
additional_fields.Set("bar", "2");
additional_fields.Set("baz", "3");
AggregatableReportSharedInfo shared_info(
base::Time::FromMillisecondsSinceUnixEpoch(1234567890123),
/*report_id=*/
base::Uuid::ParseLowercase("21abd97f-73e8-4b88-9389-a9fee6abda5e"),
url::Origin::Create(GURL("https://reporting.example")),
AggregatableReportSharedInfo::DebugMode::kEnabled,
std::move(additional_fields),
/*api_version=*/"1.0",
/*api_identifier=*/"example-api");
const char kExpectedString[] =
R"({)"
R"("api":"example-api",)"
R"("bar":"2",)"
R"("baz":"3",)"
R"("debug_mode":"enabled",)"
R"("foo":"1",)"
R"("report_id":"21abd97f-73e8-4b88-9389-a9fee6abda5e",)"
R"("reporting_origin":"https://reporting.example",)"
R"("scheduled_report_time":"1234567890",)"
R"("version":"1.0")"
R"(})";
EXPECT_EQ(shared_info.SerializeAsJson(), kExpectedString);
}
TEST_P(AggregatableReportTest, ReportingPathSet_SetInRequest) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest(
blink::mojom::AggregationServiceMode::kExperimentalPoplar);
std::string reporting_path = "/example-path";
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(example_request.payload_contents(),
example_request.shared_info().Clone(),
reporting_path);
ASSERT_TRUE(request.has_value());
EXPECT_EQ(request->reporting_path(), reporting_path);
EXPECT_EQ(request->GetReportingUrl().path(), reporting_path);
EXPECT_EQ(request->GetReportingUrl().GetWithEmptyPath(),
example_request.shared_info().reporting_origin.GetURL());
}
TEST_P(AggregatableReportTest, RequestCreatedWithInvalidFailedAttempt_Failed) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregatableReportSharedInfo shared_info =
example_request.shared_info().Clone();
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(
example_request.payload_contents(), std::move(shared_info),
/*reporting_path=*/"", /*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*failed_send_attempts=*/-1);
EXPECT_FALSE(request.has_value());
}
TEST_P(AggregatableReportTest,
RequestCreatedWithMaxContributionsAllowed_FailsIfInvalid) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.max_contributions_allowed = -1;
std::optional<AggregatableReportRequest> negative_request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone());
EXPECT_FALSE(negative_request.has_value());
payload_contents.contributions.emplace_back(/*bucket=*/456,
/*value=*/78,
/*filtering_id=*/std::nullopt);
payload_contents.max_contributions_allowed = 1;
std::optional<AggregatableReportRequest> too_small_max_request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone());
EXPECT_FALSE(too_small_max_request.has_value());
payload_contents.contributions = {};
payload_contents.max_contributions_allowed = 0;
std::optional<AggregatableReportRequest> empty_zero_request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone());
EXPECT_TRUE(empty_zero_request.has_value());
}
TEST_P(AggregatableReportTest, FailedSendAttempts) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
// Requests are initialized with no failed attempts by default
EXPECT_EQ(example_request.failed_send_attempts(), 0);
AggregatableReportRequest example_request_with_failed_attempts =
aggregation_service::CreateExampleRequest(
/*aggregation_mode=*/blink::mojom::AggregationServiceMode::kDefault,
/*failed_send_attempts=*/2);
// The failed attempts are correctly serialized & deserialized
std::vector<uint8_t> proto = example_request_with_failed_attempts.Serialize();
std::optional<AggregatableReportRequest> parsed_request =
AggregatableReportRequest::Deserialize(proto);
EXPECT_EQ(parsed_request.value().failed_send_attempts(), 2);
}
TEST_P(AggregatableReportTest, MaxContributionsAllowed) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.max_contributions_allowed = 20;
AggregatableReportRequest request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone())
.value();
// The max contributions allowed is correctly serialized and deserialized
std::vector<uint8_t> proto = request.Serialize();
std::optional<AggregatableReportRequest> parsed_request =
AggregatableReportRequest::Deserialize(proto);
EXPECT_EQ(parsed_request.value().payload_contents().max_contributions_allowed,
20);
}
TEST_P(AggregatableReportTest, AggregationCoordinatorOrigin) {
const struct {
std::optional<url::Origin> aggregation_coordinator_origin;
bool creation_should_succeed;
const char* description;
} kTestCases[] = {
{std::nullopt, true, "default coordinator"},
{url::Origin::Create(GURL("https://a.test")), true, "valid coordinator"},
{url::Origin::Create(GURL("https://c.test")), false,
"invalid coordinator"},
};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(test_case.description);
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.aggregation_coordinator_origin =
test_case.aggregation_coordinator_origin;
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(
payload_contents, example_request.shared_info().Clone());
EXPECT_EQ(request.has_value(), test_case.creation_should_succeed);
if (!request.has_value()) {
continue;
}
// The coordinator origin is correctly serialized and deserialized
std::vector<uint8_t> proto = request->Serialize();
std::optional<AggregatableReportRequest> parsed_request =
AggregatableReportRequest::Deserialize(proto);
EXPECT_EQ(parsed_request.value()
.payload_contents()
.aggregation_coordinator_origin,
test_case.aggregation_coordinator_origin);
}
}
TEST_P(AggregatableReportTest, AggregationCoordinatorOriginAllowlistChanged) {
std::optional<
::aggregation_service::ScopedAggregationCoordinatorAllowlistForTesting>
scoped_coordinator_allowlist;
scoped_coordinator_allowlist.emplace(
{url::Origin::Create(GURL("https://a.test"))});
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.aggregation_coordinator_origin =
url::Origin::Create(GURL("https://a.test"));
AggregatableReportRequest request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone())
.value();
std::vector<uint8_t> proto = request.Serialize();
// Change the allowlist between serializing and deserializing
scoped_coordinator_allowlist.reset();
scoped_coordinator_allowlist.emplace(
{url::Origin::Create(GURL("https://b.test"))});
// Expect the report to fail to be recreated.
std::optional<AggregatableReportRequest> parsed_request =
AggregatableReportRequest::Deserialize(proto);
EXPECT_FALSE(parsed_request.has_value());
}
TEST_P(AggregatableReportTest, ReportingPathEmpty_NotSetInRequest) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest(
blink::mojom::AggregationServiceMode::kExperimentalPoplar);
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(example_request.payload_contents(),
example_request.shared_info().Clone());
ASSERT_TRUE(request.has_value());
EXPECT_TRUE(request->reporting_path().empty());
// If the reporting path is empty,
EXPECT_FALSE(request->GetReportingUrl().is_valid());
}
TEST_P(AggregatableReportTest, EmptyPayloads) {
AggregatableReport report(/*payloads=*/{}, "example_shared_info",
/*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt);
std::string report_json_string;
base::JSONWriter::Write(base::Value(report.GetAsJson()), &report_json_string);
const char kExpectedJsonString[] =
R"({)"
R"("aggregation_coordinator_origin":"https://a.test",)"
R"("shared_info":"example_shared_info")"
R"(})";
EXPECT_EQ(report_json_string, kExpectedJsonString);
}
TEST_P(AggregatableReportFilteringIdTest, FilteringIdMaxBytesNullopt) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.filtering_id_max_bytes.reset();
AggregatableReportRequest request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone())
.value();
// The filtering_id_max_bytes is correctly serialized and deserialized
std::vector<uint8_t> proto = request.Serialize();
std::optional<AggregatableReportRequest> parsed_request =
AggregatableReportRequest::Deserialize(proto);
EXPECT_FALSE(parsed_request.value()
.payload_contents()
.filtering_id_max_bytes.has_value());
// Trying to set any explicit filtering ID will cause an error
payload_contents.contributions[0].filtering_id = 0;
EXPECT_FALSE(AggregatableReportRequest::Create(
payload_contents, example_request.shared_info().Clone())
.has_value());
}
TEST_P(AggregatableReportFilteringIdTest, FilteringIdMaxBytesMax) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.filtering_id_max_bytes =
AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes;
// Trying to set any explicit filtering ID (or none) should work.
const std::optional<uint64_t> kTestCases[] = {
std::nullopt, 0, 1, std::numeric_limits<uint64_t>::max()};
for (const std::optional<uint64_t> test_case : kTestCases) {
payload_contents.contributions[0].filtering_id = test_case;
AggregatableReportRequest request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone())
.value();
// The report is correctly serialized and deserialized
std::vector<uint8_t> proto = request.Serialize();
std::optional<AggregatableReportRequest> parsed_request =
AggregatableReportRequest::Deserialize(proto);
EXPECT_EQ(parsed_request.value().payload_contents().filtering_id_max_bytes,
AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes);
EXPECT_EQ(
parsed_request.value().payload_contents().contributions[0].filtering_id,
test_case);
}
}
TEST_P(AggregatableReportFilteringIdTest, FilteringIdMaxBytesNotMax) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.filtering_id_max_bytes = 1;
// The filtering ID needs to fit in the specified space to be accepted.
const struct {
std::optional<uint64_t> filtering_id;
bool expect_success;
} kTestCases[] = {{std::nullopt, true},
{0, true},
{1, true},
{255, true},
{256, false},
{std::numeric_limits<uint64_t>::max(), false}};
for (const auto& test_case : kTestCases) {
payload_contents.contributions[0].filtering_id = test_case.filtering_id;
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(
payload_contents, example_request.shared_info().Clone());
EXPECT_EQ(request.has_value(), test_case.expect_success);
if (!request.has_value()) {
continue;
}
// The report is correctly serialized and deserialized
std::vector<uint8_t> proto = request->Serialize();
std::optional<AggregatableReportRequest> parsed_request =
AggregatableReportRequest::Deserialize(proto);
EXPECT_EQ(parsed_request.value().payload_contents().filtering_id_max_bytes,
1);
EXPECT_EQ(
parsed_request.value().payload_contents().contributions[0].filtering_id,
test_case.filtering_id);
}
}
TEST_P(AggregatableReportTest, FilteringIdsIgnoredIfFeatureDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(
kPrivacySandboxAggregationServiceFilteringIds);
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
// No matter what combination is used (even if typically invalid), the
// filtering IDs and max bytes should be ignored.
const struct {
std::optional<uint64_t> filtering_id;
std::optional<int> filtering_id_max_bytes;
} kTestCases[] = {
{std::nullopt, std::nullopt},
{0, std::nullopt},
{std::nullopt, 1},
{0, 1},
{std::numeric_limits<uint64_t>::max(), std::nullopt},
{std::numeric_limits<uint64_t>::max(), 1},
{std::nullopt, -1},
{std::nullopt,
AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes + 1}};
for (const auto& test_case : kTestCases) {
payload_contents.contributions[0].filtering_id = test_case.filtering_id;
payload_contents.filtering_id_max_bytes = test_case.filtering_id_max_bytes;
AggregatableReportRequest request =
AggregatableReportRequest::Create(payload_contents,
example_request.shared_info().Clone())
.value();
EXPECT_FALSE(request.payload_contents().filtering_id_max_bytes.has_value());
EXPECT_FALSE(
request.payload_contents().contributions[0].filtering_id.has_value());
}
}
TEST_P(AggregatableReportFilteringIdTest, FilteringIdMaxBytesTooSmall) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.filtering_id_max_bytes = 0;
EXPECT_FALSE(AggregatableReportRequest::Create(
payload_contents, example_request.shared_info().Clone())
.has_value());
payload_contents.filtering_id_max_bytes = -1;
EXPECT_FALSE(AggregatableReportRequest::Create(
payload_contents, example_request.shared_info().Clone())
.has_value());
}
TEST_P(AggregatableReportFilteringIdTest, FilteringIdMaxBytesTooLarge) {
AggregatableReportRequest example_request =
aggregation_service::CreateExampleRequest();
AggregationServicePayloadContents payload_contents =
example_request.payload_contents();
payload_contents.filtering_id_max_bytes =
AggregationServicePayloadContents::kMaximumFilteringIdMaxBytes + 1;
EXPECT_FALSE(AggregatableReportRequest::Create(
payload_contents, example_request.shared_info().Clone())
.has_value());
}
TEST(AggregatableReportProtoMigrationTest,
NoDebugKeyOrFailedSendAttempts_ParsesCorrectly) {
// An `AggregatableReport` serialized before the addition of the `debug_key`
// field and `failed_send_attempts` field.
const char kHexEncodedOldProto[] =
"0A071205107B18C803126208D0DA8693FDBECF17122431323334353637382D393061622D"
"346364652D386631322D3334353637383930616263641A1368747470733A2F2F6578616D"
"706C652E636F6D2A0F6578616D706C652D76657273696F6E320B6578616D706C652D6170"
"691A0C6578616D706C652D70617468";
std::vector<uint8_t> old_proto;
EXPECT_TRUE(base::HexStringToBytes(kHexEncodedOldProto, &old_proto));
std::optional<AggregatableReportRequest> deserialized_request =
AggregatableReportRequest::Deserialize(old_proto);
ASSERT_TRUE(deserialized_request.has_value());
AggregatableReportRequest expected_request =
AggregatableReportRequest::Create(
AggregationServicePayloadContents(
AggregationServicePayloadContents::Operation::kHistogram,
{blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/123, /*value=*/456,
/*filtering_id=*/std::nullopt)},
blink::mojom::AggregationServiceMode::kDefault,
/*aggregation_coordinator_origin=*/std::nullopt,
/*max_contributions_allowed=*/1,
/*filtering_id_max_bytes=*/std::nullopt),
AggregatableReportSharedInfo(
base::Time::FromMillisecondsSinceUnixEpoch(1652984901234),
base::Uuid::ParseLowercase(
"12345678-90ab-4cde-8f12-34567890abcd"),
/*reporting_origin=*/
url::Origin::Create(GURL("https://example.com")),
AggregatableReportSharedInfo::DebugMode::kDisabled,
/*additional_fields=*/base::Value::Dict(),
/*api_version=*/"example-version",
/*api_identifier=*/"example-api"),
/*reporting_path=*/"example-path", /*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*failed_send_attempts=*/0)
.value();
EXPECT_TRUE(aggregation_service::ReportRequestsEqual(
deserialized_request.value(), expected_request));
}
TEST(AggregatableReportProtoMigrationTest, NegativeDebugKey_ParsesCorrectly) {
// An `AggregatableReport` serialized while `debug_key` was stored as a signed
// int64 and used a value that was larger than the maximum int64. It was
// therefore stored as a negative number.
const char kHexEncodedOldProto[] =
"0A071205107B18C803126408D0DA8693FDBECF17122431323334353637382D393061622D"
"346364652D386631322D3334353637383930616263641A1368747470733A2F2F6578616D"
"706C652E636F6D20012A0F6578616D706C652D76657273696F6E320B6578616D706C652D"
"6170691A0C6578616D706C652D7061746820FFFFFFFFFFFFFFFFFF01";
std::vector<uint8_t> old_proto;
EXPECT_TRUE(base::HexStringToBytes(kHexEncodedOldProto, &old_proto));
std::optional<AggregatableReportRequest> deserialized_request =
AggregatableReportRequest::Deserialize(old_proto);
ASSERT_TRUE(deserialized_request.has_value());
AggregatableReportRequest expected_request =
AggregatableReportRequest::Create(
AggregationServicePayloadContents(
AggregationServicePayloadContents::Operation::kHistogram,
{blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/123, /*value=*/456,
/*filtering_id=*/std::nullopt)},
blink::mojom::AggregationServiceMode::kDefault,
/*aggregation_coordinator_origin=*/std::nullopt,
/*max_contributions_allowed=*/1,
/*filtering_id_max_bytes=*/std::nullopt),
AggregatableReportSharedInfo(
base::Time::FromMillisecondsSinceUnixEpoch(1652984901234),
base::Uuid::ParseLowercase(
"12345678-90ab-4cde-8f12-34567890abcd"),
/*reporting_origin=*/
url::Origin::Create(GURL("https://example.com")),
AggregatableReportSharedInfo::DebugMode::kEnabled,
/*additional_fields=*/base::Value::Dict(),
/*api_version=*/"example-version",
/*api_identifier=*/"example-api"),
/*reporting_path=*/"example-path",
/*debug_key=*/std::numeric_limits<uint64_t>::max())
.value();
EXPECT_TRUE(aggregation_service::ReportRequestsEqual(
deserialized_request.value(), expected_request));
}
TEST(
AggregatableReportProtoMigrationTest,
NoAdditionalFieldsOrAggregationCoordinatorOriginOrFilteringId_ParsesCorrectly) {
// An `AggregatableReport` serialized before `additional_fields`,
// `aggregataion_coordinator_origin`, `filtering_id` and
// `filtering_id_max_bytes` were added to the proto definition.
const char kHexEncodedOldProto[] =
"0A071205107B18C803126208D0DA8693FDBECF17122431323334353637382D393061622D"
"346364652D386631322D3334353637383930616263641A1368747470733A2F2F6578616D"
"706C652E636F6D2A0F6578616D706C652D76657273696F6E320B6578616D706C652D6170"
"691A0C6578616D706C652D70617468";
std::vector<uint8_t> old_proto;
EXPECT_TRUE(base::HexStringToBytes(kHexEncodedOldProto, &old_proto));
std::optional<AggregatableReportRequest> deserialized_request =
AggregatableReportRequest::Deserialize(old_proto);
ASSERT_TRUE(deserialized_request.has_value());
AggregatableReportRequest expected_request =
AggregatableReportRequest::Create(
AggregationServicePayloadContents(
AggregationServicePayloadContents::Operation::kHistogram,
{blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/123, /*value=*/456,
/*filtering_id=*/std::nullopt)},
blink::mojom::AggregationServiceMode::kDefault,
/*aggregation_coordinator_origin=*/std::nullopt,
/*max_contributions_allowed=*/1,
/*filtering_id_max_bytes=*/std::nullopt),
AggregatableReportSharedInfo(
base::Time::FromMillisecondsSinceUnixEpoch(1652984901234),
base::Uuid::ParseLowercase(
"12345678-90ab-4cde-8f12-34567890abcd"),
/*reporting_origin=*/
url::Origin::Create(GURL("https://example.com")),
AggregatableReportSharedInfo::DebugMode::kDisabled,
/*additional_fields=*/base::Value::Dict(),
/*api_version=*/"example-version",
/*api_identifier=*/"example-api"),
/*reporting_path=*/"example-path", /*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*failed_send_attempts=*/0)
.value();
EXPECT_TRUE(aggregation_service::ReportRequestsEqual(
deserialized_request.value(), expected_request));
}
TEST_P(AggregatableReportTest, ProcessingUrlSet) {
AggregatableReportRequest request =
aggregation_service::CreateExampleRequest();
EXPECT_THAT(
request.processing_urls(),
::testing::ElementsAre(GetAggregationServiceProcessingUrl(
::aggregation_service::GetDefaultAggregationCoordinatorOrigin())));
}
TEST_P(AggregatableReportTest, AggregationCoordinator_ProcessingUrlSet) {
const struct {
std::optional<url::Origin> aggregation_coordinator_origin;
std::vector<GURL> expected_urls;
} kTestCases[] = {
{
std::nullopt,
{GURL("https://a.test/.well-known/aggregation-service/v1/"
"public-keys")},
},
{
url::Origin::Create(GURL("https://a.test")),
{GURL("https://a.test/.well-known/aggregation-service/v1/"
"public-keys")},
},
{
url::Origin::Create(GURL("https://b.test")),
{GURL("https://b.test/.well-known/aggregation-service/v1/"
"public-keys")},
},
{
url::Origin::Create(GURL("https://c.test")),
{},
},
};
for (const auto& test_case : kTestCases) {
std::optional<AggregatableReportRequest> request =
AggregatableReportRequest::Create(
AggregationServicePayloadContents(
AggregationServicePayloadContents::Operation::kHistogram,
{blink::mojom::AggregatableReportHistogramContribution(
/*bucket=*/123,
/*value=*/456,
/*filtering_id=*/std::nullopt)},
blink::mojom::AggregationServiceMode::kDefault,
test_case.aggregation_coordinator_origin,
/*max_contributions_allowed=*/20,
/*filtering_id_max_bytes=*/std::nullopt),
AggregatableReportSharedInfo(
/*scheduled_report_time=*/base::Time::Now(),
/*report_id=*/
base::Uuid::GenerateRandomV4(),
url::Origin::Create(GURL("https://reporting.example")),
AggregatableReportSharedInfo::DebugMode::kDisabled,
/*additional_fields=*/base::Value::Dict(),
/*api_version=*/"",
/*api_identifier=*/"example-api"),
/*reporting_path=*/"example-path",
/*debug_key=*/std::nullopt, /*additional_fields=*/{},
/*failed_send_attempts=*/0);
if (test_case.expected_urls.empty()) {
EXPECT_FALSE(request.has_value());
} else {
EXPECT_EQ(request->processing_urls(), test_case.expected_urls);
}
}
}
TEST_P(AggregatableReportTest, AggregationCoordinator_SetInReport) {
std::vector<AggregatableReport::AggregationServicePayload> payloads;
payloads.emplace_back(/*payload=*/kABCD1234AsBytes,
/*key_id=*/"key_1",
/*debug_cleartext_payload=*/std::nullopt);
AggregatableReport report(std::move(payloads), "example_shared_info",
/*debug_key=*/std::nullopt,
/*additional_fields=*/{},
/*aggregation_coordinator_origin=*/std::nullopt);
std::string report_json_string;
base::JSONWriter::Write(base::Value(report.GetAsJson()), &report_json_string);
const char kExpectedJsonString[] =
R"({)"
R"("aggregation_coordinator_origin":"https://a.test",)"
R"("aggregation_service_payloads":[)"
R"({"key_id":"key_1","payload":"ABCD1234"})"
R"(],)"
R"("shared_info":"example_shared_info")"
R"(})";
EXPECT_EQ(report_json_string, kExpectedJsonString);
}
INSTANTIATE_TEST_SUITE_P(All, AggregatableReportTest, testing::Bool());
INSTANTIATE_TEST_SUITE_P(All,
AggregatableReportFilteringIdTest,
testing::Bool());
} // namespace
} // namespace content