// 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 "net/base/proxy_chain.h"

#include <array>
#include <optional>
#include <sstream>

#include "base/pickle.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/gtest_util.h"
#include "build/buildflag.h"
#include "net/base/proxy_server.h"
#include "net/base/proxy_string_util.h"
#include "net/net_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace {

// Multi-proxy chains can only be created outside of Ip Protection in debug
// builds.
constexpr bool kAreNonIppMultiProxyChainsValid =
    BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS);

TEST(ProxyChainTest, DefaultConstructor) {
  ProxyChain proxy_chain;
  EXPECT_FALSE(proxy_chain.IsValid());
}

TEST(ProxyChainTest, ConstructorsAndAssignmentOperators) {
  std::vector proxy_servers = {
      ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS),
      ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_HTTPS)};

  ProxyChain proxy_chain = ProxyChain(proxy_servers);

  ProxyChain copy_constructed(proxy_chain);
  EXPECT_EQ(proxy_chain, copy_constructed);

  ProxyChain copy_assigned = proxy_chain;
  EXPECT_EQ(proxy_chain, copy_assigned);

  ProxyChain move_constructed{std::move(copy_constructed)};
  EXPECT_EQ(proxy_chain, move_constructed);

  ProxyChain move_assigned = std::move(copy_assigned);
  EXPECT_EQ(proxy_chain, move_assigned);
}

TEST(ProxyChainTest, DirectProxy) {
  ProxyChain proxy_chain1 = ProxyChain::Direct();
  ProxyChain proxy_chain2 = ProxyChain(std::vector<ProxyServer>());
  std::vector<ProxyServer> proxy_servers = {};

  // Equal and valid proxy chains.
  ASSERT_EQ(proxy_chain1, proxy_chain2);
  EXPECT_TRUE(proxy_chain1.IsValid());
  EXPECT_TRUE(proxy_chain2.IsValid());

  EXPECT_TRUE(proxy_chain1.is_direct());
  EXPECT_FALSE(proxy_chain1.is_single_proxy());
  EXPECT_FALSE(proxy_chain1.is_multi_proxy());
  ASSERT_EQ(proxy_chain1.length(), 0u);
  ASSERT_EQ(proxy_chain1.proxy_servers(), proxy_servers);
}

TEST(ProxyChainTest, Ostream) {
  ProxyChain proxy_chain =
      ProxyChain::FromSchemeHostAndPort(ProxyServer::SCHEME_HTTP, "foo", 80);
  std::ostringstream out;
  out << proxy_chain;
  EXPECT_EQ(out.str(), "[foo:80]");
}

TEST(ProxyChainTest, ToDebugString) {
  ProxyChain proxy_chain1 =
      ProxyChain(ProxyUriToProxyServer("foo:333", ProxyServer::SCHEME_SOCKS5));
  EXPECT_EQ(proxy_chain1.ToDebugString(), "[socks5://foo:333]");

  ProxyChain direct_proxy_chain = ProxyChain::Direct();
  EXPECT_EQ(direct_proxy_chain.ToDebugString(), "[direct://]");

  ProxyChain ip_protection_proxy_chain = ProxyChain::ForIpProtection(
      {ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS),
       ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS)});
  EXPECT_EQ(ip_protection_proxy_chain.ToDebugString(),
            "[https://foo:444, https://foo:555] (IP Protection)");

  ProxyChain invalid_proxy_chain = ProxyChain();
  EXPECT_EQ(invalid_proxy_chain.ToDebugString(), "INVALID PROXY CHAIN");

  if (kAreNonIppMultiProxyChainsValid) {
    ProxyChain proxy_chain2 = ProxyChain(
        {ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS),
         ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS)});
    EXPECT_EQ(proxy_chain2.ToDebugString(),
              "[https://foo:444, https://foo:555]");
  }

  ProxyChain proxy_chain_with_opaque_data = ProxyChain::WithOpaqueData(
      {ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS)},
      /*opaque_data=*/123);
  EXPECT_EQ(proxy_chain_with_opaque_data.ToDebugString(),
            "[https://foo:555] (Opaque data 123)");
}

TEST(ProxyChainTest, FromSchemeHostAndPort) {
  struct Tests {
    const ProxyServer::Scheme input_scheme;
    const char* const input_host;
    const std::optional<uint16_t> input_port;
    const char* const input_port_str;
    const char* const expected_host;
    const uint16_t expected_port;
  };
  const auto tests = std::to_array<Tests>({
      {ProxyServer::SCHEME_HTTP, "foopy", 80, "80", "foopy", 80},

      // Non-standard port
      {ProxyServer::SCHEME_HTTP, "foopy", 10, "10", "foopy", 10},
      {ProxyServer::SCHEME_HTTP, "foopy", 0, "0", "foopy", 0},

      // Hostname canonicalization
      {ProxyServer::SCHEME_HTTP, "FoOpY", 80, "80", "foopy", 80},
      {ProxyServer::SCHEME_HTTP, "f\u00fcpy", 80, "80", "xn--fpy-hoa", 80},

      // IPv4 literal
      {ProxyServer::SCHEME_HTTP, "1.2.3.4", 80, "80", "1.2.3.4", 80},

      // IPv4 literal canonicalization
      {ProxyServer::SCHEME_HTTP, "127.1", 80, "80", "127.0.0.1", 80},
      {ProxyServer::SCHEME_HTTP, "0x7F.0x1", 80, "80", "127.0.0.1", 80},
      {ProxyServer::SCHEME_HTTP, "0177.01", 80, "80", "127.0.0.1", 80},

      // IPv6 literal
      {ProxyServer::SCHEME_HTTP, "[3ffe:2a00:100:7031::1]", 80, "80",
       "[3ffe:2a00:100:7031::1]", 80},
      {ProxyServer::SCHEME_HTTP, "3ffe:2a00:100:7031::1", 80, "80",
       "[3ffe:2a00:100:7031::1]", 80},

      // IPv6 literal canonicalization
      {ProxyServer::SCHEME_HTTP, "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", 80,
       "80", "[fedc:ba98:7654:3210:fedc:ba98:7654:3210]", 80},
      {ProxyServer::SCHEME_HTTP, "::192.9.5.5", 80, "80", "[::c009:505]", 80},

      // Other schemes
      {ProxyServer::SCHEME_HTTPS, "foopy", 111, "111", "foopy", 111},
      {ProxyServer::SCHEME_SOCKS4, "foopy", 111, "111", "foopy", 111},
      {ProxyServer::SCHEME_SOCKS5, "foopy", 111, "111", "foopy", 111},

      // Default ports
      {ProxyServer::SCHEME_HTTP, "foopy", std::nullopt, "", "foopy", 80},
      {ProxyServer::SCHEME_HTTPS, "foopy", std::nullopt, "", "foopy", 443},
      {ProxyServer::SCHEME_SOCKS4, "foopy", std::nullopt, "", "foopy", 1080},
      {ProxyServer::SCHEME_SOCKS5, "foopy", std::nullopt, "", "foopy", 1080},
  });

  for (size_t i = 0; i < std::size(tests); ++i) {
    SCOPED_TRACE(base::NumberToString(i) + ": " + tests[i].input_host + ":" +
                 base::NumberToString(tests[i].input_port.value_or(-1)));
    auto chain = ProxyChain::FromSchemeHostAndPort(
        tests[i].input_scheme, tests[i].input_host, tests[i].input_port);
    auto proxy = chain.First();

    ASSERT_TRUE(proxy.is_valid());
    EXPECT_EQ(proxy.scheme(), tests[i].input_scheme);
    EXPECT_EQ(proxy.GetHost(), tests[i].expected_host);
    EXPECT_EQ(proxy.GetPort(), tests[i].expected_port);

    auto chain_from_string_port = ProxyChain::FromSchemeHostAndPort(
        tests[i].input_scheme, tests[i].input_host, tests[i].input_port_str);
    auto proxy_from_string_port = chain_from_string_port.First();
    EXPECT_TRUE(proxy_from_string_port.is_valid());
    EXPECT_EQ(proxy, proxy_from_string_port);
  }
}

TEST(ProxyChainTest, InvalidHostname) {
  const auto tests = std::to_array<const char*>({
      "",
      "[]",
      "[foo]",
      "foo:",
      "foo:80",
      ":",
      "http://foo",
      "3ffe:2a00:100:7031::1]",
      "[3ffe:2a00:100:7031::1",
      "foo.80",
  });

  for (size_t i = 0; i < std::size(tests); ++i) {
    SCOPED_TRACE(base::NumberToString(i) + ": " + tests[i]);
    auto proxy = ProxyChain::FromSchemeHostAndPort(ProxyServer::SCHEME_HTTP,
                                                   tests[i], 80);
    EXPECT_FALSE(proxy.IsValid());
  }
}

TEST(ProxyChainTest, InvalidPort) {
  const auto tests = std::to_array<const char*>({
      "-1",
      "65536",
      "foo",
      "0x35",
  });

  for (size_t i = 0; i < std::size(tests); ++i) {
    SCOPED_TRACE(base::NumberToString(i) + ": " + tests[i]);
    auto proxy = ProxyChain::FromSchemeHostAndPort(ProxyServer::SCHEME_HTTP,
                                                   "foopy", tests[i]);
    EXPECT_FALSE(proxy.IsValid());
  }
}

TEST(ProxyChainTest, SingleProxyChain) {
  auto proxy_server =
      ProxyUriToProxyServer("foo:333", ProxyServer::SCHEME_HTTPS);

  std::vector<ProxyServer> proxy_servers = {proxy_server};
  auto proxy = ProxyChain(proxy_servers);

  EXPECT_FALSE(proxy.is_direct());
  EXPECT_TRUE(proxy.is_single_proxy());
  EXPECT_FALSE(proxy.is_multi_proxy());
  ASSERT_EQ(proxy.proxy_servers(), proxy_servers);
  ASSERT_EQ(proxy.length(), 1u);
  ASSERT_EQ(proxy.GetProxyServer(0), proxy_server);
}

TEST(ProxyChainTest, SplitLast) {
  auto proxy_server1 =
      ProxyUriToProxyServer("foo:333", ProxyServer::SCHEME_HTTPS);
  auto proxy_server2 =
      ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS);
  auto proxy_server3 =
      ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS);

  auto chain3 = ProxyChain::ForIpProtection(
      {proxy_server1, proxy_server2, proxy_server3});
  EXPECT_EQ(chain3.SplitLast(),
            std::make_pair(
                ProxyChain::ForIpProtection({proxy_server1, proxy_server2}),
                proxy_server3));

  auto chain1 = ProxyChain({proxy_server1});
  EXPECT_EQ(chain1.SplitLast(),
            std::make_pair(ProxyChain::Direct(), proxy_server1));

  if (kAreNonIppMultiProxyChainsValid) {
    auto chain2 = ProxyChain({proxy_server1, proxy_server2});
    EXPECT_EQ(chain2.SplitLast(),
              std::make_pair(ProxyChain({proxy_server1}), proxy_server2));
  }
}

TEST(ProxyChainTest, Prefix) {
  auto proxy_server1 =
      ProxyUriToProxyServer("foo:333", ProxyServer::SCHEME_HTTPS);
  auto proxy_server2 =
      ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS);
  auto proxy_server3 =
      ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS);

  for (bool for_ip_protection : {false, true}) {
    SCOPED_TRACE(for_ip_protection);

    const auto create_chain = [&](std::vector<ProxyServer> proxies) {
      if (for_ip_protection) {
        return ProxyChain::ForIpProtection(std::move(proxies));
      }
      return ProxyChain(std::move(proxies));
    };

    const auto empty_proxy_chain = create_chain({});
    EXPECT_EQ(empty_proxy_chain.Prefix(0), empty_proxy_chain);

    const auto single_proxy_chain = create_chain({proxy_server1});
    EXPECT_EQ(single_proxy_chain.Prefix(0), empty_proxy_chain);
    EXPECT_EQ(single_proxy_chain.Prefix(1), single_proxy_chain);

    if (!for_ip_protection && !kAreNonIppMultiProxyChainsValid) {
      continue;
    }

    const auto double_proxy_chain =
        create_chain({proxy_server1, proxy_server2});
    EXPECT_EQ(double_proxy_chain.Prefix(0), empty_proxy_chain);
    EXPECT_EQ(double_proxy_chain.Prefix(1), single_proxy_chain);
    EXPECT_EQ(double_proxy_chain.Prefix(2), double_proxy_chain);

    auto triple_proxy_chain =
        create_chain({proxy_server1, proxy_server2, proxy_server3});
    EXPECT_EQ(triple_proxy_chain.Prefix(0), empty_proxy_chain);
    EXPECT_EQ(triple_proxy_chain.Prefix(1), single_proxy_chain);
    EXPECT_EQ(triple_proxy_chain.Prefix(2), double_proxy_chain);
    EXPECT_EQ(triple_proxy_chain.Prefix(3), triple_proxy_chain);
  }
}

TEST(ProxyChainTest, First) {
  auto proxy_server1 =
      ProxyUriToProxyServer("foo:333", ProxyServer::SCHEME_HTTPS);

  auto chain = ProxyChain({proxy_server1});
  EXPECT_EQ(chain.First(), proxy_server1);

  if (kAreNonIppMultiProxyChainsValid) {
    auto proxy_server2 =
        ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS);

    chain = ProxyChain({proxy_server1, proxy_server2});
    EXPECT_EQ(chain.First(), proxy_server1);
  }
}

TEST(ProxyChainTest, Last) {
  auto proxy_server1 =
      ProxyUriToProxyServer("foo:333", ProxyServer::SCHEME_HTTPS);

  auto chain = ProxyChain({proxy_server1});
  EXPECT_EQ(chain.Last(), proxy_server1);

  if (kAreNonIppMultiProxyChainsValid) {
    auto proxy_server2 =
        ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS);

    chain = ProxyChain({proxy_server1, proxy_server2});
    EXPECT_EQ(chain.Last(), proxy_server2);
  }
}

TEST(ProxyChainTest, IsForIpProtection) {
  auto regular_proxy_chain1 = ProxyChain::Direct();
  EXPECT_FALSE(regular_proxy_chain1.is_for_ip_protection());

  auto ip_protection_proxy_chain1 =
      ProxyChain::ForIpProtection(std::vector<ProxyServer>());
  EXPECT_TRUE(ip_protection_proxy_chain1.is_for_ip_protection());

  auto regular_proxy_chain2 =
      ProxyChain({ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS),
                  ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_HTTPS)});
  EXPECT_FALSE(regular_proxy_chain2.is_for_ip_protection());

  auto ip_protection_proxy_chain2 = ProxyChain::ForIpProtection(
      {ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS),
       ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_HTTPS)});
  EXPECT_TRUE(ip_protection_proxy_chain2.is_for_ip_protection());
}

TEST(ProxyChainTest, ForIpProtection) {
  auto ip_protection_proxy_chain1 =
      ProxyChain::ForIpProtection(std::vector<ProxyServer>());
  EXPECT_TRUE(ip_protection_proxy_chain1.is_direct());
  EXPECT_TRUE(ip_protection_proxy_chain1.is_for_ip_protection());
  EXPECT_EQ(ip_protection_proxy_chain1.ip_protection_chain_id(),
            ProxyChain::kDefaultIpProtectionChainId);

  // Ensure that ProxyChain can be reassigned a new value created using its own
  // `proxy_severs()`.
  auto proxy_chain =
      ProxyChain({ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS)});
  auto copied_proxy_chain = proxy_chain;

  // Assert that the newly created `ProxyChain` is not for IP protection.
  EXPECT_FALSE(proxy_chain.is_for_ip_protection());
  EXPECT_EQ(proxy_chain.ip_protection_chain_id(),
            ProxyChain::kNotIpProtectionChainId);

  // Re-assign new value to `proxy_chain` by using its own proxy servers to
  // create a proxy chain for IP protection.
  proxy_chain =
      ProxyChain::ForIpProtection(std::move(proxy_chain.proxy_servers()));

  // Assert re-assigned proxy chain is now for IP protection and contains the
  // same servers from the original copy.
  EXPECT_TRUE(proxy_chain.is_for_ip_protection());
  EXPECT_EQ(proxy_chain.ip_protection_chain_id(),
            ProxyChain::kDefaultIpProtectionChainId);
  EXPECT_FALSE(copied_proxy_chain.is_for_ip_protection());
  EXPECT_EQ(proxy_chain.proxy_servers(), copied_proxy_chain.proxy_servers());

  auto chain_with_id = ProxyChain::ForIpProtection(
      {ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS),
       ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_HTTPS)},
      /*chain_id=*/3);
  EXPECT_FALSE(chain_with_id.is_direct());
  EXPECT_TRUE(chain_with_id.is_for_ip_protection());
  EXPECT_EQ(chain_with_id.ip_protection_chain_id(), 3);
}

TEST(ProxyChainTest, WithOpaqueData) {
  auto regular_proxy_chain1 = ProxyChain::Direct();
  EXPECT_FALSE(regular_proxy_chain1.opaque_data().has_value());

  auto regular_proxy_chain2 =
      ProxyChain({ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS),
                  ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_HTTPS)});
  EXPECT_FALSE(regular_proxy_chain2.opaque_data().has_value());

  auto proxy_chain_with_opaque_data1 =
      ProxyChain::WithOpaqueData(std::vector<ProxyServer>(),
                                 /*opaque_data=*/123);
  EXPECT_TRUE(proxy_chain_with_opaque_data1.opaque_data().has_value());
  EXPECT_EQ(*proxy_chain_with_opaque_data1.opaque_data(), 123);
  EXPECT_TRUE(
      proxy_chain_with_opaque_data1.proxy_servers_if_valid().has_value());
  EXPECT_TRUE(proxy_chain_with_opaque_data1.proxy_servers_if_valid()->empty());
  EXPECT_FALSE(proxy_chain_with_opaque_data1.is_for_ip_protection());

  auto proxy_chain_copy = proxy_chain_with_opaque_data1;
  EXPECT_TRUE(proxy_chain_copy.opaque_data().has_value());
  EXPECT_EQ(*proxy_chain_copy.opaque_data(), 123);
  EXPECT_TRUE(proxy_chain_copy.proxy_servers_if_valid().has_value());
  EXPECT_FALSE(proxy_chain_copy.is_for_ip_protection());

  const auto proxy_servers = std::vector<ProxyServer>(
      {ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS)});
  auto proxy_chain_with_opaque_data2 =
      ProxyChain::WithOpaqueData(proxy_servers,
                                 /*opaque_data=*/333);
  EXPECT_TRUE(proxy_chain_with_opaque_data2.opaque_data().has_value());
  EXPECT_EQ(proxy_chain_with_opaque_data2.opaque_data(), 333);
  EXPECT_TRUE(
      proxy_chain_with_opaque_data2.proxy_servers_if_valid().has_value());
  EXPECT_EQ(*proxy_chain_with_opaque_data2.proxy_servers_if_valid(),
            proxy_servers);
  EXPECT_FALSE(proxy_chain_with_opaque_data2.is_for_ip_protection());
}

TEST(ProxyChainTest, IsGetToProxyAllowed) {
  auto https_server1 =
      ProxyUriToProxyServer("foo:333", ProxyServer::SCHEME_HTTPS);
  auto https_server2 =
      ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS);
  auto http_server = ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTP);
  auto socks_server =
      ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_SOCKS4);

  EXPECT_FALSE(ProxyChain::Direct().is_get_to_proxy_allowed());
  EXPECT_TRUE(ProxyChain({https_server1}).is_get_to_proxy_allowed());
  EXPECT_TRUE(ProxyChain({http_server}).is_get_to_proxy_allowed());
  EXPECT_FALSE(ProxyChain({socks_server}).is_get_to_proxy_allowed());
  EXPECT_FALSE(
      ProxyChain({https_server1, https_server2}).is_get_to_proxy_allowed());
}

TEST(ProxyChainTest, IsValid) {
  // Single hop proxy of type Direct is valid.
  EXPECT_TRUE(ProxyChain::Direct().IsValid());

  auto https1 = ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS);
  auto https2 = ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS);
  auto quic1 = ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_QUIC);
  auto quic2 = ProxyUriToProxyServer("foo:777", ProxyServer::SCHEME_QUIC);
  auto socks = ProxyUriToProxyServer("foo:777", ProxyServer::SCHEME_SOCKS5);

  // Single proxy chain is valid.
  EXPECT_TRUE(ProxyChain({https1}).IsValid());

  // Invalid Chains.
  //
  // If multi-proxy chain support is disabled, any chain greater
  // than length 1 is considered invalid. If multi-proxy support is enabled AND
  // QUIC proxy support is enabled, these chains remain invalid due to the
  // sequence of schemes.
  EXPECT_FALSE(ProxyChain({https1, quic2}).IsValid());
  EXPECT_FALSE(ProxyChain({https1, https2, quic1, quic2}).IsValid());
  // Multi-hop ProxyChains cannot contain a SOCKS server. Only QUIC and HTTPS.
  EXPECT_FALSE(ProxyChain({socks, https1}).IsValid());
  EXPECT_FALSE(ProxyChain({socks, https1, https2}).IsValid());
  EXPECT_FALSE(ProxyChain({https1, socks}).IsValid());
  EXPECT_FALSE(ProxyChain({https1, https2, socks}).IsValid());

  // IP protection accepts chains with SCHEME_QUIC and/or multi-proxy chains
  EXPECT_TRUE(ProxyChain::ForIpProtection({https1}).IsValid());
  EXPECT_TRUE(ProxyChain::ForIpProtection({quic1}).IsValid());
  EXPECT_TRUE(ProxyChain::ForIpProtection({https1, https2}).IsValid());
  EXPECT_TRUE(ProxyChain::ForIpProtection({quic1, https1}).IsValid());
  EXPECT_TRUE(
      ProxyChain::ForIpProtection({quic1, quic2, https1, https2}).IsValid());

  // QUIC cannot follow HTTPS proxy server.
  EXPECT_FALSE(ProxyChain::ForIpProtection({https1, quic2}).IsValid());
  EXPECT_FALSE(
      ProxyChain::ForIpProtection({https1, https2, quic1, quic2}).IsValid());
  // Socks proxy server is not valid for multi-proxy chain.
  EXPECT_FALSE(ProxyChain::ForIpProtection({socks, https1}).IsValid());
  EXPECT_FALSE(ProxyChain::ForIpProtection({socks, https1, https2}).IsValid());
  EXPECT_FALSE(ProxyChain::ForIpProtection({https1, socks}).IsValid());
  EXPECT_FALSE(ProxyChain::ForIpProtection({https1, https2, socks}).IsValid());

#if !BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
  bool multi_proxy_chain_supported = false;
#else  // BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS)
  bool multi_proxy_chain_supported = true;
#endif
  // Multi-proxy chains are only supported in debug mode.
  EXPECT_EQ(ProxyChain({https1, https2}).IsValid(),
            multi_proxy_chain_supported);

#if !BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
  bool is_quic_supported = false;
#else  // BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
  bool is_quic_supported = true;
#endif
  // Multi-proxy chains are only supported in debug mode.
  EXPECT_EQ(ProxyChain({quic1}).IsValid(), is_quic_supported);

// If quic proxy support is enabled AND multi-proxy chain support is
// enabled, the following chains are valid. Otherwise, they are invalid.
#if !BUILDFLAG(ENABLE_BRACKETED_PROXY_URIS) || \
    !BUILDFLAG(ENABLE_QUIC_PROXY_SUPPORT)
  bool is_multi_proxy_quic_supported = false;
#else
  bool is_multi_proxy_quic_supported = true;
#endif
  EXPECT_EQ(ProxyChain({quic1, https1}).IsValid(),
            is_multi_proxy_quic_supported);
  EXPECT_EQ(ProxyChain({quic1, quic2, https1, https2}).IsValid(),
            is_multi_proxy_quic_supported);

  // `ProxyChain::WithOpaqueData` should not crash when the proxy server list
  // is invalid.
  EXPECT_FALSE(ProxyChain::WithOpaqueData({https1, quic2}, /*opaque_data=*/123)
                   .IsValid());
}

TEST(ProxyChainTest, Unequal) {
  // Ordered proxy chains.
  std::vector<ProxyChain> proxy_chain_list = {
      ProxyChain::Direct(),
      ProxyUriToProxyChain("foo:333", ProxyServer::SCHEME_HTTP),
      ProxyUriToProxyChain("foo:444", ProxyServer::SCHEME_HTTP),
      ProxyChain({ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS),
                  ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_HTTPS)}),
      ProxyUriToProxyChain("socks4://foo:33", ProxyServer::SCHEME_SOCKS4),
      ProxyUriToProxyChain("http://foo:33", ProxyServer::SCHEME_HTTP),
      ProxyChain({ProxyUriToProxyChain("bar:33", ProxyServer::SCHEME_HTTP)}),
      ProxyChain::ForIpProtection(
          {ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS),
           ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_HTTPS)})};

  // Unordered proxy chains.
  std::set<ProxyChain> proxy_chain_set(proxy_chain_list.begin(),
                                       proxy_chain_list.end());

  // Initial proxy chain list and set are equal.
  ASSERT_EQ(proxy_chain_list.size(), proxy_chain_set.size());

  for (const ProxyChain& proxy_chain1 : proxy_chain_list) {
    auto proxy_chain2 = proxy_chain_set.begin();
    // Chain set entries less than `proxy_chain1`.
    while (*proxy_chain2 < proxy_chain1) {
      EXPECT_TRUE(*proxy_chain2 < proxy_chain1);
      EXPECT_FALSE(proxy_chain1 < *proxy_chain2);
      EXPECT_FALSE(*proxy_chain2 == proxy_chain1);
      EXPECT_FALSE(proxy_chain1 == *proxy_chain2);
      ++proxy_chain2;
    }

    // Chain set entry for `proxy_chain1`.
    EXPECT_FALSE(*proxy_chain2 < proxy_chain1);
    EXPECT_FALSE(proxy_chain1 < *proxy_chain2);
    EXPECT_TRUE(*proxy_chain2 == proxy_chain1);
    EXPECT_TRUE(proxy_chain1 == *proxy_chain2);
    ++proxy_chain2;

    // Chain set entries greater than `proxy_chain1`.
    while (proxy_chain2 != proxy_chain_set.end() &&
           proxy_chain1 < *proxy_chain2) {
      EXPECT_FALSE(*proxy_chain2 < proxy_chain1);
      EXPECT_TRUE(proxy_chain1 < *proxy_chain2);
      EXPECT_FALSE(*proxy_chain2 == proxy_chain1);
      EXPECT_FALSE(proxy_chain1 == *proxy_chain2);
      ++proxy_chain2;
    }
    ASSERT_EQ(proxy_chain2, proxy_chain_set.end());
  }
}

TEST(ProxyChainTest, Equal) {
  ProxyServer proxy_server =
      ProxyUriToProxyServer("foo:11", ProxyServer::SCHEME_HTTP);

  ProxyChain proxy_chain1 = ProxyChain(proxy_server);
  ProxyChain proxy_chain2 = ProxyChain(std::vector<ProxyServer>{proxy_server});
  ProxyChain proxy_chain3 =
      ProxyChain(ProxyServer::SCHEME_HTTP, HostPortPair("foo", 11));

  EXPECT_FALSE(proxy_chain1 < proxy_chain2);
  EXPECT_FALSE(proxy_chain2 < proxy_chain1);
  EXPECT_TRUE(proxy_chain2 == proxy_chain1);
  EXPECT_TRUE(proxy_chain2 == proxy_chain1);

  EXPECT_FALSE(proxy_chain2 < proxy_chain3);
  EXPECT_FALSE(proxy_chain3 < proxy_chain2);
  EXPECT_TRUE(proxy_chain3 == proxy_chain2);
  EXPECT_TRUE(proxy_chain3 == proxy_chain2);
}

TEST(ProxyChainTest, PickleDirect) {
  ProxyChain proxy_chain = ProxyChain::Direct();
  base::Pickle pickle;
  proxy_chain.Persist(&pickle);
  base::PickleIterator iter(pickle);
  std::optional<ProxyChain> proxy_chain_from_pickle =
      ProxyChain::InitFromPickle(iter);
  ASSERT_TRUE(proxy_chain_from_pickle);
  EXPECT_EQ(proxy_chain, *proxy_chain_from_pickle);
}

TEST(ProxyChainTest, PickleDirectIpProtection) {
  ProxyChain proxy_chain =
      ProxyChain::ForIpProtection(/*proxy_server_list=*/{});
  base::Pickle pickle;
  proxy_chain.Persist(&pickle);
  base::PickleIterator iter(pickle);
  std::optional<ProxyChain> proxy_chain_from_pickle =
      ProxyChain::InitFromPickle(iter);
  ASSERT_TRUE(proxy_chain_from_pickle);
  EXPECT_EQ(proxy_chain, *proxy_chain_from_pickle);
  EXPECT_TRUE(proxy_chain_from_pickle->is_for_ip_protection());
  EXPECT_TRUE(proxy_chain_from_pickle->is_direct());
}

TEST(ProxyChainTest, PickleOneProxy) {
  ProxyChain proxy_chain =
      ProxyChain(ProxyUriToProxyServer("foo:11", ProxyServer::SCHEME_HTTPS));
  base::Pickle pickle;
  proxy_chain.Persist(&pickle);
  base::PickleIterator iter(pickle);
  std::optional<ProxyChain> proxy_chain_from_pickle =
      ProxyChain::InitFromPickle(iter);
  ASSERT_TRUE(proxy_chain_from_pickle);
  EXPECT_EQ(proxy_chain, *proxy_chain_from_pickle);
}

TEST(ProxyChainTest, PickleOneProxyIpProtection) {
  ProxyChain proxy_chain = ProxyChain::ForIpProtection(
      {ProxyUriToProxyServer("foo:11", ProxyServer::SCHEME_HTTPS)});
  base::Pickle pickle;
  proxy_chain.Persist(&pickle);
  base::PickleIterator iter(pickle);
  std::optional<ProxyChain> proxy_chain_from_pickle =
      ProxyChain::InitFromPickle(iter);
  ASSERT_TRUE(proxy_chain_from_pickle);
  EXPECT_EQ(proxy_chain, *proxy_chain_from_pickle);
}

TEST(ProxyChainTest, UnpickleInvalidProxy) {
  ProxyServer invalid_proxy_server;
  // Manually pickle a proxy chain with an invalid proxy server.
  base::Pickle pickle;
  pickle.WriteInt(ProxyChain::kNotIpProtectionChainId);
  pickle.WriteInt(1);  // Length of the chain
  invalid_proxy_server.Persist(&pickle);

  base::PickleIterator iter(pickle);
  // Unpickling should fail and leave us with an invalid proxy chain.
  EXPECT_FALSE(ProxyChain::InitFromPickle(iter));
  // Make sure that we unpickled the invalid proxy server.
  EXPECT_TRUE(iter.ReachedEnd());
}

// Same as above, but with an IP Protection chain ID. For consistency we'd like
// the invalid proxy chain to match the default constructed one even if
// unpickling fails for IP Protection chains.
TEST(ProxyChainTest, UnpickleInvalidProxyIpProtection) {
  ProxyServer invalid_proxy_server;
  base::Pickle pickle;
  pickle.WriteInt(ProxyChain::kMaxIpProtectionChainId);
  pickle.WriteInt(1);  // Length of the chain
  invalid_proxy_server.Persist(&pickle);

  base::PickleIterator iter(pickle);
  // Unpickling should fail and leave us with an invalid proxy chain.
  EXPECT_FALSE(ProxyChain::InitFromPickle(iter));
  // Make sure that we unpickled the invalid proxy server.
  EXPECT_TRUE(iter.ReachedEnd());
}

// Multi-proxy chains that are not for Ip Protection are not allowed in release
// builds. If created, it should be considered invalid.
TEST(ProxyChainTest, MultiProxyChainNotForIpProtectionInvalidProxyChain) {
  if (kAreNonIppMultiProxyChainsValid) {
    GTEST_SKIP()
        << "Only relevant when non-ipp multi-proxy chains are invalid.";
  }
  ProxyChain invalid_chain =
      ProxyChain({ProxyUriToProxyServer("foo:11", ProxyServer::SCHEME_HTTPS),
                  ProxyUriToProxyServer("hoo:11", ProxyServer::SCHEME_HTTPS)});

  EXPECT_FALSE(invalid_chain.IsValid());
}
TEST(ProxyChainTest, MultiProxyChain) {
  if (!kAreNonIppMultiProxyChainsValid) {
    GTEST_SKIP() << "Only relevant when non-ipp multi-proxy chains are valid.";
  }

  auto proxy_server1 =
      ProxyUriToProxyServer("foo:333", ProxyServer::SCHEME_HTTPS);
  auto proxy_server2 =
      ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS);
  auto proxy_server3 =
      ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS);

  std::vector<ProxyServer> proxy_servers = {proxy_server1, proxy_server2,
                                            proxy_server3};
  auto proxy = ProxyChain(proxy_servers);

  EXPECT_FALSE(proxy.is_direct());
  EXPECT_FALSE(proxy.is_single_proxy());
  EXPECT_TRUE(proxy.is_multi_proxy());
  ASSERT_EQ(proxy.proxy_servers(), proxy_servers);
  ASSERT_EQ(proxy.length(), 3u);
  ASSERT_EQ(proxy.GetProxyServer(0), proxy_server1);
  ASSERT_EQ(proxy.GetProxyServer(1), proxy_server2);
  ASSERT_EQ(proxy.GetProxyServer(2), proxy_server3);

  // Ensure that proxy chains are equal even if one is for IP Protection.
  auto regular_proxy_chain = ProxyChain({proxy_server1, proxy_server2});
  auto ip_protection_proxy_chain =
      ProxyChain::ForIpProtection({proxy_server1, proxy_server2});
  EXPECT_TRUE(ip_protection_proxy_chain.is_for_ip_protection());
  EXPECT_EQ(regular_proxy_chain.proxy_servers(),
            ip_protection_proxy_chain.proxy_servers());
}

TEST(ProxyChainTest, MultiProxyChainsCanBeConvertedToForIpProtection) {
  if (!kAreNonIppMultiProxyChainsValid) {
    GTEST_SKIP() << "Only relevant when non-ipp multi-proxy chains are valid.";
  }

  ProxyChain proxy_chain =
      ProxyChain({ProxyUriToProxyServer("foo:555", ProxyServer::SCHEME_HTTPS),
                  ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_HTTPS)});
  ProxyChain copied_proxy_chain = proxy_chain;

  // Assert the proxy chain is currently not for ip protection.
  EXPECT_FALSE(proxy_chain.is_for_ip_protection());
  EXPECT_EQ(proxy_chain.ip_protection_chain_id(),
            ProxyChain::kNotIpProtectionChainId);

  // Convert proxy_chain to be for IP protection.
  proxy_chain =
      ProxyChain::ForIpProtection(std::move(proxy_chain.proxy_servers()));

  // Assert proxy_chain now shows it is for IP protection while copied proxy
  // chain still isn't.
  EXPECT_TRUE(proxy_chain.is_for_ip_protection());
  EXPECT_EQ(proxy_chain.ip_protection_chain_id(),
            ProxyChain::kDefaultIpProtectionChainId);
  EXPECT_FALSE(copied_proxy_chain.is_for_ip_protection());
  EXPECT_EQ(copied_proxy_chain.ip_protection_chain_id(),
            ProxyChain::kNotIpProtectionChainId);

  // Ensure servers contained are still equal.
  EXPECT_EQ(proxy_chain.proxy_servers(), copied_proxy_chain.proxy_servers());
}

TEST(ProxyChainTest, PickleTwoProxies) {
  if (!kAreNonIppMultiProxyChainsValid) {
    GTEST_SKIP() << "Only relevant when non-ipp multi-proxy chains are valid.";
  }

  ProxyChain proxy_chain =
      ProxyChain({ProxyUriToProxyServer("foo:11", ProxyServer::SCHEME_HTTPS),
                  ProxyUriToProxyServer("foo:22", ProxyServer::SCHEME_HTTPS)});
  base::Pickle pickle;
  proxy_chain.Persist(&pickle);
  base::PickleIterator iter(pickle);
  std::optional<ProxyChain> proxy_chain_from_pickle =
      ProxyChain::InitFromPickle(iter);
  ASSERT_TRUE(proxy_chain_from_pickle);
  EXPECT_EQ(proxy_chain, *proxy_chain_from_pickle);
}

TEST(ProxyChainTest, UnpickleTwoProxiesIpProtection) {
  ProxyChain proxy_chain = ProxyChain::ForIpProtection(
      {ProxyUriToProxyServer("foo:11", ProxyServer::SCHEME_HTTPS),
       ProxyUriToProxyServer("foo:22", ProxyServer::SCHEME_HTTPS)});

  base::Pickle pickle;
  proxy_chain.Persist(&pickle);
  base::PickleIterator iter(pickle);
  std::optional<ProxyChain> proxy_chain_from_pickle =
      ProxyChain::InitFromPickle(iter);
  ASSERT_TRUE(proxy_chain_from_pickle);
  EXPECT_EQ(proxy_chain, *proxy_chain_from_pickle);
}

TEST(ProxyChainTest, GetHistogramSuffix) {
  // Test IP Protection chains.

  // Test direct IPP chain.
  EXPECT_EQ(ProxyChain::ForIpProtection({}).GetHistogramSuffix(), "Chain0");

  auto https_server =
      ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_HTTPS);
  auto quic_server = ProxyUriToProxyServer("foo:666", ProxyServer::SCHEME_QUIC);

  // Test single-proxy IPP chains.
  EXPECT_EQ(ProxyChain::ForIpProtection({https_server}).GetHistogramSuffix(),
            "Chain0.HTTPS");
  EXPECT_EQ(ProxyChain::ForIpProtection({https_server}, 2).GetHistogramSuffix(),
            "Chain2.HTTPS");
  EXPECT_EQ(ProxyChain::ForIpProtection({quic_server}).GetHistogramSuffix(),
            "Chain0.QUIC");
  EXPECT_EQ(ProxyChain::ForIpProtection({quic_server}, 3).GetHistogramSuffix(),
            "Chain3.QUIC");

  // Test multi-proxy IPP chains.
  EXPECT_EQ(ProxyChain::ForIpProtection({https_server, https_server})
                .GetHistogramSuffix(),
            "Chain0.HTTPS");
  EXPECT_EQ(ProxyChain::ForIpProtection({quic_server, quic_server})
                .GetHistogramSuffix(),
            "Chain0.QUIC");

  // Test non-IP Protection chains.

  // Test direct non-IPP chain.
  EXPECT_EQ(ProxyChain::Direct().GetHistogramSuffix(), "Direct");

  // Test single-proxy non-IPP chains.
  EXPECT_EQ(ProxyChain({https_server}).GetHistogramSuffix(), "HTTPS");

  auto socks_server =
      ProxyUriToProxyServer("foo:444", ProxyServer::SCHEME_SOCKS5);
  EXPECT_EQ(ProxyChain({socks_server}).GetHistogramSuffix(), "SOCKS5");

  if (kAreNonIppMultiProxyChainsValid) {
    // Test multi-proxy non-IPP chains.
    EXPECT_EQ(ProxyChain({https_server, https_server}).GetHistogramSuffix(),
              "HTTPS");
  }
}

}  // namespace

}  // namespace net
