// Copyright 2014 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/metrics/net/network_metrics_provider.h"

#include <stdint.h>

#include <algorithm>
#include <string>
#include <utility>

#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "net/base/net_errors.h"
#include "net/nqe/effective_connection_type_observer.h"
#include "net/nqe/network_quality_estimator.h"

#if BUILDFLAG(IS_ANDROID)
#include "services/network/public/cpp/network_connection_tracker.h"
#endif

namespace metrics {

SystemProfileProto::Network::EffectiveConnectionType
ConvertEffectiveConnectionType(
    net::EffectiveConnectionType effective_connection_type) {
  switch (effective_connection_type) {
    case net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN:
      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
    case net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G:
      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G;
    case net::EFFECTIVE_CONNECTION_TYPE_2G:
      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G;
    case net::EFFECTIVE_CONNECTION_TYPE_3G:
      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_3G;
    case net::EFFECTIVE_CONNECTION_TYPE_4G:
      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_4G;
    case net::EFFECTIVE_CONNECTION_TYPE_OFFLINE:
      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_OFFLINE;
    case net::EFFECTIVE_CONNECTION_TYPE_LAST:
      NOTREACHED();
      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
  }
  NOTREACHED();
  return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
}

NetworkMetricsProvider::NetworkMetricsProvider(
    network::NetworkConnectionTrackerAsyncGetter
        network_connection_tracker_async_getter,
    std::unique_ptr<NetworkQualityEstimatorProvider>
        network_quality_estimator_provider)
    : network_connection_tracker_(nullptr),
      connection_type_is_ambiguous_(false),
      connection_type_(network::mojom::ConnectionType::CONNECTION_UNKNOWN),
      network_connection_tracker_initialized_(false),
      wifi_phy_layer_protocol_is_ambiguous_(false),
      wifi_phy_layer_protocol_(net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN),
      network_quality_estimator_provider_(
          std::move(network_quality_estimator_provider)),
      effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
      min_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
      max_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
  network_connection_tracker_async_getter.Run(
      base::BindOnce(&NetworkMetricsProvider::SetNetworkConnectionTracker,
                     weak_ptr_factory_.GetWeakPtr()));
  ProbeWifiPHYLayerProtocol();

  if (network_quality_estimator_provider_) {
    // Use |network_quality_estimator_provider_| to get network quality
    // tracker.
    network_quality_estimator_provider_->PostReplyOnNetworkQualityChanged(
        base::BindRepeating(
            &NetworkMetricsProvider::OnEffectiveConnectionTypeChanged,
            weak_ptr_factory_.GetWeakPtr()));
  }
}

NetworkMetricsProvider::~NetworkMetricsProvider() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (network_connection_tracker_)
    network_connection_tracker_->RemoveNetworkConnectionObserver(this);
}

void NetworkMetricsProvider::SetNetworkConnectionTracker(
    network::NetworkConnectionTracker* network_connection_tracker) {
  DCHECK(network_connection_tracker);
  network_connection_tracker_ = network_connection_tracker;
  network_connection_tracker_->AddNetworkConnectionObserver(this);
  network_connection_tracker_->GetConnectionType(
      &connection_type_,
      base::BindOnce(&NetworkMetricsProvider::OnConnectionChanged,
                     weak_ptr_factory_.GetWeakPtr()));
  if (connection_type_ != network::mojom::ConnectionType::CONNECTION_UNKNOWN)
    network_connection_tracker_initialized_ = true;
}

void NetworkMetricsProvider::ProvideSystemProfileMetrics(
    SystemProfileProto* system_profile) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!connection_type_is_ambiguous_ ||
         network_connection_tracker_initialized_);
  SystemProfileProto::Network* network = system_profile->mutable_network();
  network->set_connection_type_is_ambiguous(connection_type_is_ambiguous_);
  network->set_connection_type(GetConnectionType());
  network->set_wifi_phy_layer_protocol_is_ambiguous(
      wifi_phy_layer_protocol_is_ambiguous_);
  network->set_wifi_phy_layer_protocol(GetWifiPHYLayerProtocol());

  network->set_min_effective_connection_type(
      ConvertEffectiveConnectionType(min_effective_connection_type_));
  network->set_max_effective_connection_type(
      ConvertEffectiveConnectionType(max_effective_connection_type_));

  // Note: We get the initial connection type when it becomes available and it
  // is handled at SetNetworkConnectionTracker() when GetConnectionType() is
  // called.
  //
  // Update the connection type. Note that this is necessary to set the network
  // type to "none" if there is no network connection for an entire UMA logging
  // window, since OnConnectionTypeChanged() ignores transitions to the "none"
  // state, and that is ok since it just deals with the current known state.
  if (network_connection_tracker_) {
    network_connection_tracker_->GetConnectionType(&connection_type_,
                                                   base::DoNothing());
  }

  if (connection_type_ != network::mojom::ConnectionType::CONNECTION_UNKNOWN)
    network_connection_tracker_initialized_ = true;
  // Reset the "ambiguous" flags, since a new metrics log session has started.
  connection_type_is_ambiguous_ = false;
  wifi_phy_layer_protocol_is_ambiguous_ = false;
  min_effective_connection_type_ = effective_connection_type_;
  max_effective_connection_type_ = effective_connection_type_;
}

void NetworkMetricsProvider::OnConnectionChanged(
    network::mojom::ConnectionType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // To avoid reporting an ambiguous connection type for users on flaky
  // connections, ignore transitions to the "none" state. Note that the
  // connection type is refreshed in ProvideSystemProfileMetrics() each time a
  // new UMA logging window begins, so users who genuinely transition to offline
  // mode for an extended duration will still be at least partially represented
  // in the metrics logs.
  if (type == network::mojom::ConnectionType::CONNECTION_NONE) {
    network_connection_tracker_initialized_ = true;
    return;
  }

  DCHECK(network_connection_tracker_initialized_ ||
         connection_type_ ==
             network::mojom::ConnectionType::CONNECTION_UNKNOWN);

  if (type != connection_type_ &&
      connection_type_ != network::mojom::ConnectionType::CONNECTION_NONE &&
      network_connection_tracker_initialized_) {
    // If |network_connection_tracker_initialized_| is false, it implies that
    // this is the first connection change callback received from network
    // connection tracker, and the previous connection type was
    // CONNECTION_UNKNOWN. In that case, connection type should not be marked as
    // ambiguous since there was no actual change in the connection type.
    connection_type_is_ambiguous_ = true;
  }

  network_connection_tracker_initialized_ = true;
  connection_type_ = type;

  ProbeWifiPHYLayerProtocol();
}

SystemProfileProto::Network::ConnectionType
NetworkMetricsProvider::GetConnectionType() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  switch (connection_type_) {
    case network::mojom::ConnectionType::CONNECTION_NONE:
      return SystemProfileProto::Network::CONNECTION_NONE;
    case network::mojom::ConnectionType::CONNECTION_UNKNOWN:
      return SystemProfileProto::Network::CONNECTION_UNKNOWN;
    case network::mojom::ConnectionType::CONNECTION_ETHERNET:
      return SystemProfileProto::Network::CONNECTION_ETHERNET;
    case network::mojom::ConnectionType::CONNECTION_WIFI:
      return SystemProfileProto::Network::CONNECTION_WIFI;
    case network::mojom::ConnectionType::CONNECTION_2G:
      return SystemProfileProto::Network::CONNECTION_2G;
    case network::mojom::ConnectionType::CONNECTION_3G:
      return SystemProfileProto::Network::CONNECTION_3G;
    case network::mojom::ConnectionType::CONNECTION_4G:
      return SystemProfileProto::Network::CONNECTION_4G;
    case network::mojom::ConnectionType::CONNECTION_5G:
      return SystemProfileProto::Network::CONNECTION_5G;
    case network::mojom::ConnectionType::CONNECTION_BLUETOOTH:
      return SystemProfileProto::Network::CONNECTION_BLUETOOTH;
  }
  NOTREACHED();
  return SystemProfileProto::Network::CONNECTION_UNKNOWN;
}

SystemProfileProto::Network::WifiPHYLayerProtocol
NetworkMetricsProvider::GetWifiPHYLayerProtocol() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  switch (wifi_phy_layer_protocol_) {
    case net::WIFI_PHY_LAYER_PROTOCOL_NONE:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_NONE;
    case net::WIFI_PHY_LAYER_PROTOCOL_ANCIENT:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_ANCIENT;
    case net::WIFI_PHY_LAYER_PROTOCOL_A:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_A;
    case net::WIFI_PHY_LAYER_PROTOCOL_B:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_B;
    case net::WIFI_PHY_LAYER_PROTOCOL_G:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_G;
    case net::WIFI_PHY_LAYER_PROTOCOL_N:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_N;
    case net::WIFI_PHY_LAYER_PROTOCOL_AC:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_AC;
    case net::WIFI_PHY_LAYER_PROTOCOL_AD:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_AD;
    case net::WIFI_PHY_LAYER_PROTOCOL_AX:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_AX;
    case net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN:
      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
  }
  NOTREACHED();
  return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
}

void NetworkMetricsProvider::ProbeWifiPHYLayerProtocol() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&net::GetWifiPHYLayerProtocol),
      base::BindOnce(&NetworkMetricsProvider::OnWifiPHYLayerProtocolResult,
                     weak_ptr_factory_.GetWeakPtr()));
}

void NetworkMetricsProvider::OnWifiPHYLayerProtocolResult(
    net::WifiPHYLayerProtocol mode) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (wifi_phy_layer_protocol_ != net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN &&
      mode != wifi_phy_layer_protocol_) {
    wifi_phy_layer_protocol_is_ambiguous_ = true;
  }
  wifi_phy_layer_protocol_ = mode;
}

void NetworkMetricsProvider::OnEffectiveConnectionTypeChanged(
    net::EffectiveConnectionType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  effective_connection_type_ = type;

  if (effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN ||
      effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) {
    // The effective connection type may be reported as Unknown if there is a
    // change in the connection type. Disregard it since network requests can't
    // be send during the changes in connection type. Similarly, disregard
    // offline as the type since it may be reported as the effective connection
    // type for a short period when there is a change in the connection type.
    return;
  }

  if (min_effective_connection_type_ ==
          net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN &&
      max_effective_connection_type_ ==
          net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
    min_effective_connection_type_ = type;
    max_effective_connection_type_ = type;
    return;
  }

  if (min_effective_connection_type_ ==
          net::EFFECTIVE_CONNECTION_TYPE_OFFLINE &&
      max_effective_connection_type_ ==
          net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) {
    min_effective_connection_type_ = type;
    max_effective_connection_type_ = type;
    return;
  }

  min_effective_connection_type_ =
      std::min(min_effective_connection_type_, effective_connection_type_);
  max_effective_connection_type_ =
      std::max(max_effective_connection_type_, effective_connection_type_);

  DCHECK_EQ(
      min_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
      max_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
  DCHECK_EQ(
      min_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE,
      max_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE);
}

}  // namespace metrics
