// 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.

#ifndef NET_BASE_PROXY_CHAIN_H_
#define NET_BASE_PROXY_CHAIN_H_

#include <stdint.h>

#include <iosfwd>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>

#include "net/base/host_port_pair.h"
#include "net/base/net_export.h"
#include "net/base/proxy_server.h"

namespace base {
class Pickle;
class PickleIterator;
}  // namespace base

namespace net {

// ProxyChain represents a chain of ProxyServers. A chain with multiple proxy
// servers means that a single connection will go through all of the proxies in
// order, using a tunnel through the first proxy to connect to the second, etc.
// A "direct" connection is a chain of length zero.
class NET_EXPORT ProxyChain {
 public:
  // Constructs an invalid ProxyChain.
  ProxyChain();

  ProxyChain(const ProxyChain& other);      // Copy constructor
  ProxyChain(ProxyChain&& other) noexcept;  // Move constructor

  ProxyChain& operator=(const ProxyChain& other);  // Copy assignment operator
  ProxyChain& operator=(
      ProxyChain&& other) noexcept;  // Move assignment operator

  ProxyChain(ProxyServer::Scheme scheme, const HostPortPair& host_port_pair);

  explicit ProxyChain(std::vector<ProxyServer> proxy_server_list);
  explicit ProxyChain(ProxyServer proxy_server);

  ~ProxyChain();  // Destructor declaration

  // Creates a single-proxy ProxyChain, validating and canonicalizing input.
  // Port is optional and, if not provided, will be replaced with the default
  // port for the given scheme. Accepts IPv6 literal `host`s with surrounding
  // brackets (URL format) or without (HostPortPair format). On invalid input,
  // result will be a `SCHEME_INVALID` ProxyChain.
  //
  // Must not be called with `SCHEME_INVALID` or `SCHEME_DIRECT`. Use
  // `ProxyChain()` or `Direct()` respectively to create an invalid or direct
  // ProxyChain.
  static ProxyChain FromSchemeHostAndPort(ProxyServer::Scheme scheme,
                                          std::string_view host,
                                          std::string_view port_str) {
    return ProxyChain(
        ProxyServer::FromSchemeHostAndPort(scheme, host, port_str));
  }
  static ProxyChain FromSchemeHostAndPort(ProxyServer::Scheme scheme,
                                          std::string_view host,
                                          std::optional<uint16_t> port) {
    return ProxyChain(ProxyServer::FromSchemeHostAndPort(scheme, host, port));
  }
  // Create a "direct" proxy chain, which includes no proxy servers.
  static ProxyChain Direct() { return ProxyChain(std::vector<ProxyServer>()); }

  // Creates a `ProxyChain` for use by the IP Protection feature. This is used
  // for metrics collection and for special handling.  If not given, the
  // chain_id defaults to 0 which corresponds to an un-identified chain.
  // If the resulting `ProxyChain` is deemed to be invalid, an invalid
  // `ProxyChain` is returned and `chain_id` is not used.
  static ProxyChain ForIpProtection(std::vector<ProxyServer> proxy_server_list,
                                    int chain_id = 0) {
    return ProxyChain(std::move(proxy_server_list), chain_id,
                      /*opaque_data=*/std::nullopt);
  }

  // Creates a `ProxyChain` with `opaque_data` attached to it. This can be later
  // on retrieved via `opaque_data()`. If the resulting `ProxyChain` is deemed
  // to be invalid, an invalid `ProxyChain` is returned and `opaque_data` is not
  // used.
  static ProxyChain WithOpaqueData(std::vector<ProxyServer> proxy_server_list,
                                   int opaque_data) {
    return ProxyChain(std::move(proxy_server_list),
                      /*ip_protection_chain_id=*/kNotIpProtectionChainId,
                      opaque_data);
  }

  // Attempt to create a new `ProxyChain` from a pickle that contains data
  // generated by a call to the `Persist` method.
  //
  // Returns a `ProxyChain` upon success, otherwise returns `std::nullopt`.
  static std::optional<ProxyChain> InitFromPickle(
      base::PickleIterator& pickle_iter);

  // Call this method to persist `ProxyChain`. Illegal to call this on an
  // invalid object.
  void Persist(base::Pickle* pickle) const;

  // Get ProxyServer at index in chain. This is not valid for direct or invalid
  // proxy chains.
  const ProxyServer& GetProxyServer(size_t chain_index) const;

  // Get the ProxyServers in this chain. This must not be called on invalid
  // proxy chains. An empty vector is returned for direct proxy chains.
  const std::vector<ProxyServer>& proxy_servers() const;

  // Return the last proxy server in the chain, together with all of the
  // preceding proxies. The chain must have at least one proxy server. If it
  // only has one proxy server, then the resulting chain will be direct.
  std::pair<ProxyChain, const ProxyServer&> SplitLast() const;

  // Return a prefix of this proxy chain, of the given length. This length must
  // be less than or equal to the chain's length.
  ProxyChain Prefix(size_t length) const;

  // Get the first ProxyServer in this chain, which must have at least one
  // server.
  const ProxyServer& First() const;

  // Get the last ProxyServer in this chain, which must have at least one
  // server.
  const ProxyServer& Last() const;

  // Get the ProxyServers in this chain, or `nullopt` if the chain is not valid.
  const std::optional<std::vector<ProxyServer>>& proxy_servers_if_valid()
      const {
    return proxy_server_list_;
  }

  // Returns number of proxy servers in chain.
  size_t length() const {
    return proxy_server_list_.has_value() ? proxy_server_list_.value().size()
                                          : 0;
  }

  // Returns true if this chain contains more than one proxy.
  bool is_multi_proxy() const {
    return proxy_server_list_.has_value()
               ? proxy_server_list_.value().size() > 1
               : false;
  }

  // Returns true if this chain contains exactly one proxy.
  bool is_single_proxy() const {
    return proxy_server_list_.has_value()
               ? proxy_server_list_.value().size() == 1
               : false;
  }

  // Returns true if this is a direct (equivalently, zero-proxy) chain.
  bool is_direct() const {
    return proxy_server_list_.has_value() ? proxy_server_list_.value().empty()
                                          : false;
  }

  template <class Predicate>
  bool AnyProxy(Predicate p) const {
    return proxy_server_list_.has_value() &&
           std::any_of(proxy_server_list_->begin(), proxy_server_list_->end(),
                       p);
  }

  // Determines if HTTP GETs to the last proxy in the chain are allowed,
  // instead of establishing a tunnel with CONNECT. This is no longer supported
  // for QUIC proxy chains and is not currently supported for multi-proxy
  // chains.
  bool is_get_to_proxy_allowed() const {
    return is_single_proxy() && (First().is_http() || First().is_https());
  }

  // Returns true if a proxy server list is available.
  bool IsValid() const { return proxy_server_list_.has_value(); }

  // A negative value for `ip_protection_chain_id()` indicating this is not an
  // IP protection chain. All IP-Protection chain IDs are non-negative.
  static constexpr int kNotIpProtectionChainId = -1;

  // A value for `ip_protection_chain_id()` for IP protection chains for which
  // no other chain ID was specified.
  static constexpr int kDefaultIpProtectionChainId = 0;

  // The largest allowed ip_protection_chain_id.
  // NOTE: Make sure to update the
  // `Net.IpProtection.CanFalloverToNextProxy.Error.{Chain}` histogram when
  // modifying this value.
  static constexpr int kMaxIpProtectionChainId = 3;

  bool is_for_ip_protection() const {
    return ip_protection_chain_id_ != kNotIpProtectionChainId;
  }
  int ip_protection_chain_id() const { return ip_protection_chain_id_; }

  std::optional<int> opaque_data() const { return opaque_data_; }

  friend bool operator==(const ProxyChain&, const ProxyChain&) = default;
  friend auto operator<=>(const ProxyChain&, const ProxyChain&) = default;

  std::string ToDebugString() const;

  // Returns a string suffix for histogram names.
  //
  // For IP Protection chains, the format is "Chain{ID}" for direct chains, and
  // "Chain{ID}.{Protocol}" otherwise, where {ID} is the
  // `ip_protection_chain_id()` and {Protocol} is the scheme of the first
  // proxy. For example, "Chain0" or "Chain1.HTTPS".
  // For other chains, the format is "Direct" for direct connections, or the
  // scheme of the proxies in the chain for proxy connections. For example,
  // "HTTPS" or "SOCKS5".
  //
  // This format should not be changed without updating the metrics that use
  // this.
  std::string GetHistogramSuffix() const;

 private:
  explicit ProxyChain(std::vector<ProxyServer> proxy_server_list,
                      int ip_protection_chain_id,
                      std::optional<int> opaque_data);

  std::optional<std::vector<ProxyServer>> proxy_server_list_;

  // If used for IP protection, this is the chain_id received from the server.
  // A negative value indicates this chain is not used for IP protection.
  int ip_protection_chain_id_ = kNotIpProtectionChainId;

  // Owners of `ProxyChain` can use to store private information. For example,
  // Cronet uses this to map each net::ProxyChain to a specific
  // org.chromium.net.Proxy.Callback, when necessary. This does not get
  // persisted during calls to the `Persist` method.
  // Note: the value of this field does not affect the validity of this
  // ProxyChain.
  std::optional<int> opaque_data_;

  // Returns true if this chain is valid. A chain is considered valid if
  //  (1) it is a single valid proxy server, or
  //  (2) it is a chain of servers, composed of zero or more SCHEME_QUIC servers
  //  followed by zero or more SCHEME_HTTPS servers.
  // If any SCHEME_QUIC servers are included, then the chain must be for IP
  // protection.
  bool IsValidInternal() const;
};

NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                            const ProxyChain& proxy_chain);

}  // namespace net

#endif  // NET_BASE_PROXY_CHAIN_H_
