// Copyright 2019 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/socket/client_socket_pool.h"

#include <string>
#include <vector>

#include "base/test/scoped_feature_list.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/privacy_mode.h"
#include "net/base/schemeful_site.h"
#include "net/dns/public/secure_dns_policy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"

namespace net {

namespace {

TEST(ClientSocketPool, GroupIdOperators) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kPartitionConnectionsByNetworkIsolationKey);

  // Each of these lists is in "<" order, as defined by Group::operator< on the
  // corresponding field.

  const uint16_t kPorts[] = {
      80,
      81,
      443,
  };

  const char* kSchemes[] = {
      url::kHttpScheme,
      url::kHttpsScheme,
  };

  const char* kHosts[] = {
      "a",
      "b",
      "c",
  };

  const PrivacyMode kPrivacyModes[] = {
      PrivacyMode::PRIVACY_MODE_DISABLED,
      PrivacyMode::PRIVACY_MODE_ENABLED,
  };

  const SchemefulSite kSiteA(GURL("http://a.test/"));
  const SchemefulSite kSiteB(GURL("http://b.test/"));
  const NetworkAnonymizationKey kNetworkAnonymizationKeys[] = {
      NetworkAnonymizationKey::CreateSameSite(kSiteA),
      NetworkAnonymizationKey::CreateSameSite(kSiteB),
  };

  const SecureDnsPolicy kDisableSecureDnsValues[] = {SecureDnsPolicy::kAllow,
                                                     SecureDnsPolicy::kDisable};

  const handles::NetworkHandle kNetworkHandles[] = {
      handles::kInvalidNetworkHandle,
      // Some arbitrary network handle.
      1,
      // Another arbitrary network handle. This makes sure that we're not just
      // comparing handle vs invalid handle, but also distinct valid handles.
      23,
  };

  // All previously created |group_ids|. They should all be less than the
  // current group under consideration.
  std::vector<ClientSocketPool::GroupId> group_ids;

  // Iterate through all sets of group ids, from least to greatest.
  for (const auto& port : kPorts) {
    SCOPED_TRACE(port);
    for (const char* scheme : kSchemes) {
      SCOPED_TRACE(scheme);
      for (const char* host : kHosts) {
        SCOPED_TRACE(host);
        for (const auto& privacy_mode : kPrivacyModes) {
          SCOPED_TRACE(privacy_mode);
          for (const auto& network_anonymization_key :
               kNetworkAnonymizationKeys) {
            SCOPED_TRACE(network_anonymization_key.ToDebugString());
            for (const auto& secure_dns_policy : kDisableSecureDnsValues) {
              for (const auto& network_handle : kNetworkHandles) {
                SCOPED_TRACE(network_handle);
                ClientSocketPool::GroupId group_id(
                    url::SchemeHostPort(scheme, host, port), privacy_mode,
                    network_anonymization_key, secure_dns_policy,
                    /*disable_cert_network_fetches=*/false, network_handle);
                for (const auto& lower_group_id : group_ids) {
                  EXPECT_FALSE(lower_group_id == group_id);
                  EXPECT_TRUE(lower_group_id < group_id);
                  EXPECT_FALSE(group_id < lower_group_id);
                }

                group_ids.push_back(group_id);

                // Compare |group_id| to itself. Use two different copies of
                // |group_id|'s value, since to protect against bugs where an
                // object only equals itself.
                EXPECT_TRUE(group_ids.back() == group_id);
                EXPECT_FALSE(group_ids.back() < group_id);
                EXPECT_FALSE(group_id < group_ids.back());
              }
            }
          }
        }
      }
    }
  }
}

TEST(ClientSocketPool, GroupIdToString) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kPartitionConnectionsByNetworkIsolationKey);

  EXPECT_EQ("http://foo <null>",
            ClientSocketPool::GroupId(
                url::SchemeHostPort(url::kHttpScheme, "foo", 80),
                PrivacyMode::PRIVACY_MODE_DISABLED, NetworkAnonymizationKey(),
                SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
                handles::kInvalidNetworkHandle)
                .ToString());
  EXPECT_EQ("http://bar:443 <null>",
            ClientSocketPool::GroupId(
                url::SchemeHostPort(url::kHttpScheme, "bar", 443),
                PrivacyMode::PRIVACY_MODE_DISABLED, NetworkAnonymizationKey(),
                SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
                handles::kInvalidNetworkHandle)
                .ToString());
  EXPECT_EQ("pm/http://bar <null>",
            ClientSocketPool::GroupId(
                url::SchemeHostPort(url::kHttpScheme, "bar", 80),
                PrivacyMode::PRIVACY_MODE_ENABLED, NetworkAnonymizationKey(),
                SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
                handles::kInvalidNetworkHandle)
                .ToString());

  EXPECT_EQ("https://foo:80 <null>",
            ClientSocketPool::GroupId(
                url::SchemeHostPort(url::kHttpsScheme, "foo", 80),
                PrivacyMode::PRIVACY_MODE_DISABLED, NetworkAnonymizationKey(),
                SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
                handles::kInvalidNetworkHandle)
                .ToString());
  EXPECT_EQ("https://bar <null>",
            ClientSocketPool::GroupId(
                url::SchemeHostPort(url::kHttpsScheme, "bar", 443),
                PrivacyMode::PRIVACY_MODE_DISABLED, NetworkAnonymizationKey(),
                SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
                handles::kInvalidNetworkHandle)
                .ToString());
  EXPECT_EQ("pm/https://bar:80 <null>",
            ClientSocketPool::GroupId(
                url::SchemeHostPort(url::kHttpsScheme, "bar", 80),
                PrivacyMode::PRIVACY_MODE_ENABLED, NetworkAnonymizationKey(),
                SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
                handles::kInvalidNetworkHandle)
                .ToString());

  EXPECT_EQ("https://foo <https://foo.test cross_site>",
            ClientSocketPool::GroupId(
                url::SchemeHostPort(url::kHttpsScheme, "foo", 443),
                PrivacyMode::PRIVACY_MODE_DISABLED,
                NetworkAnonymizationKey::CreateCrossSite(
                    SchemefulSite(GURL("https://foo.test"))),
                SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
                handles::kInvalidNetworkHandle)
                .ToString());

  EXPECT_EQ(
      "dsd/pm/https://bar:80 <null>",
      ClientSocketPool::GroupId(
          url::SchemeHostPort(url::kHttpsScheme, "bar", 80),
          PrivacyMode::PRIVACY_MODE_ENABLED, NetworkAnonymizationKey(),
          SecureDnsPolicy::kDisable, /*disable_cert_network_fetches=*/false,
          handles::kInvalidNetworkHandle)
          .ToString());

  EXPECT_EQ("disable_cert_network_fetches/pm/https://bar:80 <null>",
            ClientSocketPool::GroupId(
                url::SchemeHostPort(url::kHttpsScheme, "bar", 80),
                PrivacyMode::PRIVACY_MODE_ENABLED, NetworkAnonymizationKey(),
                SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/true,
                handles::kInvalidNetworkHandle)
                .ToString());

  EXPECT_EQ("target_network=1/https://bar:80 <null>",
            ClientSocketPool::GroupId(
                url::SchemeHostPort(url::kHttpsScheme, "bar", 80),
                PrivacyMode::PRIVACY_MODE_DISABLED, NetworkAnonymizationKey(),
                SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
                /*target_network=*/1)
                .ToString());
}

TEST(ClientSocketPool, SplitHostCacheByNetworkIsolationKeyDisabled) {
  const SchemefulSite kSiteFoo(GURL("https://foo.com"));
  const SchemefulSite kSiteBar(GURL("https://bar.com"));
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kPartitionConnectionsByNetworkIsolationKey);

  ClientSocketPool::GroupId group_id1(
      url::SchemeHostPort(url::kHttpsScheme, "foo", 443),
      PrivacyMode::PRIVACY_MODE_DISABLED,
      NetworkAnonymizationKey::CreateSameSite(kSiteFoo),
      SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
      handles::kInvalidNetworkHandle);

  ClientSocketPool::GroupId group_id2(
      url::SchemeHostPort(url::kHttpsScheme, "foo", 443),
      PrivacyMode::PRIVACY_MODE_DISABLED,
      NetworkAnonymizationKey::CreateSameSite(kSiteBar),
      SecureDnsPolicy::kAllow, /*disable_cert_network_fetches=*/false,
      handles::kInvalidNetworkHandle);

  EXPECT_TRUE(group_id1.network_anonymization_key().IsEmpty());
  EXPECT_TRUE(group_id2.network_anonymization_key().IsEmpty());
  EXPECT_EQ(group_id1.network_anonymization_key(),
            group_id2.network_anonymization_key());
  EXPECT_EQ(group_id1, group_id2);

  EXPECT_EQ("https://foo", group_id1.ToString());
  EXPECT_EQ("https://foo", group_id2.ToString());
}

}  // namespace

}  // namespace net
