// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef NET_NQE_NETWORK_QUALITY_ESTIMATOR_PARAMS_H_
#define NET_NQE_NETWORK_QUALITY_ESTIMATOR_PARAMS_H_

#include <map>
#include <optional>
#include <string>

#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "net/base/net_export.h"
#include "net/base/network_change_notifier.h"
#include "net/nqe/effective_connection_type.h"
#include "net/nqe/network_quality.h"

namespace net {

// Forces NQE to return a specific effective connection type. Set using the
// |params| provided to the NetworkQualityEstimatorParams constructor.
NET_EXPORT extern const char kForceEffectiveConnectionType[];
NET_EXPORT extern const char kEffectiveConnectionTypeSlow2GOnCellular[];

// HTTP RTT thresholds for different effective connection types.
NET_EXPORT extern const base::TimeDelta
    kHttpRttEffectiveConnectionTypeThresholds[EFFECTIVE_CONNECTION_TYPE_LAST];

// NetworkQualityEstimatorParams computes the configuration parameters for
// the network quality estimator.
class NET_EXPORT NetworkQualityEstimatorParams {
 public:
  // |params| is the map containing all field trial parameters related to
  // NetworkQualityEstimator field trial.
  explicit NetworkQualityEstimatorParams(
      const std::map<std::string, std::string>& params);

  NetworkQualityEstimatorParams(const NetworkQualityEstimatorParams&) = delete;
  NetworkQualityEstimatorParams& operator=(
      const NetworkQualityEstimatorParams&) = delete;

  ~NetworkQualityEstimatorParams();

  // Returns the default observation for connection |type|. The default
  // observations are different for different connection types (e.g., 2G, 3G,
  // 4G, WiFi). The default observations may be used to determine the network
  // quality in absence of any other information.
  const nqe::internal::NetworkQuality& DefaultObservation(
      NetworkChangeNotifier::ConnectionType type) const;

  // Returns the typical network quality for connection |type|.
  const nqe::internal::NetworkQuality& TypicalNetworkQuality(
      EffectiveConnectionType type) const;

  // Returns the threshold for effective connection type |type|.
  const nqe::internal::NetworkQuality& ConnectionThreshold(
      EffectiveConnectionType type) const;

  // Returns the minimum number of requests in-flight to consider the network
  // fully utilized. A throughput observation is taken only when the network is
  // considered as fully utilized.
  size_t throughput_min_requests_in_flight() const;

  // Tiny transfer sizes may give inaccurate throughput results.
  // Minimum size of the transfer over which the throughput is computed.
  int64_t GetThroughputMinTransferSizeBits() const;

  // Returns the weight multiplier per second, which represents the factor by
  // which the weight of an observation reduces every second.
  double weight_multiplier_per_second() const {
    return weight_multiplier_per_second_;
  }

  // Returns an unset value if the effective connection type has not been forced
  // via the |params| provided to this class. Otherwise, returns a value set to
  // the effective connection type that has been forced. Forced ECT can be
  // forced based on |connection_type| (e.g. Slow-2G on cellular, and default on
  // other connection type).
  std::optional<EffectiveConnectionType> GetForcedEffectiveConnectionType(
      NetworkChangeNotifier::ConnectionType connection_type);

  void SetForcedEffectiveConnectionType(
      EffectiveConnectionType forced_effective_connection_type) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    forced_effective_connection_type_ = forced_effective_connection_type;
  }

  // Returns true if reading from the persistent cache is enabled.
  bool persistent_cache_reading_enabled() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return persistent_cache_reading_enabled_;
  }

  void set_persistent_cache_reading_enabled(
      bool persistent_cache_reading_enabled) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    persistent_cache_reading_enabled_ = persistent_cache_reading_enabled;
  }

  // Returns the the minimum interval betweeen consecutive notifications to a
  // single socket watcher.
  base::TimeDelta min_socket_watcher_notification_interval() const {
    return min_socket_watcher_notification_interval_;
  }

  // Number of bytes received during a throughput observation window of duration
  // 1 HTTP RTT should be at least the value returned by this method times
  // the typical size of a congestion window. If not, the throughput observation
  // window is heuristically determined as hanging.
  double throughput_hanging_requests_cwnd_size_multiplier() const {
    return throughput_hanging_requests_cwnd_size_multiplier_;
  }

  // Returns the multiplier by which the transport RTT should be multipled when
  // computing the HTTP RTT. The multiplied value of the transport RTT serves
  // as a lower bound to the HTTP RTT estimate. e.g., if the multiplied
  // transport RTT is 100 msec., then HTTP RTT estimate can't be lower than
  // 100 msec. Returns a negative value if the param is not set.
  double lower_bound_http_rtt_transport_rtt_multiplier() const {
    return lower_bound_http_rtt_transport_rtt_multiplier_;
  }

  // Returns the multiplier by which the end to end RTT estimate should be
  // multiplied when computing the HTTP RTT. The multiplied value of the
  // end to end RTT serves as an upper bound to the HTTP RTT estimate. e.g., if
  // the multiplied end to end RTT is 100 msec., then HTTP RTT estimate can't be
  // more than |upper_bound_http_rtt_endtoend_rtt_multiplier| times 100 msec.
  // Returns a negative value if the param is not set.
  double upper_bound_http_rtt_endtoend_rtt_multiplier() const {
    return upper_bound_http_rtt_endtoend_rtt_multiplier_;
  }

  // For the purpose of estimating the HTTP RTT, a request is marked as hanging
  // only if its RTT is at least this times the transport RTT estimate.
  int hanging_request_http_rtt_upper_bound_transport_rtt_multiplier() const {
    return hanging_request_http_rtt_upper_bound_transport_rtt_multiplier_;
  }

  // For the purpose of estimating the HTTP RTT, a request is marked as hanging
  // only if its RTT is at least this times the HTTP RTT estimate.
  int hanging_request_http_rtt_upper_bound_http_rtt_multiplier() const {
    return hanging_request_http_rtt_upper_bound_http_rtt_multiplier_;
  }

  // For the purpose of estimating the HTTP RTT, a request is marked as hanging
  // only if its RTT is at least as much the value returned by this method.
  base::TimeDelta hanging_request_upper_bound_min_http_rtt() const {
    return hanging_request_upper_bound_min_http_rtt_;
  }

  // Returns the number of transport RTT observations that should be available
  // before the transport RTT estimate can be used to clamp the HTTP RTT
  // estimate. Set to 5 by default which ensures that when the transport RTT
  // is available only from the connection type, it is not used for computing
  // the HTTP RTT estimate.
  size_t http_rtt_transport_rtt_min_count() const {
    return http_rtt_transport_rtt_min_count_;
  }

  // Returns the minimum interval between successive computations of the
  // increase in transport RTT.
  base::TimeDelta increase_in_transport_rtt_logging_interval() const {
    return increase_in_transport_rtt_logging_interval_;
  }

  // The maximum age of RTT observations for them to be considered recent for
  // the computation of the increase in RTT.
  base::TimeDelta recent_time_threshold() const {
    return recent_time_threshold_;
  }

  // The maximum age of observations for them to be considered useful for
  // calculating the minimum transport RTT from the historical data.
  base::TimeDelta historical_time_threshold() const {
    return historical_time_threshold_;
  }

  // Determines if the responses smaller than |kMinTransferSizeInBytes|
  // or shorter than |kMinTransferSizeInBytes| can be used in estimating the
  // network quality. Set to true only for tests.
  bool use_small_responses() const;

  // Returns the typical HTTP RTT that maps to the given
  // |effective_connection_type|. May return invalid value if
  // |effective_connection_type| is less than Slow2G or faster than 4G,
  static base::TimeDelta GetDefaultTypicalHttpRtt(
      EffectiveConnectionType effective_connection_type);

  // Returns the typical downslink throughput (in kbps) that maps to the given
  // |effective_connection_type|. May return invalid value if
  // |effective_connection_type| is less than Slow2G or faster than 4G,
  static int32_t GetDefaultTypicalDownlinkKbps(
      EffectiveConnectionType effective_connection_type);

  // |use_small_responses| should only be true when testing.
  // Allows the responses smaller than |kMinTransferSizeInBits| to be used for
  // network quality estimation.
  void SetUseSmallResponsesForTesting(bool use_small_responses);

  // If an in-flight request does not receive any data for a duration longer
  // than the value of this multiplier times the current HTTP RTT estimate, then
  // the request should be considered as hanging. If this multiplier has a
  // negative or a zero value, then none of the request should be considered as
  // hanging.
  int hanging_request_duration_http_rtt_multiplier() const {
    return hanging_request_duration_http_rtt_multiplier_;
  }

  // An in-flight request may be marked as hanging only if it does not receive
  // any data for at least this duration.
  base::TimeDelta hanging_request_min_duration() const {
    return hanging_request_min_duration_;
  }

  // Returns true if default values provided by the platform should be used for
  // estimation. Set to false only for testing.
  bool add_default_platform_observations() const {
    return add_default_platform_observations_;
  }

  // Number of observations received after which the effective connection type
  // should be recomputed.
  size_t count_new_observations_received_compute_ect() const { return 50; }

  // Maximum number of observations that can be held in a single
  // ObservationBuffer.
  size_t observation_buffer_size() const { return 300; }

  // Minimun interval between consecutive notifications from socket
  // watchers who live on the same thread as the network quality estimator.
  base::TimeDelta socket_watchers_min_notification_interval() const {
    return socket_watchers_min_notification_interval_;
  }

  // Returns true if end-to-end RTT estimates can be used for computing network
  // quality estimate.
  bool use_end_to_end_rtt() const { return use_end_to_end_rtt_; }

  // Returns a multiplier which is used to clamp Kbps on slow connections. For
  // a given ECT, the upper bound on Kbps is computed based on this returned
  // multiplier and the typical Kbps for the given ECT. If
  // upper_bound_typical_kbps_multiplier() is -1, then clamping should be
  // disabled.
  double upper_bound_typical_kbps_multiplier() const {
    return upper_bound_typical_kbps_multiplier_;
  }

  // Returns true if RTTs should be adjusted based on RTT counts.
  // If there are not enough transport RTT samples, end-to-end RTT samples and
  // the cached estimates are unavailble/too stale, then the computed value of
  // HTTP RTT can't be trusted due to hanging GETs. In that case, NQE returns
  // the typical HTTP RTT for a fast connection if
  // adjust_rtt_based_on_rtt_counts() returns true.
  bool adjust_rtt_based_on_rtt_counts() const {
    return adjust_rtt_based_on_rtt_counts_;
  }

  // Sets the forced effective connection type as |type|.
  void SetForcedEffectiveConnectionTypeForTesting(EffectiveConnectionType type);

 private:
  // Map containing all field trial parameters related to
  // NetworkQualityEstimator field trial.
  const std::map<std::string, std::string> params_;

  const size_t throughput_min_requests_in_flight_;
  const int throughput_min_transfer_size_kilobytes_;
  const double throughput_hanging_requests_cwnd_size_multiplier_;
  const double weight_multiplier_per_second_;
  std::optional<EffectiveConnectionType> forced_effective_connection_type_;
  const bool forced_effective_connection_type_on_cellular_only_;
  bool persistent_cache_reading_enabled_;
  const base::TimeDelta min_socket_watcher_notification_interval_;
  const double lower_bound_http_rtt_transport_rtt_multiplier_ = 1.0;
  const double upper_bound_http_rtt_endtoend_rtt_multiplier_;
  const int hanging_request_http_rtt_upper_bound_transport_rtt_multiplier_;
  const int hanging_request_http_rtt_upper_bound_http_rtt_multiplier_;
  const base::TimeDelta hanging_request_upper_bound_min_http_rtt_ =
      base::Milliseconds(500);
  const size_t http_rtt_transport_rtt_min_count_;
  const base::TimeDelta increase_in_transport_rtt_logging_interval_;
  const base::TimeDelta recent_time_threshold_;
  const base::TimeDelta historical_time_threshold_;
  const int hanging_request_duration_http_rtt_multiplier_;
  const base::TimeDelta hanging_request_min_duration_ =
      base::Milliseconds(3000);
  const bool add_default_platform_observations_;
  const base::TimeDelta socket_watchers_min_notification_interval_;
  const bool use_end_to_end_rtt_ = true;
  const double upper_bound_typical_kbps_multiplier_;
  const bool adjust_rtt_based_on_rtt_counts_;

  bool use_small_responses_ = false;

  // Default network quality observations obtained from |params_|.
  nqe::internal::NetworkQuality
      default_observations_[NetworkChangeNotifier::CONNECTION_LAST + 1];

  // Typical network quality for different effective connection types obtained
  // from |params_|.
  nqe::internal::NetworkQuality typical_network_quality_
      [EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_LAST];

  // Thresholds for different effective connection types obtained from
  // |params_|. These thresholds encode how different connection types behave
  // in general.
  nqe::internal::NetworkQuality connection_thresholds_
      [EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_LAST];

  SEQUENCE_CHECKER(sequence_checker_);
};

}  // namespace net

#endif  // NET_NQE_NETWORK_QUALITY_ESTIMATOR_PARAMS_H_
