chromium/chromeos/ash/components/network/traffic_counters_handler.cc

// 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 "chromeos/ash/components/network/traffic_counters_handler.h"

#include <memory>
#include <string>

#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_metadata_store.h"

namespace ash {

namespace {

// Interval duration to determine the auto reset check frequency.
constexpr base::TimeDelta kResetCheckInterval = base::Hours(6);

// Default reset day assigned to a network when the reset day has not been set.
constexpr int kDefaultResetDay = 1;

// Stores the number of bytes per MB for UMA purposes.
constexpr uint64_t kBytesPerMb = 1024 * 1024;

// Holds an unowned pointer the Singleton instance of this class.
traffic_counters::TrafficCountersHandler* g_traffic_counters_handler = nullptr;

// Gets the last day of the month when the user specified reset day
// exceeds the number of day in that month. For example, if the user specified
// reset day is 31, this function would ensure that the returned time would
// represent Feb 28 (non-leap year), Apr 30, etc.
base::Time GetValidTime(base::Time::Exploded exploded_time) {
  base::Time time;
  while (!base::Time::FromLocalExploded(exploded_time, &time)) {
    if (exploded_time.day_of_month > 28) {
      --exploded_time.day_of_month;
    } else {
      break;
    }
  }
  return time;
}

// Calculates when the traffic counters were expected to be reset last month.
base::Time CalculateLastMonthResetTime(base::Time::Exploded exploded,
                                       int user_specified_reset_day) {
  exploded.month -= 1;
  if (exploded.month < 1) {
    exploded.month = 12;
    exploded.year -= 1;
  }

  exploded.day_of_month = user_specified_reset_day;

  return GetValidTime(exploded);
}

// Calculates when the traffic counters were/are expected to be reset this
// month.
base::Time CalculateCurrentMonthResetTime(base::Time::Exploded exploded,
                                          int user_specified_reset_day) {
  exploded.day_of_month = user_specified_reset_day;

  return GetValidTime(exploded);
}

// To avoid discrepancies between different times of the same day, set all times
// to 12:00:00 AM. This is safe to do so because traffic counters will never be
// automatically reset more than once on any given day.
void AdjustExplodedTimeValues(base::Time::Exploded* exploded_time) {
  exploded_time->hour = 0;
  exploded_time->minute = 0;
  exploded_time->second = 0;
  exploded_time->millisecond = 0;
}

// Returns a string representing of the network technology type.
std::string GetNetworkTechnologyString(
    NetworkState::NetworkTechnologyType type) {
  switch (type) {
    case NetworkState::NetworkTechnologyType::kCellular:
      return "Cellular";
    case NetworkState::NetworkTechnologyType::kEthernet:
      return "Ethernet";
    case NetworkState::NetworkTechnologyType::kEthernetEap:
      return "EthernetEap";
    case NetworkState::NetworkTechnologyType::kWiFi:
      return "WiFi";
    case NetworkState::NetworkTechnologyType::kTether:
      return "Tether";
    case NetworkState::NetworkTechnologyType::kVPN:
      return "VPN";
    case NetworkState::NetworkTechnologyType::kUnknown:
      return "Unknown";
  }
}

// Since rx_bytes and tx_bytes may be larger than the maximum value
// representable by uint32_t, we must check whether it was implicitly converted
// to a double during D-Bus deserialization.
uint64_t GetBytes(const base::Value::Dict& tc_dict, const std::string& key) {
  uint64_t bytes = 0;
  if (const base::Value* const value = tc_dict.Find(key)) {
    if (value->is_int()) {
      bytes = value->GetInt();
    } else if (value->is_double()) {
      bytes = std::floor(value->GetDouble());
    } else {
      NET_LOG(ERROR) << "Unexpected type " << value->type() << " for " << key;
    }
  } else {
    NET_LOG(ERROR) << "Missing field: " << key;
  }
  return bytes;
}

// Checks whether traffic counters operations (like ResetTrafficCounters) are
// allowed for the network specified by |service_path|.
bool AreTrafficCounterOperationsAllowed(const std::string& service_path) {
  const NetworkState* network_state =
      NetworkHandler::Get()->network_state_handler()->GetNetworkState(
          service_path);

  if (!network_state) {
    NET_LOG(ERROR) << "AreTrafficCounterOperationsAllowed for network "
                   << NetworkPathId(service_path)
                   << " failed: network state is null";
    return false;
  }

  auto network_technology = network_state->GetNetworkTechnologyType();

  if (network_technology != NetworkState::NetworkTechnologyType::kCellular &&
      network_technology != NetworkState::NetworkTechnologyType::kWiFi) {
    NET_LOG(ERROR) << "AreTrafficCounterOperationsAllowed for network "
                   << NetworkPathId(service_path)
                   << " failed: unexpected network technology type: "
                   << GetNetworkTechnologyString(network_technology);
    return false;
  }

  if (network_technology == NetworkState::NetworkTechnologyType::kWiFi &&
      !features::IsTrafficCountersForWiFiTestingEnabled()) {
    NET_LOG(ERROR) << "AreTrafficCounterOperationsAllowed for network "
                   << NetworkPathId(service_path)
                   << " failed: traffic_counters for WiFi disabled";
    return false;
  }

  return true;
}

}  // namespace

namespace traffic_counters {

// static
void TrafficCountersHandler::Initialize() {
  CHECK(!g_traffic_counters_handler);
  g_traffic_counters_handler = new TrafficCountersHandler();
  g_traffic_counters_handler->StartAutoReset();
}

// static
void TrafficCountersHandler::Shutdown() {
  CHECK(g_traffic_counters_handler);
  delete g_traffic_counters_handler;
  g_traffic_counters_handler = nullptr;
}

// static
TrafficCountersHandler* TrafficCountersHandler::Get() {
  CHECK(g_traffic_counters_handler)
      << "TrafficCountersHandler::Get() called before Initialize()";
  return g_traffic_counters_handler;
}

// static
void TrafficCountersHandler::InitializeForTesting() {
  CHECK(!g_traffic_counters_handler);
  // Note that unlike Initialize(), this function does not call
  // StartAutoReset(). This allows test properties to be set before the test
  // runs. Call StartAutoResetForTesting() after the test properties have been
  // set.
  g_traffic_counters_handler = new TrafficCountersHandler();
}

// static
void TrafficCountersHandler::StartAutoResetForTesting() {
  CHECK(g_traffic_counters_handler)
      << "TrafficCountersHandler::StartAutoResetForTesting() called before "
         "Initialize()";
  StartAutoReset();
}

// static
bool TrafficCountersHandler::IsInitialized() {
  return g_traffic_counters_handler != nullptr;
}

TrafficCountersHandler::TrafficCountersHandler()
    : time_getter_(base::BindRepeating([]() { return base::Time::Now(); })),
      timer_(std::make_unique<base::RepeatingTimer>()) {
  CHECK(features::IsTrafficCountersEnabled());
}

TrafficCountersHandler::~TrafficCountersHandler() = default;

void TrafficCountersHandler::StartAutoReset() {
  RunAutoResetTrafficCountersForActiveNetworks();
  timer_->Start(
      FROM_HERE, kResetCheckInterval, this,
      &TrafficCountersHandler::RunAutoResetTrafficCountersForActiveNetworks);
}

void TrafficCountersHandler::RequestTrafficCounters(
    const std::string& service_path,
    RequestTrafficCountersCallback callback) {
  if (!AreTrafficCounterOperationsAllowed(service_path)) {
    NET_LOG(ERROR) << "RequestTrafficCounters for network "
                   << NetworkPathId(service_path) << " failed";
    std::move(callback).Run(std::nullopt);
    return;
  }
  NetworkHandler::Get()->network_state_handler()->RequestTrafficCounters(
      service_path, std::move(callback));
}

void TrafficCountersHandler::ResetTrafficCounters(
    const std::string& service_path) {
  if (!AreTrafficCounterOperationsAllowed(service_path)) {
    NET_LOG(ERROR) << "ResetTrafficCounters for network "
                   << NetworkPathId(service_path) << " failed";
    return;
  }
  RequestTrafficCounters(
      service_path,
      base::BindOnce(
          &TrafficCountersHandler::
              OnTrafficCountersRequestedForTrackingDailyAverageDataUsage,
          weak_ptr_factory_.GetWeakPtr(), service_path));
}

void TrafficCountersHandler::
    OnTrafficCountersRequestedForTrackingDailyAverageDataUsage(
        const std::string& service_path,
        std::optional<base::Value> traffic_counters) {
  double total_data_usage = 0;
  if (!traffic_counters || !traffic_counters->is_list() ||
      !traffic_counters->GetList().size()) {
    NET_LOG(ERROR) << "Failed to get traffic counters for tracking daily "
                   << "average for network:" << NetworkPathId(service_path);
    NetworkHandler::Get()
        ->managed_network_configuration_handler()
        ->GetManagedProperties(
            LoginState::Get()->primary_user_hash(), service_path,
            base::BindOnce(
                &TrafficCountersHandler::OnGetManagedPropertiesForLastResetTime,
                weak_ptr_factory_.GetWeakPtr(), /*total_data_usage=*/0.0));
    return;
  }
  for (const base::Value& tc : traffic_counters->GetList()) {
    DCHECK(tc.is_dict());

    const base::Value::Dict& tc_dict = tc.GetDict();
    total_data_usage += GetBytes(tc_dict, "rx_bytes");
    total_data_usage += GetBytes(tc_dict, "tx_bytes");
  }
  NET_LOG(EVENT) << "Total data usage for network "
                 << NetworkPathId(service_path) << ": " << total_data_usage;
  NetworkHandler::Get()
      ->managed_network_configuration_handler()
      ->GetManagedProperties(
          LoginState::Get()->primary_user_hash(), service_path,
          base::BindOnce(
              &TrafficCountersHandler::OnGetManagedPropertiesForLastResetTime,
              weak_ptr_factory_.GetWeakPtr(), total_data_usage));
}

void TrafficCountersHandler::OnGetManagedPropertiesForLastResetTime(
    double total_data_usage,
    const std::string& service_path,
    std::optional<base::Value::Dict> properties,
    std::optional<std::string> error) {
  // Since last reset time has already been retrieved (via
  // GetManagedProperties), the network's traffic counters can be reset and the
  // last reset time can be modified in the platform.
  NetworkHandler::Get()->network_state_handler()->ResetTrafficCounters(
      service_path);
  if (!properties) {
    NET_LOG(ERROR) << "GetManagedProperties failed for: "
                   << NetworkPathId(service_path)
                   << " Error: " << error.value_or("Failed");
    return;
  }
  const std::optional<double> last_reset =
      properties->FindDouble(::onc::network_config::kTrafficCounterResetTime);
  const NetworkState* network_state =
      NetworkHandler::Get()->network_state_handler()->GetNetworkState(
          service_path);
  if (network_state == nullptr) {
    NET_LOG(ERROR) << "Failed to retrieve NetworkState (and Technology type) "
                      "during ResetTrafficCounters for "
                   << NetworkPathId(service_path);
    return;
  }
  const std::string network_technology =
      GetNetworkTechnologyString(network_state->GetNetworkTechnologyType());
  if (!last_reset.has_value()) {
    // Any network that does not have a last reset time was reset during the
    // first TrafficCountersHandler run. So, a failure to find the last reset
    // time of this network is unexpected.
    NET_LOG(ERROR) << "Failed to retrieve last rest time for network: "
                   << NetworkPathId(service_path);

    base::UmaHistogramMemoryLargeMB("Network.TrafficCounters." +
                                        network_technology +
                                        ".DataUsageNoLastResetTime",
                                    (total_data_usage / kBytesPerMb));
    return;
  }

  base::Time last_reset_time = base::Time::FromDeltaSinceWindowsEpoch(
      base::Milliseconds(last_reset.value()));
  base::Time current_time = time_getter_.Run();
  int total_days_since_last_reset =
      (current_time - last_reset_time).InDays() + 1;
  base::UmaHistogramMemoryLargeMB(
      "Network.TrafficCounters." + network_technology +
          ".AverageDailyDataUsage",
      (total_data_usage / kBytesPerMb) / total_days_since_last_reset);
}

void TrafficCountersHandler::SetTrafficCountersResetDay(
    const std::string& guid,
    uint32_t day,
    SetTrafficCountersResetDayCallback callback) {
  const NetworkState* network_state =
      NetworkHandler::Get()->network_state_handler()->GetNetworkStateFromGuid(
          guid);
  CHECK(network_state);
  if (!AreTrafficCounterOperationsAllowed(network_state->path())) {
    NET_LOG(ERROR) << "SetTrafficCountersResetDay for network "
                   << NetworkGuidId(guid) << " failed";
    std::move(callback).Run(/*success=*/false);
    return;
  }
  if (day < 1 || day > 31) {
    NET_LOG(ERROR) << "Failed to set reset day " << day << " for "
                   << NetworkGuidId(guid)
                   << ": day must be between 1 and 31 (inclusive)";
    std::move(callback).Run(/*success=*/false);
    return;
  }
  NetworkHandler::Get()
      ->network_metadata_store()
      ->SetDayOfTrafficCountersAutoReset(guid, day);
  std::move(callback).Run(/*success=*/true);
}

uint32_t TrafficCountersHandler::GetUserSpecifiedResetDay(
    const std::string& guid) {
  const base::Value* reset_day_ptr =
      NetworkHandler::Get()
          ->network_metadata_store()
          ->GetDayOfTrafficCountersAutoReset(guid);
  if (!reset_day_ptr) {
    NET_LOG(ERROR) << "Failed to get auto reset day for network: "
                   << NetworkGuidId(guid);
    return kDefaultResetDay;
  }
  CHECK(reset_day_ptr) << "Reset day found for guid: " << NetworkGuidId(guid)
                       << ", value: " << reset_day_ptr->GetInt();

  return reset_day_ptr->GetInt();
}

void TrafficCountersHandler::RunAutoResetTrafficCountersForActiveNetworks() {
  NET_LOG(EVENT)
      << "Starting RunAutoResetTrafficCountersForActiveNetworks() at: "
      << time_getter_.Run();
  // Retrieve list of currently active networks.
  NetworkStateHandler::NetworkStateList active_network_states;
  auto network_type_pattern = NetworkTypePattern::Cellular();
  if (features::IsTrafficCountersForWiFiTestingEnabled()) {
    network_type_pattern = network_type_pattern | NetworkTypePattern::WiFi();
  }
  NetworkHandler::Get()->network_state_handler()->GetActiveNetworkListByType(
      network_type_pattern, &active_network_states);
  NET_LOG(EVENT) << "TrafficCountersHandler found "
                 << active_network_states.size() << " active networks";
  for (const auto& network : active_network_states) {
    NET_LOG(EVENT) << "Retrieving managed network configuration properties for "
                      "network "
                   << NetworkGuidId(network->guid());
    const std::string service_path = network->path();
    NetworkHandler::Get()
        ->managed_network_configuration_handler()
        ->GetManagedProperties(
            LoginState::Get()->primary_user_hash(), service_path,
            base::BindOnce(
                &TrafficCountersHandler::OnGetManagedPropertiesForAutoReset,
                weak_ptr_factory_.GetWeakPtr(), network->guid()));
  }
}

void TrafficCountersHandler::OnGetManagedPropertiesForAutoReset(
    std::string guid,
    const std::string& service_path,
    std::optional<base::Value::Dict> properties,
    std::optional<std::string> error) {
  if (!properties) {
    NET_LOG(ERROR) << "GetManagedProperties failed for: " << NetworkGuidId(guid)
                   << " Error: " << error.value_or("Failed");
    return;
  }
  const std::optional<double> last_reset =
      properties->FindDouble(::onc::network_config::kTrafficCounterResetTime);
  // The last reset time for a network should only be unset once
  // during the first traffic counters run for a "new" network.
  if (!last_reset.has_value()) {
    // No last reset time, trigger an initial reset.
    NET_LOG(EVENT) << "Resetting traffic counters for network: "
                   << NetworkGuidId(guid);
    ResetTrafficCounters(service_path);
    return;
  }

  const base::Value* reset_day_ptr =
      NetworkHandler::Get()
          ->network_metadata_store()
          ->GetDayOfTrafficCountersAutoReset(guid);
  // The user specified reset day for a network should only be unset once
  // during the first traffic counters run for a "new" network.
  if (!reset_day_ptr) {
    NET_LOG(EVENT) << "Failed to retrieve auto reset day for network "
                   << NetworkGuidId(guid) << ", setting auto reset day to "
                   << kDefaultResetDay << " and resetting traffic counters "
                   << "for the network";
    ResetTrafficCounters(service_path);
    NetworkHandler::Get()
        ->network_metadata_store()
        ->SetDayOfTrafficCountersAutoReset(guid, kDefaultResetDay);
    return;
  }

  auto user_specified_reset_day = reset_day_ptr->GetInt();
  NET_LOG(EVENT) << "The user specified reset day for network "
                 << NetworkGuidId(guid) << " is " << user_specified_reset_day;

  base::Time::Exploded current_time_exploded = CurrentDateExploded();

  base::Time last_month_reset = CalculateLastMonthResetTime(
      current_time_exploded, user_specified_reset_day);
  base::Time last_reset_time = base::Time::FromDeltaSinceWindowsEpoch(
      base::Milliseconds(last_reset.value()));
  // If the last time traffic counters were reset (last_reset_time) was before
  // the time traffic counters were expected to be reset last month
  // (last_month_reset), then the traffic counters should be reset. This handles
  // the case where the traffic counters feature was disabled for over a month
  // and then re-enabled.
  if (last_reset_time < last_month_reset) {
    NET_LOG(EVENT) << "Resetting traffic counters since " << last_reset_time
                   << " < " << last_month_reset;
    ResetTrafficCounters(service_path);
    return;
  }

  base::Time curr_month_reset = CalculateCurrentMonthResetTime(
      current_time_exploded, user_specified_reset_day);
  base::Time current_date = GetValidTime(current_time_exploded);
  // If the last time traffic counters were reset (last_reset_time) was before
  // the expected reset date on this month (curr_month_resset), and today
  // (current_date) is equal to or greater than the expected date of reset for
  // this month (curr_month_reset), then reset the counters. For example, let's
  // assume that traffic counters are to be reset on the 5th of every month and
  // were last reset on January 5th. If the current_date is between February
  // 1st-February 4th, then current_date (e.g, Feb 3rd) < curr_month_reset (Feb
  // 5th), so the counters are not reset. However, if the current date is Feb
  // 5th onwards, then current_date (Feb 5th) = curr_month_reset (Feb 5th), so
  // traffic counters are reset.
  if (last_reset_time < curr_month_reset && current_date >= curr_month_reset) {
    NET_LOG(EVENT) << "Resetting traffic counters since " << last_reset_time
                   << " < " << curr_month_reset << " && " << current_date
                   << " >= " << curr_month_reset;
    ResetTrafficCounters(service_path);
  }
}

base::Time::Exploded TrafficCountersHandler::CurrentDateExploded() {
  base::Time::Exploded current_time_exploded;
  time_getter_.Run().LocalExplode(&current_time_exploded);
  AdjustExplodedTimeValues(&current_time_exploded);

  return current_time_exploded;
}

void TrafficCountersHandler::RunForTesting() {
  RunAutoResetTrafficCountersForActiveNetworks();
}

void TrafficCountersHandler::SetTimeGetterForTesting(TimeGetter time_getter) {
  time_getter_ = std::move(time_getter);
}

}  // namespace traffic_counters

}  // namespace ash