910e62b5创建于 1月15日历史提交
// Copyright 2023 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/event_report_windows.h"

#include <stddef.h>

#include <algorithm>
#include <functional>
#include <iterator>
#include <optional>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_set.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "base/values.h"
#include "components/attribution_reporting/constants.h"
#include "components/attribution_reporting/parsing_utils.h"
#include "components/attribution_reporting/source_registration_error.mojom-shared.h"
#include "components/attribution_reporting/source_type.mojom-shared.h"

namespace attribution_reporting {

namespace {

using ::attribution_reporting::mojom::SourceRegistrationError;
using ::attribution_reporting::mojom::SourceType;

bool IsValid(base::TimeDelta start_time,
             const base::flat_set<base::TimeDelta>& end_times) {
  return !start_time.is_negative() && !end_times.empty() &&
         end_times.size() <= kMaxEventLevelReportWindows &&
         *end_times.begin() > start_time &&
         *end_times.begin() >= kMinReportWindow;
}

bool IsReportWindowValid(base::TimeDelta report_window) {
  return report_window >= kMinReportWindow && report_window <= kMaxSourceExpiry;
}

bool IsStrictlyIncreasing(const std::vector<base::TimeDelta>& end_times) {
  return std::ranges::adjacent_find(end_times, std::not_fn(std::less{})) ==
         end_times.end();
}

base::Time ReportTimeFromDeadline(base::Time source_time,
                                  base::TimeDelta deadline) {
  // Valid conversion reports should always have a valid reporting deadline.
  CHECK(deadline.is_positive());
  return source_time + deadline;
}

}  // namespace

// static
std::optional<EventReportWindows> EventReportWindows::FromDefaults(
    base::TimeDelta report_window,
    SourceType source_type) {
  if (!IsReportWindowValid(report_window)) {
    return std::nullopt;
  }
  return EventReportWindows(report_window, source_type);
}

// static
std::optional<EventReportWindows> EventReportWindows::Create(
    base::TimeDelta start_time,
    std::vector<base::TimeDelta> end_times) {
  if (!IsStrictlyIncreasing(end_times)) {
    return std::nullopt;
  }
  base::flat_set<base::TimeDelta> end_times_set(base::sorted_unique,
                                                std::move(end_times));
  if (!IsValid(start_time, end_times_set)) {
    return std::nullopt;
  }
  return EventReportWindows(start_time, std::move(end_times_set));
}

EventReportWindows::EventReportWindows(
    base::TimeDelta start_time,
    base::flat_set<base::TimeDelta> end_times)
    : start_time_(start_time), end_times_(std::move(end_times)) {
  CHECK(IsValid(start_time_, end_times_));
}

EventReportWindows::EventReportWindows(base::TimeDelta report_window,
                                       SourceType source_type) {
  CHECK(IsReportWindowValid(report_window));

  std::vector<base::TimeDelta> end_times;
  switch (source_type) {
    case SourceType::kNavigation:
      end_times = {base::Days(2), base::Days(7)};
      break;
    case SourceType::kEvent:
      break;
  }

  while (!end_times.empty() && report_window <= end_times.back()) {
    end_times.pop_back();
  }

  end_times.push_back(report_window);
  end_times_.replace(std::move(end_times));
  CHECK(IsValid(start_time_, end_times_));
}

EventReportWindows::EventReportWindows()
    : EventReportWindows(/*start_time=*/base::TimeDelta(),
                         /*end_times=*/{kMaxSourceExpiry}) {}

EventReportWindows::~EventReportWindows() = default;

EventReportWindows::EventReportWindows(const EventReportWindows&) = default;

EventReportWindows& EventReportWindows::operator=(const EventReportWindows&) =
    default;

EventReportWindows::EventReportWindows(EventReportWindows&&) = default;

EventReportWindows& EventReportWindows::operator=(EventReportWindows&&) =
    default;

// Follows the steps detailed in
// https://wicg.github.io/attribution-reporting-api/#obtain-an-event-level-report-delivery-time
// Starting from step 2.
base::Time EventReportWindows::ComputeReportTime(
    base::Time source_time,
    base::Time trigger_time) const {
  // It is possible for a source to have an assigned time of T and a trigger
  // that is attributed to it to have a time of T-X e.g. due to user-initiated
  // clock changes.
  //
  // TODO(crbug.com/40282914): Assume `source_time` is smaller than
  // `trigger_time` once attribution time resolution is implemented in storage.
  const base::Time trigger_time_floored =
      source_time < trigger_time ? trigger_time
                                 : source_time + base::Microseconds(1);
  base::TimeDelta reporting_window_to_use = *end_times_.rbegin();

  for (base::TimeDelta reporting_window : end_times_) {
    if (source_time + reporting_window <= trigger_time_floored) {
      continue;
    }
    reporting_window_to_use = reporting_window;
    break;
  }
  return ReportTimeFromDeadline(source_time, reporting_window_to_use);
}

base::Time EventReportWindows::ReportTimeAtWindow(base::Time source_time,
                                                  int window_index) const {
  CHECK_GE(window_index, 0);
  CHECK_LT(static_cast<size_t>(window_index), end_times_.size());

  return ReportTimeFromDeadline(source_time,
                                *std::next(end_times_.begin(), window_index));
}

base::Time EventReportWindows::StartTimeAtWindow(base::Time source_time,
                                                 int window_index) const {
  CHECK_GE(window_index, 0);
  CHECK_LT(static_cast<size_t>(window_index), end_times_.size());

  if (window_index == 0) {
    return source_time + start_time_;
  }

  return source_time + *std::next(end_times_.begin(), window_index - 1);
}

EventReportWindows::WindowResult EventReportWindows::FallsWithin(
    base::TimeDelta trigger_moment) const {
  // It is possible for a source to have an assigned time of T and a trigger
  // that is attributed to it to have a time of T-X e.g. due to user-initiated
  // clock changes.
  //
  // TODO(crbug.com/40283992): Assume trigger moment is not negative once
  // attribution time resolution is implemented in storage.
  base::TimeDelta bounded_trigger_moment =
      trigger_moment.is_negative() ? base::Microseconds(0) : trigger_moment;

  if (bounded_trigger_moment < start_time_) {
    return WindowResult::kNotStarted;
  }
  if (bounded_trigger_moment >= *end_times_.rbegin()) {
    return WindowResult::kPassed;
  }
  return WindowResult::kFallsWithin;
}

base::expected<EventReportWindows, SourceRegistrationError>
EventReportWindows::FromJSON(const base::Value::Dict& registration,
                             base::TimeDelta expiry,
                             SourceType source_type) {
  const base::Value* singular_window = registration.Find(kEventReportWindow);
  const base::Value* multiple_windows = registration.Find(kEventReportWindows);

  if (singular_window && multiple_windows) {
    return base::unexpected(
        SourceRegistrationError::kBothEventReportWindowFieldsFound);
  } else if (singular_window) {
    ASSIGN_OR_RETURN(
        base::TimeDelta report_window,
        ParseLegacyDuration(*singular_window,
                            /*clamp_min=*/kMinReportWindow,
                            /*clamp_max=*/expiry),
        [](ParseError) {
          return SourceRegistrationError::kEventReportWindowValueInvalid;
        });

    return EventReportWindows(report_window, source_type);
  } else if (!multiple_windows) {
    return EventReportWindows(expiry, source_type);
  }

  const base::Value::Dict* dict = multiple_windows->GetIfDict();
  if (!dict) {
    return base::unexpected(
        SourceRegistrationError::kEventReportWindowsWrongType);
  }

  base::TimeDelta start_time = base::Seconds(0);
  if (const base::Value* start_time_value = dict->Find(kStartTime)) {
    ASSIGN_OR_RETURN(
        int int_value, ParseInt(*start_time_value), [](ParseError) {
          return SourceRegistrationError::kEventReportWindowsStartTimeInvalid;
        });
    start_time = base::Seconds(int_value);
    if (start_time.is_negative() || start_time > expiry) {
      return base::unexpected(
          SourceRegistrationError::kEventReportWindowsStartTimeInvalid);
    }
  }

  const base::Value* end_times_value = dict->Find(kEndTimes);
  if (!end_times_value) {
    return base::unexpected(
        SourceRegistrationError::kEventReportWindowsEndTimesMissing);
  }

  const base::Value::List* end_times_list = end_times_value->GetIfList();
  if (!end_times_list || end_times_list->empty() ||
      end_times_list->size() > kMaxEventLevelReportWindows) {
    return base::unexpected(
        SourceRegistrationError::kEventReportWindowsEndTimesListInvalid);
  }

  std::vector<base::TimeDelta> end_times;
  end_times.reserve(end_times_list->size());

  base::TimeDelta start_duration = start_time;
  for (const auto& item : *end_times_list) {
    ASSIGN_OR_RETURN(base::TimeDelta end_time, ParseDuration(item),
                     [](ParseError) {
                       return SourceRegistrationError::
                           kEventReportWindowsEndTimeValueInvalid;
                     });

    if (!end_time.is_positive()) {
      return base::unexpected(
          SourceRegistrationError::kEventReportWindowsEndTimeValueInvalid);
    }

    if (end_time > expiry) {
      end_time = expiry;
    }
    if (end_time < kMinReportWindow) {
      end_time = kMinReportWindow;
    }

    if (end_time <= start_duration) {
      return base::unexpected(
          SourceRegistrationError::kEventReportWindowsEndTimeDurationLTEStart);
    }
    end_times.push_back(end_time);
    start_duration = end_time;
  }

  return EventReportWindows(start_time, std::move(end_times));
}

void EventReportWindows::Serialize(base::Value::Dict& dict) const {
  base::Value::Dict windows_dict;

  windows_dict.Set(kStartTime, static_cast<int>(start_time_.InSeconds()));

  auto list = base::Value::List::with_capacity(end_times_.size());
  for (const auto& end_time : end_times_) {
    list.Append(static_cast<int>(end_time.InSeconds()));
  }

  windows_dict.Set(kEndTimes, std::move(list));
  dict.Set(kEventReportWindows, std::move(windows_dict));
}

bool EventReportWindows::IsValidForExpiry(base::TimeDelta expiry) const {
  return start_time_ <= expiry && *end_times_.rbegin() <= expiry;
}

}  // namespace attribution_reporting