910e62b5创建于 1月15日历史提交
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/attribution_reporting/privacy_math.h"

#include <stdint.h>

#include <cmath>
#include <limits>
#include <map>
#include <optional>
#include <set>
#include <utility>
#include <vector>

#include "base/numerics/checked_math.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "components/attribution_reporting/attribution_scopes_data.h"
#include "components/attribution_reporting/attribution_scopes_set.h"
#include "components/attribution_reporting/constants.h"
#include "components/attribution_reporting/event_report_windows.h"
#include "components/attribution_reporting/fuzz_utils.h"
#include "components/attribution_reporting/max_event_level_reports.h"
#include "components/attribution_reporting/source_type.mojom.h"
#include "components/attribution_reporting/test_utils.h"
#include "components/attribution_reporting/trigger_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/fuzztest/src/fuzztest/fuzztest.h"

namespace attribution_reporting {
namespace {

using ::attribution_reporting::EventReportWindows;
using ::attribution_reporting::MaxEventLevelReports;
using ::attribution_reporting::TriggerDataSet;
using ::attribution_reporting::mojom::SourceType;
using ::base::test::ErrorIs;
using ::base::test::ValueIs;
using ::testing::UnorderedElementsAreArray;

MATCHER_P(IsValidAndHolds, v, "") {
  if (!arg.IsValid()) {
    *result_listener << "which is invalid";
    return false;
  }

  return ExplainMatchResult(v, arg.ValueOrDie(), result_listener);
}

TEST(PrivacyMathTest, BinomialCoefficient) {
  // Test cases generated via a python program using scipy.special.comb.
  struct {
    uint32_t n;
    uint32_t k;
    uint32_t expected;
  } kTestCases[]{
      // All cases for n and k in [0, 10).
      // clang-format off
      {0, 0, 1},  {0, 1, 0},  {0, 2, 0},  {0, 3, 0},  {0, 4, 0},   {0, 5, 0},
      {0, 6, 0},  {0, 7, 0},  {0, 8, 0},  {0, 9, 0},  {1, 0, 1},   {1, 1, 1},
      {1, 2, 0},  {1, 3, 0},  {1, 4, 0},  {1, 5, 0},  {1, 6, 0},   {1, 7, 0},
      {1, 8, 0},  {1, 9, 0},  {2, 0, 1},  {2, 1, 2},  {2, 2, 1},   {2, 3, 0},
      {2, 4, 0},  {2, 5, 0},  {2, 6, 0},  {2, 7, 0},  {2, 8, 0},   {2, 9, 0},
      {3, 0, 1},  {3, 1, 3},  {3, 2, 3},  {3, 3, 1},  {3, 4, 0},   {3, 5, 0},
      {3, 6, 0},  {3, 7, 0},  {3, 8, 0},  {3, 9, 0},  {4, 0, 1},   {4, 1, 4},
      {4, 2, 6},  {4, 3, 4},  {4, 4, 1},  {4, 5, 0},  {4, 6, 0},   {4, 7, 0},
      {4, 8, 0},  {4, 9, 0},  {5, 0, 1},  {5, 1, 5},  {5, 2, 10},  {5, 3, 10},
      {5, 4, 5},  {5, 5, 1},  {5, 6, 0},  {5, 7, 0},  {5, 8, 0},   {5, 9, 0},
      {6, 0, 1},  {6, 1, 6},  {6, 2, 15}, {6, 3, 20}, {6, 4, 15},  {6, 5, 6},
      {6, 6, 1},  {6, 7, 0},  {6, 8, 0},  {6, 9, 0},  {7, 0, 1},   {7, 1, 7},
      {7, 2, 21}, {7, 3, 35}, {7, 4, 35}, {7, 5, 21}, {7, 6, 7},   {7, 7, 1},
      {7, 8, 0},  {7, 9, 0},  {8, 0, 1},  {8, 1, 8},  {8, 2, 28},  {8, 3, 56},
      {8, 4, 70}, {8, 5, 56}, {8, 6, 28}, {8, 7, 8},  {8, 8, 1},   {8, 9, 0},
      {9, 0, 1},  {9, 1, 9},  {9, 2, 36}, {9, 3, 84}, {9, 4, 126}, {9, 5, 126},
      {9, 6, 84}, {9, 7, 36}, {9, 8, 9},  {9, 9, 1},
      // clang-format on

      // A few larger cases:
      {30, 3, 4060},
      {100, 2, 4950},
      {100, 5, 75287520},
  };
  for (const auto& test_case : kTestCases) {
    EXPECT_THAT(internal::BinomialCoefficient(test_case.n, test_case.k),
                IsValidAndHolds(test_case.expected))
        << "n=" << test_case.n << ", k=" << test_case.k;
  }
}

TEST(PrivacyMathTest, GetKCombinationAtIndex) {
  // Test cases vetted via an equivalent calculator:
  // https://planetcalc.com/8592/
  struct {
    uint32_t index;
    uint32_t k;
    std::vector<uint32_t> expected;
  } kTestCases[]{
      {0, 0, {}},

      // clang-format off
      {0, 1, {0}},        {1, 1, {1}},        {2, 1, {2}},
      {3, 1, {3}},        {4, 1, {4}},        {5, 1, {5}},
      {6, 1, {6}},        {7, 1, {7}},        {8, 1, {8}},
      {9, 1, {9}},        {10, 1, {10}},      {11, 1, {11}},
      {12, 1, {12}},      {13, 1, {13}},      {14, 1, {14}},
      {15, 1, {15}},      {16, 1, {16}},      {17, 1, {17}},
      {18, 1, {18}},      {19, 1, {19}},

      {0, 2, {1, 0}},     {1, 2, {2, 0}},     {2, 2, {2, 1}},
      {3, 2, {3, 0}},     {4, 2, {3, 1}},     {5, 2, {3, 2}},
      {6, 2, {4, 0}},     {7, 2, {4, 1}},     {8, 2, {4, 2}},
      {9, 2, {4, 3}},     {10, 2, {5, 0}},    {11, 2, {5, 1}},
      {12, 2, {5, 2}},    {13, 2, {5, 3}},    {14, 2, {5, 4}},
      {15, 2, {6, 0}},    {16, 2, {6, 1}},    {17, 2, {6, 2}},
      {18, 2, {6, 3}},    {19, 2, {6, 4}},

      {0, 3, {2, 1, 0}},  {1, 3, {3, 1, 0}},  {2, 3, {3, 2, 0}},
      {3, 3, {3, 2, 1}},  {4, 3, {4, 1, 0}},  {5, 3, {4, 2, 0}},
      {6, 3, {4, 2, 1}},  {7, 3, {4, 3, 0}},  {8, 3, {4, 3, 1}},
      {9, 3, {4, 3, 2}},  {10, 3, {5, 1, 0}}, {11, 3, {5, 2, 0}},
      {12, 3, {5, 2, 1}}, {13, 3, {5, 3, 0}}, {14, 3, {5, 3, 1}},
      {15, 3, {5, 3, 2}}, {16, 3, {5, 4, 0}}, {17, 3, {5, 4, 1}},
      {18, 3, {5, 4, 2}}, {19, 3, {5, 4, 3}},
      // clang-format on

      {2924, 3, {26, 25, 24}},
  };
  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(internal::GetKCombinationAtIndex(test_case.index, test_case.k),
              test_case.expected)
        << "index=" << test_case.index << ", k=" << test_case.k;
  }
}

// Simple stress test to make sure that GetKCombinationAtIndex is returning
// combinations uniquely indexed by the given index, i.e. there are never any
// repeats.
TEST(PrivacyMathTest, GetKCombination_NoRepeats) {
  for (uint32_t k = 1u; k < 5u; k++) {
    std::set<std::vector<uint32_t>> seen_combinations;
    for (uint32_t index = 0; index < 3000; index++) {
      std::vector<uint32_t> combination =
          internal::GetKCombinationAtIndex(index, k);
      EXPECT_TRUE(seen_combinations.insert(std::move(combination)).second)
          << "index=" << index << ", k=" << k;
    }
  }
}

// The k-combination at a given index is the unique set of k positive integers
// a_k > a_{k-1} > ... > a_2 > a_1 >= 0 such that
// `index` = \sum_{i=1}^k {a_i}\choose{i}
TEST(PrivacyMathTest, GetKCombination_MatchesDefinition) {
  for (uint32_t k = 1; k < 5; k++) {
    for (uint32_t index = 0; index < 3000; index++) {
      const std::vector<uint32_t> combination =
          internal::GetKCombinationAtIndex(index, k);
      base::CheckedNumeric<uint32_t> sum = 0;
      for (uint32_t i = 0; i < k; i++) {
        sum += internal::BinomialCoefficient(combination[i], k - i);
      }
      EXPECT_THAT(sum, IsValidAndHolds(index));
    }
  }
}

TEST(PrivacyMathTest, GetNumberOfStarsAndBarsSequences) {
  EXPECT_THAT(internal::GetNumberOfStarsAndBarsSequences(/*num_stars=*/1u,
                                                         /*num_bars=*/2u),
              IsValidAndHolds(3u));

  EXPECT_THAT(internal::GetNumberOfStarsAndBarsSequences(/*num_stars=*/3u,
                                                         /*num_bars=*/24u),
              IsValidAndHolds(2925u));
}

TEST(PrivacyMathTest, GetStarIndices) {
  const struct {
    uint32_t num_stars;
    uint32_t num_bars;
    uint32_t sequence_index;
    std::vector<uint32_t> expected;
  } kTestCases[] = {
      {1, 2, 2, {2}},
      {3, 24, 23, {6, 3, 0}},
  };

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(internal::GetStarIndices(test_case.num_stars, test_case.num_bars,
                                       test_case.sequence_index),
              test_case.expected);
  }
}

TEST(PrivacyMathTest, GetBarsPrecedingEachStar) {
  const struct {
    std::vector<uint32_t> star_indices;
    std::vector<uint32_t> expected;
  } kTestCases[] = {
      {{2}, {2}},
      {{6, 3, 0}, {4, 2, 0}},
  };

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(internal::GetBarsPrecedingEachStar(test_case.star_indices),
              test_case.expected);
  }
}

// Adapted from
// https://github.com/WICG/attribution-reporting-api/blob/ab43f8c989cf881ffd7a7f71801b98d649ed164a/flexible-event/privacy.test.ts#L76C1-L82C2
TEST(PrivacyMathTest, BinaryEntropy) {
  const struct {
    double x;
    double expected;
  } kTestCases[] = {
      {.x = 0, .expected = 0},
      {.x = 0.5, .expected = 1},
      {.x = 1, .expected = 0},
      {.x = 0.01, .expected = 0.08079313589591118},
      {.x = 0.99, .expected = 0.08079313589591124},
  };

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(test_case.expected, internal::BinaryEntropy(test_case.x));
  }
}

// Adapted from
// https://github.com/WICG/attribution-reporting-api/blob/ab43f8c989cf881ffd7a7f71801b98d649ed164a/flexible-event/privacy.test.ts#L10-L31
TEST(PrivacyMathTest, GetRandomizedResponseRate) {
  const struct {
    uint32_t num_states;
    double epsilon;
    double expected;
  } kTestCases[] = {{
                        .num_states = 2,
                        .epsilon = std::log(3),
                        .expected = 0.5,
                    },
                    {
                        .num_states = 3,
                        .epsilon = std::log(3),
                        .expected = 0.6,
                    },
                    {
                        .num_states = 2925,
                        .epsilon = 14,
                        .expected = 0.0024263221679834087,
                    },
                    {
                        .num_states = 3,
                        .epsilon = 14,
                        .expected = 0.000002494582008677539,
                    },
                    {
                        .num_states = 3,
                        .epsilon = 14,
                        .expected = 0.000002494582008677539,
                    },
                    {.num_states = std::numeric_limits<uint32_t>::max(),
                     .epsilon = 14,
                     .expected = 0.99972007548289821}};

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(test_case.expected, GetRandomizedResponseRate(
                                      test_case.num_states, test_case.epsilon));
  }
}

// Adapted from
// https://github.com/WICG/attribution-reporting-api/blob/ab43f8c989cf881ffd7a7f71801b98d649ed164a/flexible-event/privacy.test.ts#L38-L69
TEST(PrivacyMathTest, ComputeChannelCapacity) {
  const struct {
    uint32_t num_states;
    double epsilon;
    double expected;
  } kTestCases[] = {
      {
          .num_states = 2,
          .epsilon = std::numeric_limits<double>::infinity(),
          .expected = 1,
      },
      {
          .num_states = 1024,
          .epsilon = std::numeric_limits<double>::infinity(),
          .expected = std::log2(1024),
      },
      {
          .num_states = 3,
          .epsilon = std::numeric_limits<double>::infinity(),
          .expected = std::log2(3),
      },
      {
          .num_states = 2,
          .epsilon = std::log(3),
          .expected = 0.18872187554086717,
      },
      {
          .num_states = 2925,
          .epsilon = 14,
          .expected = 11.461727965384876,
      },
      {
          .num_states = 3,
          .epsilon = 14,
          .expected = 1.584926511508231,
      },
      {
          .num_states = 1,
          .epsilon = 14,
          .expected = 0,
      },
  };

  for (const auto& test_case : kTestCases) {
    double rate =
        GetRandomizedResponseRate(test_case.num_states, test_case.epsilon);

    EXPECT_EQ(test_case.expected,
              internal::ComputeChannelCapacity(test_case.num_states, rate));
  }
}

TEST(PrivacyMathTest, ComputeChannelCapacityWithScopes) {
  const struct {
    uint32_t num_states;
    uint32_t max_event_states;
    uint32_t attribution_scopes_limit;
    double expected;
  } kTestCases[] = {
      {
          .num_states = 2925,
          .max_event_states = 5,
          .attribution_scopes_limit = 20,
          .expected = 11.560332834212442,
      },
      {
          .num_states = 2925,
          .max_event_states = 3,
          .attribution_scopes_limit = 1,
          .expected = 11.51422090935813,
      },
      {
          .num_states = 2925,
          .max_event_states = 3,
          .attribution_scopes_limit = 5,
          .expected = 11.520127550324851,
      },
      {
          .num_states = 2925,
          .max_event_states = 165,
          .attribution_scopes_limit = 5,
          .expected = 11.807757403589267,
      },
      {
          .num_states = 300000,
          .max_event_states = 2,
          .attribution_scopes_limit = 2,
          .expected = 18.194612593092849,
      },
      {
          .num_states = 300000,
          .max_event_states = 3,
          .attribution_scopes_limit = 3,
          .expected = 18.194631828770252,
      },
      {
          .num_states = 300000,
          .max_event_states = 4,
          .attribution_scopes_limit = 4,
          .expected = 18.194660681805477,
      },
      {
          .num_states = 300000,
          .max_event_states = 5,
          .attribution_scopes_limit = 5,
          .expected = 18.194699151621514,
      },
      {
          .num_states = 1,
          .max_event_states = 5,
          .attribution_scopes_limit = 5,
          .expected = 4.3923174227787607,
      },
      // Regression test for multiplication overflow in
      // https://crbug.com/366998247
      {
          .num_states = 1,
          .max_event_states = std::numeric_limits<uint32_t>::max(),
          .attribution_scopes_limit = std::numeric_limits<uint32_t>::max(),
          .expected = 63.999999998992287,
      },
  };

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(test_case.expected,
              internal::ComputeChannelCapacityScopes(
                  test_case.num_states, test_case.max_event_states,
                  test_case.attribution_scopes_limit));
  }
}

TEST(PrivacyMathTest, GetFakeReportsForSequenceIndex) {
  const struct {
    SourceType source_type;
    uint32_t sequence_index;
    std::vector<FakeEventLevelReport> expected;
  } kTestCases[] = {
      // Event sources only have 3 output states, so we can enumerate them:
      {
          .source_type = SourceType::kEvent,
          .sequence_index = 0,
          .expected = {},
      },
      {
          .source_type = SourceType::kEvent,
          .sequence_index = 1,
          .expected = {{.trigger_data = 0, .window_index = 0}},
      },
      {
          .source_type = SourceType::kEvent,
          .sequence_index = 2,
          .expected = {{.trigger_data = 1, .window_index = 0}},
      },
      // Navigation sources have 2925 output states, so pick interesting ones:
      {
          .source_type = SourceType::kNavigation,
          .sequence_index = 0,
          .expected = {},
      },
      {
          .source_type = SourceType::kNavigation,
          .sequence_index = 20,
          .expected = {{.trigger_data = 3, .window_index = 0}},
      },
      {
          .source_type = SourceType::kNavigation,
          .sequence_index = 41,
          .expected =
              {
                  {.trigger_data = 4, .window_index = 0},
                  {.trigger_data = 2, .window_index = 0},
              },
      },
      {
          .source_type = SourceType::kNavigation,
          .sequence_index = 50,
          .expected =
              {
                  {.trigger_data = 4, .window_index = 0},
                  {.trigger_data = 4, .window_index = 0},
              },
      },
      {
          .source_type = SourceType::kNavigation,
          .sequence_index = 1268,
          .expected =
              {
                  {.trigger_data = 1, .window_index = 2},
                  {.trigger_data = 6, .window_index = 1},
                  {.trigger_data = 7, .window_index = 0},
              },
      },
  };

  for (int i = 0; const auto& test_case : kTestCases) {
    SCOPED_TRACE(i);

    EXPECT_THAT(internal::GetFakeReportsForSequenceIndex(
                    TriggerDataSet(test_case.source_type),
                    *EventReportWindows::FromDefaults(base::Days(30),
                                                      test_case.source_type),
                    MaxEventLevelReports(test_case.source_type),
                    test_case.sequence_index),
                ValueIs(UnorderedElementsAreArray(test_case.expected)));

    ++i;
  }
}

void RunRandomFakeReportsTest(const TriggerDataSet& trigger_data,
                              const EventReportWindows& event_report_windows,
                              const MaxEventLevelReports max_reports,
                              const int num_samples,
                              const double tolerance) {
  std::map<std::vector<FakeEventLevelReport>, int> output_counts;
  ASSERT_OK_AND_ASSIGN(
      const uint32_t num_states,
      GetNumStates(trigger_data, event_report_windows, max_reports));
  for (int i = 0; i < num_samples; i++) {
    // Use epsilon = 0 to ensure that random data is always sampled from the RR
    // mechanism.
    ASSERT_OK_AND_ASSIGN(
        RandomizedResponseData response,
        DoRandomizedResponse(trigger_data, event_report_windows, max_reports,
                             /*epsilon=*/0, SourceType::kNavigation,
                             /*scopes_data=*/std::nullopt,
                             PrivacyMathConfig()));
    ASSERT_TRUE(response.response().has_value());
    auto [it, _] =
        output_counts.try_emplace(std::move(*response.response()), 0);
    ++it->second;
  }

  // This is the coupon collector problem (see
  // https://en.wikipedia.org/wiki/Coupon_collector%27s_problem).
  // For n possible results:
  //
  // the expected number of trials needed to see all possible results is equal
  // to n * Sum_{i = 1,..,n} 1/i.
  //
  // The variance of the number of trials is equal to
  // Sum_{i = 1,.., n} (1 - p_i) / p_i^2,
  // where p_i = (n - i + 1) / n.
  //
  // The probability that t trials are not enough to see all possible results is
  // at most n^{-t/(n*ln(n)) + 1}.
  EXPECT_EQ(output_counts.size(), num_states);

  // For any of the n possible results, the expected number of times it is seen
  // is equal to 1/n. Moreover, for any possible result, the probability that it
  // is seen more than (1+alpha)*t/n times is at most p_high = exp(- D(1/n +
  // alpha/n || 1/n) * t).
  //
  // The probability that it is seen less than (1-alpha)*t/n times is at most
  // p_low = exp(-D(1/n - alpha/n || 1/n) * t,
  //
  // where D( x || y) = x * ln(x/y) + (1-x) * ln( (1-x) / (1-y) ).
  // See
  // https://en.wikipedia.org/wiki/Chernoff_bound#Additive_form_(absolute_error)
  // for details.
  //
  // Thus, the probability that the number of occurrences of one of the results
  // deviates from its expectation by alpha*t/n is at most
  // n * (p_high + p_low).
  const int expected_counts = num_samples / static_cast<double>(num_states);
  const double abs_error = expected_counts * tolerance;
  for (const auto& [_, output_count] : output_counts) {
    EXPECT_NEAR(output_count, expected_counts, abs_error);
  }
}

TEST(PrivacyMathTest, GetRandomFakeReports_Event_MatchesExpectedDistribution) {
  // The probability that not all of the 3 states are seen after `num_samples`
  // trials is at most ~1e-14476, which is 0 for all practical purposes, so the
  // `expected_num_combinations` check should always pass.
  //
  // For the distribution check, the probability of failure with `tolerance` is
  // at most 1e-9.
  RunRandomFakeReportsTest(
      TriggerDataSet(SourceType::kEvent),
      *EventReportWindows::FromDefaults(base::Days(30), SourceType::kEvent),
      MaxEventLevelReports(1),
      /*num_samples=*/100'000,
      /*tolerance=*/0.03);
}

TEST(PrivacyMathTest,
     GetRandomFakeReports_Navigation_MatchesExpectedDistribution) {
  // The probability that not all of the 2925 states are seen after
  // `num_samples` trials is at most ~1e-19, which is 0 for all practical
  // purposes, so the `expected_num_combinations` check should always pass.
  //
  // For the distribution check, the probability of failure with `tolerance` is
  // at most .0002.
  RunRandomFakeReportsTest(TriggerDataSet(SourceType::kNavigation),
                           *EventReportWindows::FromDefaults(
                               base::Days(30), SourceType::kNavigation),
                           MaxEventLevelReports(3),
                           /*num_samples=*/150'000,
                           /*tolerance=*/0.9);
}

TEST(PrivacyMathTest, GetRandomFakeReports_Custom_MatchesExpectedDistribution) {
  // The probability that not all of the 3 states are seen after `num_samples`
  // trials is at most ~1e-14476, which is 0 for all practical purposes, so the
  // `expected_num_combinations` check should always pass.
  //
  // For the distribution check, the probability of failure with `tolerance` is
  // at most 1e-9.

  const auto kTriggerData = *TriggerDataSet::Create({
      1,
      5,
      3,
      4294967295,
  });

  const auto kEventReportWindows = *EventReportWindows::Create(
      /*start_time=*/base::Seconds(5),
      /*end_times=*/{base::Days(10), base::Days(20)});

  const MaxEventLevelReports kMaxReports(2);

  // The distribution check will fail with probability 6e-7.
  ASSERT_OK_AND_ASSIGN(
      const uint32_t num_states,
      GetNumStates(kTriggerData, kEventReportWindows, kMaxReports));
  EXPECT_EQ(45, num_states);
  RunRandomFakeReportsTest(kTriggerData, kEventReportWindows, kMaxReports,
                           /*num_samples=*/100'000,
                           /*tolerance=*/0.1);
}

const struct {
  MaxEventLevelReports max_reports;
  int num_report_windows;
  int trigger_data_cardinality;
  base::expected<uint32_t, RandomizedResponseError> expected_num_states;
} kNumStateTestCases[] = {
    {MaxEventLevelReports(3), 3, 8, 2925},
    {MaxEventLevelReports(1), 1, 2, 3},

    {MaxEventLevelReports(1), 1, 1, 2},
    {MaxEventLevelReports(5), 1, 1, 6},

    // Cases for # of states > 10000 will skip the unique check, otherwise the
    // tests won't ever finish.
    {MaxEventLevelReports(20), 5, 3, 3247943160},

    // Cases that exceed `UINT32_MAX` will return a RandomizedResponseError.
    {MaxEventLevelReports(20), 5, 32,
     base::unexpected(
         RandomizedResponseError::kExceedsTriggerStateCardinalityLimit)},
};

TEST(PrivacyMathTest, GetNumStates) {
  for (const auto& test_case : kNumStateTestCases) {
    const auto event_report_windows =
        EventReportWindowsWithCount(test_case.num_report_windows);
    const auto trigger_data =
        TriggerDataSetWithCardinality(test_case.trigger_data_cardinality);
    EXPECT_EQ(test_case.expected_num_states,
              GetNumStates(trigger_data, event_report_windows,
                           test_case.max_reports));
  }
}

TEST(PrivacyMathTest, NumStatesForTriggerDataSet_UniqueSampling) {
  for (const auto& test_case : kNumStateTestCases) {
    const auto event_report_windows =
        EventReportWindowsWithCount(test_case.num_report_windows);
    const auto trigger_data =
        TriggerDataSetWithCardinality(test_case.trigger_data_cardinality);
    ASSERT_EQ(test_case.expected_num_states,
              GetNumStates(trigger_data, event_report_windows,
                           test_case.max_reports));

    if (!test_case.expected_num_states.has_value() ||
        *test_case.expected_num_states > 10000) {
      continue;
    }

    std::set<std::vector<FakeEventLevelReport>> seen_outputs;
    for (uint32_t i = 0; i < *test_case.expected_num_states; i++) {
      if (auto output = internal::GetFakeReportsForSequenceIndex(
              trigger_data, event_report_windows, test_case.max_reports, i);
          output.has_value()) {
        seen_outputs.insert(*std::move(output));
      }
    }
    EXPECT_EQ(static_cast<size_t>(*test_case.expected_num_states),
              seen_outputs.size());
  }
}

TEST(PrivacyMathTest, NumStatesHistogramRecorded) {
  for (const auto& test_case : kNumStateTestCases) {
    base::HistogramTester histograms;
    const auto event_report_windows =
        EventReportWindowsWithCount(test_case.num_report_windows);
    const auto trigger_data =
        TriggerDataSetWithCardinality(test_case.trigger_data_cardinality);
    auto channel_capacity_response = DoRandomizedResponse(
        trigger_data, event_report_windows, test_case.max_reports,
        /*epsilon=*/14, SourceType::kNavigation,
        /*scopes_data=*/std::nullopt, PrivacyMathConfig());

    if (test_case.expected_num_states.has_value()) {
      histograms.ExpectUniqueSample(
          "Conversions.NumTriggerStates",
          *test_case.expected_num_states >= std::numeric_limits<int>::max()
              ? std::numeric_limits<int>::max() - 1
              : *test_case.expected_num_states,
          1);
    }
  }
}

// Regression test for http://crbug.com/1503728 in which the optimized
// randomized-response incorrectly returned the trigger data *index* rather than
// the trigger data *value* in the fake reports.
TEST(PrivacyMathTest, NonDefaultTriggerData) {
  // Note that the trigger data does not start at 0.
  const auto kTriggerData = *TriggerDataSet::Create({123});
  const EventReportWindows kEventReportWindows;
  const MaxEventLevelReports kMaxReports(1);

  // There are only 2 states (0 reports or 1 report with trigger data 123), so
  // loop until we hit the non-empty case.

  RandomizedResponse response;
  do {
    ASSERT_OK_AND_ASSIGN(
        RandomizedResponseData response_data,
        DoRandomizedResponse(kTriggerData, kEventReportWindows, kMaxReports,
                             /*epsilon=*/0, SourceType::kNavigation,
                             /*scopes_data=*/std::nullopt,
                             PrivacyMathConfig()));
    response = std::move(response_data.response());
  } while (!response.has_value() || response->empty());

  ASSERT_EQ(uint64_t{123u}, response->front().trigger_data);
}

TEST(PrivacyMathTest, RandomizedResponse_ExceedsChannelCapacity) {
  constexpr PrivacyMathConfig kConfig{.max_channel_capacity_navigation = 1};

  auto channel_capacity_response =
      DoRandomizedResponse(TriggerDataSet(SourceType::kNavigation),
                           EventReportWindows(), MaxEventLevelReports(1),
                           /*epsilon=*/14, SourceType::kNavigation,
                           /*scopes_data=*/std::nullopt, kConfig);

  EXPECT_THAT(channel_capacity_response,
              ErrorIs(RandomizedResponseError::kExceedsChannelCapacityLimit));
}

TEST(PrivacyMathTest, RandomizedResponse_ExceedsScopesChannelCapacity) {
  // Navigation
  auto channel_capacity_response = DoRandomizedResponse(
      TriggerDataSet(SourceType::kNavigation), EventReportWindows(),
      MaxEventLevelReports(1),
      /*epsilon=*/14, SourceType::kNavigation,
      AttributionScopesData::Create(AttributionScopesSet({"1"}),
                                    /*attribution_scope_limit=*/100u,
                                    /*max_event_states=*/100u),
      PrivacyMathConfig());

  EXPECT_THAT(
      channel_capacity_response,
      ErrorIs(RandomizedResponseError::kExceedsScopesChannelCapacityLimit));

  // Event
  channel_capacity_response = DoRandomizedResponse(
      TriggerDataSet(SourceType::kEvent), EventReportWindows(),
      MaxEventLevelReports(1),
      /*epsilon=*/14, SourceType::kEvent,
      AttributionScopesData::Create(AttributionScopesSet({"1"}),
                                    /*attribution_scope_limit=*/100u,
                                    /*max_event_states=*/3u),
      PrivacyMathConfig());

  EXPECT_THAT(
      channel_capacity_response,
      ErrorIs(RandomizedResponseError::kExceedsScopesChannelCapacityLimit));
}

// Regression test for http://crbug.com/1504144 in which empty trigger data
// causes an invalid iterator dereference and thus a crash.
TEST(PrivacyMathTest, UnaryChannel) {
  const struct {
    const char* desc;
    TriggerDataSet trigger_data;
    MaxEventLevelReports max_reports;
  } kTestCases[] = {
      {
          .desc = "empty-trigger-data",
          .max_reports = MaxEventLevelReports(20),
      },
      {
          .desc = "zero-max-reports",
          .trigger_data = TriggerDataSet(SourceType::kNavigation),
          .max_reports = MaxEventLevelReports(0),
      },
  };

  for (const auto& test_case : kTestCases) {
    SCOPED_TRACE(test_case.desc);

    ASSERT_OK_AND_ASSIGN(
        const uint32_t num_states,
        GetNumStates(test_case.trigger_data, EventReportWindows(),
                     test_case.max_reports));
    EXPECT_EQ(1u, num_states);

    EXPECT_EQ(RandomizedResponseData(
                  /*rate=*/1,
                  /*response=*/std::vector<FakeEventLevelReport>()),
              DoRandomizedResponse(test_case.trigger_data, EventReportWindows(),
                                   test_case.max_reports,
                                   /*epsilon=*/0, SourceType::kNavigation,
                                   /*scopes_data=*/std::nullopt,
                                   PrivacyMathConfig()));
  }
}

TEST(PrivacyMathTest, IsValid) {
  const TriggerDataSet kTriggerData(SourceType::kNavigation);
  const auto kEventReportWindows = *EventReportWindows::FromDefaults(
      base::Days(30), SourceType::kNavigation);
  const MaxEventLevelReports kMaxReports(1);

  const struct {
    const char* desc;
    RandomizedResponse response;
    bool expected;
  } kTestCases[] = {
      {
          "null",
          std::nullopt,
          true,
      },
      {
          "non_null",
          std::vector<FakeEventLevelReport>{
              {
                  .trigger_data = 5,
                  .window_index = 1,
              },
          },
          true,
      },
      {
          "too_many_reports",
          std::vector<FakeEventLevelReport>{
              {
                  .trigger_data = 0,
                  .window_index = 0,
              },
              {
                  .trigger_data = 0,
                  .window_index = 0,
              },
          },
          false,
      },
      {
          "invalid_trigger_data",
          std::vector<FakeEventLevelReport>{
              {
                  .trigger_data = 8,
                  .window_index = 0,
              },
          },
          false,
      },
      {
          "window_index_too_large",
          std::vector<FakeEventLevelReport>{
              {
                  .trigger_data = 0,
                  .window_index = 3,
              },
          },
          false,
      },
      {
          "window_index_negative",
          std::vector<FakeEventLevelReport>{
              {
                  .trigger_data = 0,
                  .window_index = -1,
              },
          },
          false,
      },
  };

  for (const auto& test_case : kTestCases) {
    SCOPED_TRACE(test_case.desc);
    EXPECT_EQ(test_case.expected, IsValid(test_case.response, kTriggerData,
                                          kEventReportWindows, kMaxReports));
  }
}

void GetKCombinationAtIndexDoesNotCrash(uint32_t combination_index,
                                        uint32_t k) {
  internal::GetKCombinationAtIndex(combination_index, k);
}

FUZZ_TEST(PrivacyMathTest, GetKCombinationAtIndexDoesNotCrash)
    .WithDomains(
        /*combination_index=*/fuzztest::Arbitrary<uint32_t>(),
        /*k=*/fuzztest::InRange<uint32_t>(0, 20));

void GetNumStatesDoesNotCrash(
    const int trigger_data_cardinality,
    const int num_report_windows,
    const MaxEventLevelReports max_event_level_reports) {
  std::ignore = GetNumStates(
      TriggerDataSetWithCardinality(trigger_data_cardinality),
      EventReportWindowsWithCount(num_report_windows), max_event_level_reports);
}

FUZZ_TEST(PrivacyMathTest, GetNumStatesDoesNotCrash)
    .WithDomains(fuzztest::InRange<int>(0, kMaxTriggerDataPerSource),
                 fuzztest::InRange<int>(1, kMaxEventLevelReportWindows),
                 AnyMaxEventLevelReports());

}  // namespace
}  // namespace attribution_reporting