// Copyright 2012 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/quic/quic_session_pool.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory_coordinator/memory_coordinator_features.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "crypto/openssl_util.h"
#include "net/base/address_list.h"
#include "net/base/connection_endpoint_metadata.h"
#include "net/base/features.h"
#include "net/base/http_user_agent_settings.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/network_handle.h"
#include "net/base/proxy_delegate.h"
#include "net/base/reconnect_notifier.h"
#include "net/base/session_usage.h"
#include "net/base/trace_constants.h"
#include "net/base/url_util.h"
#include "net/cert/cert_verifier.h"
#include "net/dns/host_resolver.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/http/http_proxy_connect_job.h"
#include "net/log/net_log.h"
#include "net/log/net_log_capture_mode.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_source_type.h"
#include "net/nqe/network_quality_estimator.h"
#include "net/quic/address_utils.h"
#include "net/quic/crypto/proof_verifier_chromium.h"
#include "net/quic/properties_based_quic_server_info.h"
#include "net/quic/quic_chromium_alarm_factory.h"
#include "net/quic/quic_chromium_client_session.h"
#include "net/quic/quic_chromium_connection_helper.h"
#include "net/quic/quic_chromium_packet_reader.h"
#include "net/quic/quic_chromium_packet_writer.h"
#include "net/quic/quic_context.h"
#include "net/quic/quic_crypto_client_stream_factory.h"
#include "net/quic/quic_server_info.h"
#include "net/quic/quic_session_attempt_manager.h"
#include "net/quic/quic_session_key.h"
#include "net/quic/quic_session_pool_async_dns_job.h"
#include "net/quic/quic_session_pool_direct_job.h"
#include "net/quic/quic_session_pool_job.h"
#include "net/quic/quic_session_pool_proxy_job.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/next_proto.h"
#include "net/socket/socket_performance_watcher.h"
#include "net/socket/socket_performance_watcher_factory.h"
#include "net/socket/udp_client_socket.h"
#include "net/spdy/multiplexed_session_creation_initiator.h"
#include "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_protocol.h"
#include "net/third_party/quiche/src/quiche/quic/core/crypto/null_decrypter.h"
#include "net/third_party/quiche/src/quiche/quic/core/crypto/proof_verifier.h"
#include "net/third_party/quiche/src/quiche/quic/core/crypto/quic_random.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_clock.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_connection.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_tag.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
#include "net/third_party/quiche/src/quiche/quic/platform/api/quic_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "third_party/boringssl/src/include/openssl/aead.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/application_status_listener.h"
#include "base/android/pre_freeze_background_memory_trimmer.h"
#include "base/memory/post_delayed_memory_reduction_task.h"
#endif

namespace net {

namespace {

enum InitialRttEstimateSource {
  INITIAL_RTT_DEFAULT,
  INITIAL_RTT_CACHED,
  INITIAL_RTT_2G,
  INITIAL_RTT_3G,
  INITIAL_RTT_SOURCE_MAX,
};

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum FindMatchingIpSessionResult {
  MATCHING_IP_SESSION_FOUND,
  CAN_POOL_BUT_DIFFERENT_IP,
  CANNOT_POOL_WITH_EXISTING_SESSIONS,
  POOLED_WITH_DIFFERENT_IP_SESSION,
  FIND_MATCHING_IP_SESSION_RESULT_MAX
};

// Whether we have partial match for `QuicSessionKey` with the same `ServerId`.
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(QuicSessionKeyPartialMatchResult)
enum QuicSessionKeyPartialMatchResult {
  kNoMatch,
  kMatchedToActiveSession,
  kMatchedToActiveJob,
  kMaxValue = kMatchedToActiveJob
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:QuicSessionKeyPartialMatchResult)

// Represents which field in `QuicSessionKey` was different among two keys with
// the same `ServerId`.
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(QuicSessionKeyMismatchedField)
enum class QuicSessionKeyMismatchedField {
  kPrivacyMode,
  kSocketTag,
  kProxyChain,
  kSessionUsage,
  kNetworkAnonymizationKey,
  kSecureDnsPolicy,
  kRequireDNSHttpsAlpn,
  kMaxValue = kRequireDNSHttpsAlpn
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:QuicSessionKeyMismatchedField)

// Represents which combination of field in `QuicSessionKey` was different
// among two keys with the same `ServerId`. We only look at the commonly
// mismatched fields to avoid combination explosion.
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(QuicSessionKeyMismatchedFieldCombination)
enum class QuicSessionKeyMismatchedFieldCombination {
  kUnknownCombination,
  kPrivacyMode,
  kNetworkAnonymizationKey,
  kPrivacyModeNetworkAnonymizationKey,
  kRequireDNSHttpsAlpn,
  kRequireDNSHttpsAlpnPrivacyMode,
  kRequireDNSHttpsNetworkAnonymizationKey,
  kPrivacyModeAndNetworkAnonymizationKeyRequireDNSHttpsAlpn,
  kMaxValue = kPrivacyModeAndNetworkAnonymizationKeyRequireDNSHttpsAlpn
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:QuicSessionKeyMismatchedFieldCombination)

std::string QuicPlatformNotificationToString(
    QuicPlatformNotification notification) {
  switch (notification) {
    case NETWORK_CONNECTED:
      return "OnNetworkConnected";
    case NETWORK_MADE_DEFAULT:
      return "OnNetworkMadeDefault";
    case NETWORK_DISCONNECTED:
      return "OnNetworkDisconnected";
    case NETWORK_SOON_TO_DISCONNECT:
      return "OnNetworkSoonToDisconnect";
    case NETWORK_IP_ADDRESS_CHANGED:
      return "OnIPAddressChanged";
    default:
      QUICHE_NOTREACHED();
      break;
  }
  return "InvalidNotification";
}

const char* AllActiveSessionsGoingAwayReasonToString(
    AllActiveSessionsGoingAwayReason reason) {
  switch (reason) {
    case kClockSkewDetected:
      return "ClockSkewDetected";
    case kIPAddressChanged:
      return "IPAddressChanged";
    case kCertDBChanged:
      return "CertDBChanged";
    case kCertVerifierChanged:
      return "CertVerifierChanged";
  }
}

void HistogramCreateSessionFailure(enum CreateSessionFailure error) {
  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.CreationError", error,
                            CREATION_ERROR_MAX);
}

void LogFindMatchingIpSessionResult(const NetLogWithSource& net_log,
                                    FindMatchingIpSessionResult result,
                                    QuicChromiumClientSession* session,
                                    const url::SchemeHostPort& destination) {
  NetLogEventType type =
      NetLogEventType::QUIC_SESSION_POOL_CANNOT_POOL_WITH_EXISTING_SESSIONS;
  switch (result) {
    case MATCHING_IP_SESSION_FOUND:
      type = NetLogEventType::QUIC_SESSION_POOL_MATCHING_IP_SESSION_FOUND;
      break;
    case POOLED_WITH_DIFFERENT_IP_SESSION:
      type =
          NetLogEventType::QUIC_SESSION_POOL_POOLED_WITH_DIFFERENT_IP_SESSION;
      break;
    case CAN_POOL_BUT_DIFFERENT_IP:
      type = NetLogEventType::QUIC_SESSION_POOL_CAN_POOL_BUT_DIFFERENT_IP;
      break;
    case CANNOT_POOL_WITH_EXISTING_SESSIONS:
    case FIND_MATCHING_IP_SESSION_RESULT_MAX:
      break;
  }
  net_log.AddEvent(type, [&] {
    base::DictValue dict;
    dict.Set("destination", destination.Serialize());
    if (session != nullptr) {
      session->net_log().source().AddToEventParameters(dict);
    }
    return dict;
  });
  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.FindMatchingIpSessionResult",
                            result, FIND_MATCHING_IP_SESSION_RESULT_MAX);
  if (IsGoogleHost(destination.host()) &&
      !destination.host().ends_with(".googlevideo.com")) {
    UMA_HISTOGRAM_ENUMERATION(
        "Net.QuicSession.FindMatchingIpSessionResultGoogle", result,
        FIND_MATCHING_IP_SESSION_RESULT_MAX);
  }
}

void SetInitialRttEstimate(base::TimeDelta estimate,
                           enum InitialRttEstimateSource source,
                           quic::QuicConfig* config) {
  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.InitialRttEsitmateSource", source,
                            INITIAL_RTT_SOURCE_MAX);
  if (estimate != base::TimeDelta()) {
    config->SetInitialRoundTripTimeUsToSend(
        base::checked_cast<uint64_t>(estimate.InMicroseconds()));
  }
}

// An implementation of quic::QuicCryptoClientConfig::ServerIdFilter that wraps
// an |origin_filter|.
class ServerIdOriginFilter
    : public quic::QuicCryptoClientConfig::ServerIdFilter {
 public:
  explicit ServerIdOriginFilter(
      const base::RepeatingCallback<bool(const GURL&)> origin_filter)
      : origin_filter_(origin_filter) {}

  bool Matches(const quic::QuicServerId& server_id) const override {
    if (origin_filter_.is_null()) {
      return true;
    }

    GURL url(base::StringPrintf("%s%s%s:%d", url::kHttpsScheme,
                                url::kStandardSchemeSeparator,
                                server_id.host().c_str(), server_id.port()));
    DCHECK(url.is_valid());
    return origin_filter_.Run(url);
  }

 private:
  const base::RepeatingCallback<bool(const GURL&)> origin_filter_;
};

std::set<std::string> HostsFromSchemeHostPorts(
    const std::set<url::SchemeHostPort>& scheme_host_ports) {
  std::set<std::string> hosts;
  for (const auto& scheme_host_port : scheme_host_ports) {
    hosts.insert(scheme_host_port.host());
  }
  return hosts;
}

void LogUsingExistingSession(const NetLogWithSource& request_net_log,
                             QuicChromiumClientSession* session,
                             const url::SchemeHostPort& destination) {
  request_net_log.AddEvent(
      NetLogEventType::QUIC_SESSION_POOL_USE_EXISTING_SESSION, [&] {
        base::DictValue dict;
        dict.Set("destination", destination.Serialize());
        session->net_log().source().AddToEventParameters(dict);
        return dict;
      });
  session->net_log().AddEventReferencingSource(
      NetLogEventType::
          QUIC_SESSION_POOL_ATTACH_HTTP_STREAM_JOB_TO_EXISTING_SESSION,
      request_net_log.source());
}

void LogSessionKeyMismatch(QuicSessionKeyPartialMatchResult result,
                           const url::SchemeHostPort destination,
                           const QuicSessionKey session_key,
                           const std::optional<QuicSessionKey> active_key) {
  const std::string_view kHistogramBase = "Net.QuicSession.SessionKeyMismatch";
  const std::string histogram_suffix =
      IsGoogleHostWithAlpnH3(destination.host()) ? ".GoogleHost" : "";

  base::UmaHistogramEnumeration(
      base::StrCat({kHistogramBase, histogram_suffix}), result);

  if (result != QuicSessionKeyPartialMatchResult::kNoMatch) {
    CHECK(active_key.has_value());
    int total_mismatch = 0;
    int mismatched_combinations = 0;
    std::string mismatch_field_histogram =
        base::StrCat({kHistogramBase, ".MismatchedField", histogram_suffix});

    // Check and record the fields that are mismatching.
    auto checkAndRecordMismatch =
        [&mismatch_field_histogram, &total_mismatch, &session_key, &active_key](
            auto method, QuicSessionKeyMismatchedField field) {
          if ((session_key.*method)() != (active_key.value().*method)()) {
            total_mismatch++;
            base::UmaHistogramEnumeration(mismatch_field_histogram, field);
            return true;
          }
          return false;
        };
    checkAndRecordMismatch(&QuicSessionKey::socket_tag,
                           QuicSessionKeyMismatchedField::kSocketTag);
    checkAndRecordMismatch(&QuicSessionKey::proxy_chain,
                           QuicSessionKeyMismatchedField::kProxyChain);
    checkAndRecordMismatch(&QuicSessionKey::session_usage,
                           QuicSessionKeyMismatchedField::kSessionUsage);
    checkAndRecordMismatch(&QuicSessionKey::secure_dns_policy,
                           QuicSessionKeyMismatchedField::kSecureDnsPolicy);

    if (checkAndRecordMismatch(&QuicSessionKey::privacy_mode,
                               QuicSessionKeyMismatchedField::kPrivacyMode)) {
      mismatched_combinations |= 1;
    }
    if (checkAndRecordMismatch(
            &QuicSessionKey::network_anonymization_key,
            QuicSessionKeyMismatchedField::kNetworkAnonymizationKey)) {
      mismatched_combinations |= (1 << 1);
    }
    if (checkAndRecordMismatch(
            &QuicSessionKey::require_dns_https_alpn,
            QuicSessionKeyMismatchedField::kRequireDNSHttpsAlpn)) {
      mismatched_combinations |= (1 << 2);
    }

    base::UmaHistogramCounts1000(
        base::StrCat(
            {kHistogramBase, ".TotalMismatchedField", histogram_suffix}),
        total_mismatch);

    base::UmaHistogramEnumeration(
        base::StrCat(
            {kHistogramBase, ".MismatchedFieldCombination", histogram_suffix}),
        static_cast<QuicSessionKeyMismatchedFieldCombination>(
            mismatched_combinations));
  }
}

base::TimeDelta GetAdditionalDelayForMainJob() {
  if (!base::FeatureList::IsEnabled(features::kAdditionalDelayMainJob)) {
    return base::TimeDelta();
  }
  return features::kAdditionalDelay.Get();
}

}  // namespace

QuicSessionRequest::QuicSessionRequest(QuicSessionPool* pool) : pool_(pool) {}

QuicSessionRequest::~QuicSessionRequest() {
  if (pool_ && !callback_.is_null()) {
    pool_->CancelRequest(this);
  }
}

int QuicSessionRequest::Request(
    url::SchemeHostPort destination,
    quic::ParsedQuicVersion quic_version,
    const ProxyChain& proxy_chain,
    std::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
    const HttpUserAgentSettings* http_user_agent_settings,
    SessionUsage session_usage,
    PrivacyMode privacy_mode,
    RequestPriority priority,
    const SocketTag& socket_tag,
    const NetworkAnonymizationKey& network_anonymization_key,
    SecureDnsPolicy secure_dns_policy,
    bool require_dns_https_alpn,
    int cert_verify_flags,
    const GURL& url,
    handles::NetworkHandle target_network,
    const NetLogWithSource& net_log,
    NetErrorDetails* net_error_details,
    MultiplexedSessionCreationInitiator session_creation_initiator,
    std::optional<ConnectionManagementConfig> management_config,
    CompletionOnceCallback failed_on_default_network_callback,
    CompletionOnceCallback callback) {
  DCHECK_EQ(quic_version.IsKnown(), !require_dns_https_alpn);
  DCHECK(net_error_details);
  DCHECK(callback_.is_null());
  DCHECK(host_resolution_callback_.is_null());
  DCHECK(pool_);

  net_error_details_ = net_error_details;
  failed_on_default_network_callback_ =
      std::move(failed_on_default_network_callback);

  // Note that `disable_cert_verification_network_fetches` must be true for
  // proxies to avoid deadlock. See comment on
  // `SSLConfig::disable_cert_verification_network_fetches`.
  bool disable_cert_verification_network_fetches =
      !!(cert_verify_flags & CertVerifier::VERIFY_DISABLE_NETWORK_FETCHES);
  CHECK(session_usage != SessionUsage::kProxy ||
        disable_cert_verification_network_fetches);
  session_key_ =
      QuicSessionKey(HostPortPair::FromURL(url), privacy_mode, proxy_chain,
                     session_usage, socket_tag, network_anonymization_key,
                     secure_dns_policy, require_dns_https_alpn,
                     disable_cert_verification_network_fetches, target_network);
  bool use_dns_aliases = session_usage != SessionUsage::kProxy;

  int rv = pool_->RequestSession(
      session_key_, std::move(destination), quic_version,
      std::move(proxy_annotation_tag), session_creation_initiator,
      management_config, http_user_agent_settings, priority, use_dns_aliases,
      cert_verify_flags, url, net_log, this);
  if (rv == ERR_IO_PENDING) {
    net_log_ = net_log;
    callback_ = std::move(callback);
  } else {
    DCHECK(!expect_on_host_resolution_);
    pool_ = nullptr;
  }

  if (rv == OK) {
    DCHECK(session_);
  }
  return rv;
}

bool QuicSessionRequest::WaitForHostResolution(
    CompletionOnceCallback callback) {
  DCHECK(host_resolution_callback_.is_null());
  if (expect_on_host_resolution_) {
    host_resolution_callback_ = std::move(callback);
  }
  return expect_on_host_resolution_;
}

void QuicSessionRequest::ExpectOnHostResolution() {
  expect_on_host_resolution_ = true;
}

void QuicSessionRequest::OnHostResolutionComplete(
    int rv,
    base::TimeTicks dns_resolution_start_time,
    base::TimeTicks dns_resolution_end_time) {
  DCHECK(expect_on_host_resolution_);
  expect_on_host_resolution_ = false;
  dns_resolution_start_time_ = dns_resolution_start_time;
  dns_resolution_end_time_ = dns_resolution_end_time;
  if (!host_resolution_callback_.is_null()) {
    std::move(host_resolution_callback_).Run(rv);
  }
}

bool QuicSessionRequest::WaitForQuicSessionCreation(
    CompletionOnceCallback callback) {
  DCHECK(create_session_callback_.is_null());
  if (expect_on_quic_session_creation_) {
    create_session_callback_ = std::move(callback);
  }
  return expect_on_quic_session_creation_;
}

void QuicSessionRequest::ExpectQuicSessionCreation() {
  expect_on_quic_session_creation_ = true;
}

void QuicSessionRequest::OnQuicSessionCreationComplete(int rv) {
  // DCHECK(expect_on_quic_session_creation_);
  expect_on_quic_session_creation_ = false;
  if (!create_session_callback_.is_null()) {
    std::move(create_session_callback_).Run(rv);
  }
}

void QuicSessionRequest::OnRequestComplete(int rv) {
  pool_ = nullptr;
  std::move(callback_).Run(rv);
}

void QuicSessionRequest::OnConnectionFailedOnDefaultNetwork() {
  if (!failed_on_default_network_callback_.is_null()) {
    std::move(failed_on_default_network_callback_).Run(OK);
  }
}

base::TimeDelta QuicSessionRequest::GetTimeDelayForWaitingJob() const {
  if (!pool_) {
    return base::TimeDelta();
  }
  return pool_->GetTimeDelayForWaitingJob(session_key_);
}

base::WeakPtr<QuicSessionRequest> QuicSessionRequest::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

void QuicSessionRequest::SetPriority(RequestPriority priority) {
  if (pool_) {
    pool_->SetRequestPriority(this, priority);
  }
}

std::unique_ptr<QuicChromiumClientSession::Handle>
QuicSessionRequest::ReleaseSessionHandle() {
  if (!session_ || !session_->IsConnected()) {
    return nullptr;
  }

  return std::move(session_);
}

void QuicSessionRequest::SetSession(
    std::unique_ptr<QuicChromiumClientSession::Handle> session) {
  session_ = std::move(session);
}

QuicSessionPool::QuicCryptoClientConfigOwner::QuicCryptoClientConfigOwner(
    NetworkAnonymizationKey network_anonymization_key,
    std::unique_ptr<quic::ProofVerifier> proof_verifier,
    std::unique_ptr<quic::QuicClientSessionCache> session_cache,
    size_t max_cache_entries,
    QuicSessionPool* quic_session_pool)
    : network_anonymization_key_(std::move(network_anonymization_key)),
      config_(std::move(proof_verifier), std::move(session_cache)),
      clock_(quic_session_pool->clock_for_testing_
                 ? quic_session_pool->clock_for_testing_.get()
                 : base::DefaultClock::GetInstance()),
      max_cache_entries_(max_cache_entries),
      quic_session_pool_(quic_session_pool) {
  DCHECK(quic_session_pool_);
  memory_pressure_listener_registration_ =
      std::make_unique<base::AsyncMemoryPressureListenerRegistration>(
          FROM_HERE, base::MemoryPressureListenerTag::kQuicSessionPool, this);
  config_.set_preferred_groups(
      quic_session_pool_->ssl_config_service_->GetSSLContextConfig()
          .GetSupportedGroups());
  if (std::vector<uint16_t> client_key_shares =
          quic_session_pool_->ssl_config_service_->GetSSLContextConfig()
              .GetSupportedGroups(/*key_shares_only=*/true);
      !client_key_shares.empty()) {
    config_.set_client_key_shares(std::move(client_key_shares));
  }
  if (quic_session_pool_->ssl_config_service_->GetSSLContextConfig()
          .tls13_cipher_prefer_aes_256) {
    config_.set_ssl_compliance_policy(ssl_compliance_policy_cnsa_202407);
  }
}

QuicSessionPool::QuicCryptoClientConfigOwner::~QuicCryptoClientConfigOwner() {
  DCHECK_EQ(num_refs_, 0);
}

void QuicSessionPool::QuicCryptoClientConfigOwner::OnMemoryPressure(
    base::MemoryPressureLevel memory_pressure_level) {
  quic::SessionCache* session_cache = config_.session_cache();
  if (!session_cache) {
    return;
  }

  uint64_t now_u64 = std::max<int64_t>(0, clock_->Now().ToTimeT());

  // Under the global ignore memory pressure experiment, we evict expired
  // entries instead of ignoring memory pressure completely.
  if (base::FeatureList::IsEnabled(
          features::kIgnoreQuicCryptoConfigMemoryPressure)) {
    if (memory_pressure_level == base::MEMORY_PRESSURE_LEVEL_MODERATE ||
        memory_pressure_level == base::MEMORY_PRESSURE_LEVEL_CRITICAL) {
      session_cache->RemoveExpiredEntries(
          quic::QuicWallTime::FromUNIXSeconds(now_u64));
    }
    return;
  }

  if (network_anonymization_key_.network_isolation_partition() ==
          NetworkIsolationPartition::kDnsOverHttps &&
      base::FeatureList::IsEnabled(
          features::kIgnoreQuicCryptoConfigMemoryPressureForDoh)) {
    // We don't want to clear the session cache used by DNS-over-HTTPS
    // connections because this will almost certainly be needed again soon,
    // and since DNS query completion is in the critical path of every other
    // request without a cached DNS record. Furthermore, the DoH-specific
    // session cache should only ever have a few entries because it's used
    // exclusively for connections to configured DoH server(s).
    return;
  }

  if (base::FeatureList::IsEnabled(base::kStatefulMemoryPressure)) {
    // The memory pressure level might have changed, which potentially changed
    // the memory limit. Enforce the new limit.
    session_cache->UpdateMaxSize(max_cache_entries_ * GetMemoryLimitRatio());
    return;
  }

  switch (memory_pressure_level) {
    case base::MEMORY_PRESSURE_LEVEL_NONE:
      break;
    case base::MEMORY_PRESSURE_LEVEL_MODERATE:
      session_cache->RemoveExpiredEntries(
          quic::QuicWallTime::FromUNIXSeconds(now_u64));
      break;
    case base::MEMORY_PRESSURE_LEVEL_CRITICAL:
      session_cache->Clear();
      break;
  }
}

bool QuicSessionPool::QuicCryptoClientConfigKey::operator==(
    const QuicSessionPool::QuicCryptoClientConfigKey& other) const {
  return Tie() == other.Tie();
}

bool QuicSessionPool::QuicCryptoClientConfigKey::operator<(
    const QuicSessionPool::QuicCryptoClientConfigKey& other) const {
  return Tie() < other.Tie();
}

std::tuple<const NetworkAnonymizationKey&,
           const ProxyChain&,
           const SessionUsage&>
QuicSessionPool::QuicCryptoClientConfigKey::Tie() const {
  return std::tie(network_anonymization_key, proxy_chain, session_usage);
}

QuicSessionPool::CryptoClientConfigHandle::CryptoClientConfigHandle(
    const QuicCryptoClientConfigMap::iterator& map_iterator)
    : map_iterator_(map_iterator) {
  DCHECK_GE(map_iterator_->second->num_refs(), 0);
  map_iterator->second->AddRef();
}

QuicSessionPool::CryptoClientConfigHandle::~CryptoClientConfigHandle() {
  DCHECK_GT(map_iterator_->second->num_refs(), 0);
  map_iterator_->second->ReleaseRef();
  if (map_iterator_->second->num_refs() == 0) {
    map_iterator_->second->quic_session_pool()->OnAllCryptoClientRefReleased(
        map_iterator_);
  }
}

quic::QuicCryptoClientConfig*
QuicSessionPool::CryptoClientConfigHandle::GetConfig() const {
  return map_iterator_->second->config();
}

QuicSessionPool::QuicSessionPool(
    NetLog* net_log,
    HostResolver* host_resolver,
    SSLConfigService* ssl_config_service,
    ClientSocketFactory* client_socket_factory,
    HttpServerProperties* http_server_properties,
    CertVerifier* cert_verifier,
    TransportSecurityState* transport_security_state,
    ProxyDelegate* proxy_delegate,
    SCTAuditingDelegate* sct_auditing_delegate,
    SocketPerformanceWatcherFactory* socket_performance_watcher_factory,
    QuicCryptoClientStreamFactory* quic_crypto_client_stream_factory,
    NetworkQualityEstimator* network_quality_estimator,
    QuicContext* quic_context)
    : net_log_(
          NetLogWithSource::Make(net_log, NetLogSourceType::QUIC_SESSION_POOL)),
      host_resolver_(host_resolver),
      client_socket_factory_(client_socket_factory),
      http_server_properties_(http_server_properties),
      network_quality_estimator_(network_quality_estimator),
      cert_verifier_(cert_verifier),
      transport_security_state_(transport_security_state),
      proxy_delegate_(proxy_delegate),
      sct_auditing_delegate_(sct_auditing_delegate),
      quic_crypto_client_stream_factory_(quic_crypto_client_stream_factory),
      random_generator_(quic_context->random_generator()),
      clock_(quic_context->clock()),
      // TODO(vasilvv): figure out how to avoid having multiple copies of
      // QuicParams.
      params_(*quic_context->params()),
      clock_skew_detector_(base::TimeTicks::Now(), base::Time::Now()),
      socket_performance_watcher_factory_(socket_performance_watcher_factory),
      recent_crypto_config_map_(kMaxRecentCryptoConfigs),
      config_(InitializeQuicConfig(*quic_context->params())),
      ping_timeout_(quic::QuicTime::Delta::FromSeconds(quic::kPingTimeoutSecs)),
      reduced_ping_timeout_(quic::QuicTime::Delta::FromMicroseconds(
          quic_context->params()->reduced_ping_timeout.InMicroseconds())),
      retransmittable_on_wire_timeout_(quic::QuicTime::Delta::FromMicroseconds(
          quic_context->params()
              ->retransmittable_on_wire_timeout.InMicroseconds())),
      yield_after_packets_(kQuicYieldAfterPacketsRead),
      yield_after_duration_(quic::QuicTime::Delta::FromMilliseconds(
          kQuicYieldAfterDurationMilliseconds)),
      default_network_(handles::kInvalidNetworkHandle),
      connectivity_monitor_(default_network_),
      task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      tick_clock_(base::DefaultTickClock::GetInstance()),
      ssl_config_service_(ssl_config_service),
      use_network_anonymization_key_for_crypto_configs_(
          NetworkAnonymizationKey::IsPartitioningEnabled()),
      skip_dns_with_origin_frame_(
          quic_context->params()->skip_dns_with_origin_frame),
      ignore_ip_matching_when_finding_existing_sessions_(
          quic_context->params()
              ->ignore_ip_matching_when_finding_existing_sessions),
      session_attempt_manager_(
          std::make_unique<QuicSessionAttemptManager>(this)) {
  DCHECK(transport_security_state_);
  DCHECK(http_server_properties_);
  if (params_.disable_tls_zero_rtt) {
    SetQuicFlag(quic_disable_client_tls_zero_rtt, true);
  }
  InitializeMigrationOptions();
  cert_verifier_->AddObserver(this);
  CertDatabase::GetInstance()->AddObserver(this);
#if BUILDFLAG(IS_ANDROID)
  RegisterPreFreezeTask();
#endif
}

QuicSessionPool::~QuicSessionPool() {
  UMA_HISTOGRAM_COUNTS_1000("Net.NumQuicSessionsAtShutdown",
                            all_sessions_.size());

  // Destroy attempts before sessions because attempts can hold non-owning
  // session pointers. This also ensures there is no active crypto config map.
  session_attempt_manager_.reset();

  CloseAllSessions(ERR_ABORTED, quic::QUIC_CONNECTION_CANCELLED);
  all_sessions_.clear();

  // Clear the active jobs, first moving out of the instance variable so that
  // calls to CancelRequest for any pending requests do not cause recursion.
  JobMap active_jobs = std::move(active_jobs_);
  active_jobs.clear();

  DCHECK(dns_aliases_by_session_key_.empty());

  // This should have been moved to the recent map when all consumers of
  // QuicCryptoClientConfigs were deleted, in the above lines.
  DCHECK(active_crypto_config_map_.empty());

  CertDatabase::GetInstance()->RemoveObserver(this);
  cert_verifier_->RemoveObserver(this);
  if (params_.close_sessions_on_ip_change ||
      params_.goaway_sessions_on_ip_change) {
    NetworkChangeNotifier::RemoveIPAddressObserver(this);
  }
  if (NetworkChangeNotifier::AreNetworkHandlesSupported()) {
    NetworkChangeNotifier::RemoveNetworkObserver(this);
  }
}

bool QuicSessionPool::CanUseExistingSession(
    const QuicSessionKey& session_key,
    const url::SchemeHostPort& destination) const {
  return FindExistingSession(session_key, destination) != nullptr;
}

bool QuicSessionPool::CanUseExistingSessionForWebSocket(
    const QuicSessionKey& session_key,
    const url::SchemeHostPort& destination,
    quic::ParsedQuicVersion* quic_version) const {
  QuicChromiumClientSession* session =
      FindExistingSession(session_key, destination);
  if (!session) {
    return false;
  }
  // The server must have sent SETTINGS_ENABLE_CONNECT_PROTOCOL=1 in its
  // SETTINGS frame, which enables Extended CONNECT for WebSocket-over-HTTP/3.
  if (!session->allow_extended_connect()) {
    return false;
  }
  if (quic_version) {
    *quic_version = session->GetQuicVersion();
  }
  return true;
}

QuicChromiumClientSession* QuicSessionPool::FindExistingSession(
    const QuicSessionKey& session_key,
    const url::SchemeHostPort& destination) const {
  auto active_session_it = active_sessions_.find(session_key);
  if (active_session_it != active_sessions_.end()) {
    return active_session_it->second;
  }

  for (const auto& key_value : active_sessions_) {
    QuicChromiumClientSession* session = key_value.second;
    if (CanWaiveIpMatching(destination, session) &&
        session->CanPool(session_key.host(), session_key)) {
      return session;
    }
  }

  return nullptr;
}

std::optional<QuicSessionKey> QuicSessionPool::GetActiveSessionToServerId(
    const QuicSessionKey& session_key) const {
  auto it = std::ranges::find_if(
      active_sessions_, [&session_key](const auto& key_value) {
        return session_key != key_value.first &&
               session_key.server_id() == key_value.first.server_id();
      });
  return it != std::end(active_sessions_)
             ? std::optional<QuicSessionKey>(it->first)
             : std::nullopt;
}

std::optional<QuicSessionKey> QuicSessionPool::GetActiveJobToServerId(
    const QuicSessionKey& session_key) const {
  auto it =
      std::ranges::find_if(active_jobs_, [&session_key](const auto& key_value) {
        return session_key != key_value.first &&
               session_key.server_id() == key_value.first.server_id();
      });
  return it != std::end(active_jobs_) ? std::optional<QuicSessionKey>(it->first)
                                      : std::nullopt;
}

QuicChromiumClientSession*
QuicSessionPool::HasMatchingIpSessionForServiceEndpoint(
    const QuicSessionAliasKey& session_alias_key,
    const ServiceEndpoint& service_endpoint,
    const std::set<std::string>& dns_aliases,
    bool use_dns_aliases,
    bool log_negative_result) {
  if (QuicChromiumClientSession* session = HasMatchingIpSession(
          session_alias_key, service_endpoint.ipv6_endpoints, dns_aliases,
          use_dns_aliases, log_negative_result)) {
    return session;
  }
  return HasMatchingIpSession(session_alias_key,
                              service_endpoint.ipv4_endpoints, dns_aliases,
                              use_dns_aliases, log_negative_result);
}

int QuicSessionPool::RequestSession(
    const QuicSessionKey& session_key,
    url::SchemeHostPort destination,
    quic::ParsedQuicVersion quic_version,
    std::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
    MultiplexedSessionCreationInitiator session_creation_initiator,
    std::optional<ConnectionManagementConfig> management_config,
    const HttpUserAgentSettings* http_user_agent_settings,
    RequestPriority priority,
    bool use_dns_aliases,
    int cert_verify_flags,
    const GURL& url,
    const NetLogWithSource& net_log,
    QuicSessionRequest* request) {
#if BUILDFLAG(IS_ANDROID)
  if (base::FeatureList::IsEnabled(features::kCloseQuicSessionsOnPreFreeze)) {
    // If the process is pre-frozen, we block new requests because the OS will
    // suspend (freeze) our execution shortly. Starting new requests now would
    // lead to requests hanging or sockets leaking since we won't be able to
    // process responses.
    const bool is_pre_frozen = base::android::PreFreezeBackgroundMemoryTrimmer::
        GetAndUpdatePreFrozenState();
    base::UmaHistogramBoolean("Net.QuicSession.RequestBlockedByPreFreeze",
                              is_pre_frozen);
    if (is_pre_frozen) {
      return ERR_ABORTED;
    }
  }
#endif
  if (clock_skew_detector_.ClockSkewDetected(base::TimeTicks::Now(),
                                             base::Time::Now())) {
    MarkAllActiveSessionsGoingAway(kClockSkewDetected);
  }
  DCHECK_EQ(HostPortPair(session_key.server_id().host(),
                         session_key.server_id().port()),
            HostPortPair::FromURL(url));

  // Add the observer in the `management_config` for the
  // `ConnectionChangeNotifier`.
  bool connection_keep_alive_enabled = false;
  if (management_config.has_value()) {
    if (management_config->keep_alive_config.has_value()) {
      connection_keep_alive_enabled =
          management_config->keep_alive_config->enable_connection_keep_alive;
    }
    if (management_config->connection_change_observer) {
      if (!connection_change_notifier_.contains(session_key)) {
        connection_change_notifier_[session_key] =
            std::make_unique<ConnectionChangeNotifier>();
      }
      connection_change_notifier_[session_key]->AddObserver(
          management_config->connection_change_observer.get());
    }
  }

  // Use active session for `session_key` if such exists, or pool to active
  // session to `destination` if possible.
  QuicChromiumClientSession* existing_session =
      FindExistingSession(session_key, destination);
  if (existing_session) {
    LogUsingExistingSession(net_log, existing_session, destination);
    if (!HasActiveSession(session_key)) {
      QuicSessionAliasKey key(destination, session_key);
      std::set<std::string> dns_aliases;
      ActivateAndMapSessionToAliasKey(existing_session, key,
                                      std::move(dns_aliases));
    }
    request->SetSession(existing_session->CreateHandle(std::move(destination)));

    // If the session exists and the connection keep alive is enabled, we send
    // a ping to check the liveness of the existing connection.
    if (connection_keep_alive_enabled) {
      existing_session->SendPing();
    }
    return OK;
  }

  // Associate with active job to |session_key| if such exists.
  auto active_job = active_jobs_.find(session_key);
  if (active_job != active_jobs_.end()) {
    active_job->second->AssociateWithNetLogSource(net_log);
    active_job->second->AddRequest(request);
    return ERR_IO_PENDING;
  }

  CheckQuicSessionKeyMismatch(session_key, destination);

  // If a proxy is in use, then a traffic annotation is required.
  if (!session_key.proxy_chain().is_direct()) {
    DCHECK(proxy_annotation_tag);
  }

  QuicSessionAliasKey key(destination, session_key);
  std::unique_ptr<Job> job;
  // Connect start time, but only for direct connections to a proxy.
  std::optional<base::TimeTicks> proxy_connect_start_time = std::nullopt;
  QuicConnectionReuseDetails quic_connection_reuse_details =
      DetermineQuicConnectionReuseDetails(session_key);

  if (session_key.proxy_chain().is_direct()) {
    if (session_key.session_usage() == SessionUsage::kProxy) {
      proxy_connect_start_time = base::TimeTicks::Now();
    }
    if (base::FeatureList::IsEnabled(features::kAsyncDnsQuicJob)) {
      job = std::make_unique<AsyncDnsJob>(
          this, quic_version, host_resolver_, std::move(key),
          CreateCryptoConfigHandle(QuicCryptoClientConfigKey(session_key)),
          params_.retry_on_alternate_network_before_handshake, priority,
          use_dns_aliases, session_key.require_dns_https_alpn(),
          cert_verify_flags, session_creation_initiator,
          quic_connection_reuse_details, management_config, net_log);
    } else {
      job = std::make_unique<DirectJob>(
          this, quic_version, host_resolver_, std::move(key),
          CreateCryptoConfigHandle(QuicCryptoClientConfigKey(session_key)),
          params_.retry_on_alternate_network_before_handshake, priority,
          use_dns_aliases, session_key.require_dns_https_alpn(),
          cert_verify_flags, session_creation_initiator,
          quic_connection_reuse_details, management_config, net_log);
    }
  } else {
    job = std::make_unique<ProxyJob>(
        this, quic_version, std::move(key), *proxy_annotation_tag,
        session_creation_initiator, quic_connection_reuse_details,
        management_config, http_user_agent_settings,
        CreateCryptoConfigHandle(QuicCryptoClientConfigKey(session_key)),
        priority, cert_verify_flags, net_log);
  }
  job->AssociateWithNetLogSource(net_log);
  int rv = job->Run(base::BindOnce(&QuicSessionPool::OnJobComplete,
                                   weak_factory_.GetWeakPtr(), job.get(),
                                   proxy_connect_start_time));
  if (rv == ERR_IO_PENDING) {
    job->AddRequest(request);
    active_jobs_[session_key] = std::move(job);
    return rv;
  }
  if (rv == OK) {
    auto it = active_sessions_.find(session_key);
    CHECK(it != active_sessions_.end());
    if (it == active_sessions_.end()) {
      return ERR_QUIC_PROTOCOL_ERROR;
    }
    QuicChromiumClientSession* session = it->second;
    request->SetSession(session->CreateHandle(std::move(destination)));
  }
  return rv;
}

std::unique_ptr<QuicSessionAttempt> QuicSessionPool::CreateSessionAttempt(
    QuicSessionAttempt::Delegate* delegate,
    const QuicSessionKey& session_key,
    QuicEndpoint quic_endpoint,
    int cert_verify_flags,
    base::TimeTicks dns_resolution_start_time,
    base::TimeTicks dns_resolution_end_time,
    std::optional<ResolutionDetails> dns_resolution_details,
    bool use_dns_aliases,
    std::set<std::string> dns_aliases,
    MultiplexedSessionCreationInitiator session_creation_initiator,
    std::optional<ConnectionManagementConfig> connection_management_config,
    bool is_stale) {
  CHECK(!HasActiveSession(session_key));
  CHECK(!HasActiveJob(session_key));

  QuicConnectionReuseDetails quic_connection_reuse_details =
      DetermineQuicConnectionReuseDetails(session_key);

  return std::make_unique<QuicSessionAttempt>(
      delegate, quic_endpoint.ip_endpoint, std::move(quic_endpoint.metadata),
      quic_endpoint.quic_version, cert_verify_flags, dns_resolution_start_time,
      dns_resolution_end_time, std::move(dns_resolution_details),
      params_.retry_on_alternate_network_before_handshake, use_dns_aliases,
      std::move(dns_aliases),
      CreateCryptoConfigHandle(QuicCryptoClientConfigKey(session_key)),
      session_creation_initiator, quic_connection_reuse_details,
      connection_management_config, is_stale);
}

void QuicSessionPool::OnSessionGoingAway(QuicChromiumClientSession* session) {
  const AliasSet& aliases = session_aliases_[session];
  for (const auto& alias : aliases) {
    const QuicSessionKey& session_key = alias.session_key();
    DCHECK(active_sessions_.count(session_key));
    DCHECK_EQ(session, active_sessions_[session_key]);
    // Track sessions which have recently gone away so that we can disable
    // port suggestions.
    if (session->goaway_received()) {
      gone_away_aliases_.insert(alias);
    }

    active_sessions_.erase(session_key);
    ProcessGoingAwaySession(session, session_key.server_id(), true);
  }
  ProcessGoingAwaySession(session, session->session_alias_key().server_id(),
                          false);
  if (!aliases.empty()) {
    DCHECK(session_peer_ip_.contains(session));
    const IPEndPoint peer_address = session_peer_ip_[session];
    ip_aliases_[peer_address].erase(session);
    if (ip_aliases_[peer_address].empty()) {
      ip_aliases_.erase(peer_address);
    }
    session_peer_ip_.erase(session);
  }
  UnmapSessionFromSessionAliases(session);
}

void QuicSessionPool::OnSessionClosed(QuicChromiumClientSession* session) {
  DCHECK_EQ(0u, session->GetNumActiveStreams());
  OnSessionGoingAway(session);
  auto it = all_sessions_.find(session);
  CHECK(it != all_sessions_.end());

  NotifyOnSessionClosed(session->quic_session_key(),
                        session->was_ever_used_to_create_streams());

  all_sessions_.erase(it);
}

void QuicSessionPool::OnBlackholeAfterHandshakeConfirmed(
    QuicChromiumClientSession* session) {
  // Reduce PING timeout when connection blackholes after the handshake.
  if (ping_timeout_ > reduced_ping_timeout_) {
    ping_timeout_ = reduced_ping_timeout_;
  }
}

void QuicSessionPool::CancelRequest(QuicSessionRequest* request) {
  auto job_iter = active_jobs_.find(request->session_key());
  // If an error (or network context shutdown) happens early in a
  // `QuicSessionRequest`, before it has been added to `active_jobs_`, then
  // this method may be called and should be resilient to the job not
  // being in the map.
  if (job_iter != active_jobs_.end()) {
    job_iter->second->RemoveRequest(request);
    NotifyOnConnectionFailure(/*session_key=*/job_iter->first);
  }
}

void QuicSessionPool::SetRequestPriority(QuicSessionRequest* request,
                                         RequestPriority priority) {
  auto job_iter = active_jobs_.find(request->session_key());
  if (job_iter == active_jobs_.end()) {
    return;
  }
  job_iter->second->SetPriority(priority);
}

void QuicSessionPool::CloseAllSessions(int error,
                                       quic::QuicErrorCode quic_error) {
  base::UmaHistogramSparse("Net.QuicSession.CloseAllSessionsError", -error);
  size_t before_active_sessions_size = active_sessions_.size();
  size_t before_all_sessions_size = active_sessions_.size();
  while (!active_sessions_.empty()) {
    size_t initial_size = active_sessions_.size();
    active_sessions_.begin()->second->CloseSessionOnError(
        error, quic_error,
        quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
    DCHECK_NE(initial_size, active_sessions_.size());
  }
  while (!all_sessions_.empty()) {
    size_t initial_size = all_sessions_.size();
    (*all_sessions_.begin())
        ->CloseSessionOnError(
            error, quic_error,
            quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
    DCHECK_NE(initial_size, all_sessions_.size());
  }
  DCHECK(all_sessions_.empty());

  // Remove all connection change notifiers.
  connection_change_notifier_.clear();

  // TODO(crbug.com/347984574): Remove before/after counts once we identified
  // the cause.
  net_log_.AddEvent(NetLogEventType::QUIC_SESSION_POOL_CLOSE_ALL_SESSIONS, [&] {
    base::DictValue dict;
    dict.Set("net_error", error);
    dict.Set("quic_error", quic::QuicErrorCodeToString(quic_error));
    dict.Set("before_active_sessions_size",
             static_cast<int>(before_active_sessions_size));
    dict.Set("before_all_sessions_size",
             static_cast<int>(before_all_sessions_size));
    dict.Set("after_active_sessions_size",
             static_cast<int>(active_sessions_.size()));
    dict.Set("after_all_sessions_size", static_cast<int>(all_sessions_.size()));
    return dict;
  });
}

base::Value QuicSessionPool::QuicSessionPoolInfoToValue() const {
  base::ListValue list;

  for (const auto& active_session : active_sessions_) {
    const quic::QuicServerId& server_id = active_session.first.server_id();
    QuicChromiumClientSession* session = active_session.second;
    const AliasSet& aliases = session_aliases_.find(session)->second;
    // Only add a session to the list once.
    if (server_id == aliases.begin()->server_id()) {
      std::set<HostPortPair> hosts;
      for (const auto& alias : aliases) {
        hosts.insert(
            HostPortPair(alias.server_id().host(), alias.server_id().port()));
      }
      list.Append(session->GetInfoAsValue(hosts));
    }
  }
  return base::Value(std::move(list));
}

void QuicSessionPool::ClearCachedStatesInCryptoConfig(
    const base::RepeatingCallback<bool(const GURL&)>& origin_filter) {
  ServerIdOriginFilter filter(origin_filter);
  for (const auto& crypto_config : active_crypto_config_map_) {
    crypto_config.second->config()->ClearCachedStates(filter);
  }

  for (const auto& crypto_config : recent_crypto_config_map_) {
    crypto_config.second->config()->ClearCachedStates(filter);
  }
}

void QuicSessionPool::ConnectAndConfigureSocket(CompletionOnceCallback callback,
                                                DatagramClientSocket* socket,
                                                IPEndPoint addr,
                                                handles::NetworkHandle network,
                                                const SocketTag& socket_tag) {
  socket->UseNonBlockingIO();

  int rv;
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  CompletionOnceCallback connect_callback =
      base::BindOnce(&QuicSessionPool::FinishConnectAndConfigureSocket,
                     weak_factory_.GetWeakPtr(),
                     std::move(split_callback.first), socket, socket_tag);
  if (!params_.migrate_sessions_on_network_change_v2) {
    rv = socket->ConnectAsync(addr, std::move(connect_callback));
  } else if (network == handles::kInvalidNetworkHandle) {
    // If caller leaves network unspecified, use current default network.
    rv = socket->ConnectUsingDefaultNetworkAsync(addr,
                                                 std::move(connect_callback));
  } else {
    rv = socket->ConnectUsingNetworkAsync(network, addr,
                                          std::move(connect_callback));
  }
  // Both callbacks within `split_callback` will always be run asynchronously,
  // even if a Connect call returns synchronously. Therefore we always return
  // ERR_IO_PENDING.
  if (rv != ERR_IO_PENDING) {
    FinishConnectAndConfigureSocket(std::move(split_callback.second), socket,
                                    socket_tag, rv);
  }
}

void QuicSessionPool::FinishConnectAndConfigureSocket(
    CompletionOnceCallback callback,
    DatagramClientSocket* socket,
    const SocketTag& socket_tag,
    int rv) {
  if (rv != OK) {
    OnFinishConnectAndConfigureSocketError(
        std::move(callback), CREATION_ERROR_CONNECTING_SOCKET, rv);
    return;
  }

  socket->ApplySocketTag(socket_tag);

  rv = socket->SetReceiveBufferSize(kQuicSocketReceiveBufferSize);
  if (rv != OK) {
    OnFinishConnectAndConfigureSocketError(
        std::move(callback), CREATION_ERROR_SETTING_RECEIVE_BUFFER, rv);
    return;
  }

  rv = socket->SetDoNotFragment();
  // SetDoNotFragment is not implemented on all platforms, so ignore errors.
  if (rv != OK && rv != ERR_NOT_IMPLEMENTED) {
    OnFinishConnectAndConfigureSocketError(
        std::move(callback), CREATION_ERROR_SETTING_DO_NOT_FRAGMENT, rv);
    return;
  }

  rv = socket->SetRecvTos();
  if (rv != OK) {
    OnFinishConnectAndConfigureSocketError(
        std::move(callback), CREATION_ERROR_SETTING_RECEIVE_ECN, rv);
    return;
  }

  // Set a buffer large enough to contain the initial CWND's worth of packet
  // to work around the problem with CHLO packets being sent out with the
  // wrong encryption level, when the send buffer is full.
  rv = socket->SetSendBufferSize(quic::kMaxOutgoingPacketSize * 20);
  if (rv != OK) {
    OnFinishConnectAndConfigureSocketError(
        std::move(callback), CREATION_ERROR_SETTING_SEND_BUFFER, rv);
    return;
  }

  if (params_.ios_network_service_type > 0) {
    socket->SetIOSNetworkServiceType(params_.ios_network_service_type);
  }

  socket->GetLocalAddress(&local_address_);
  if (need_to_check_persisted_supports_quic_) {
    need_to_check_persisted_supports_quic_ = false;
    if (http_server_properties_->WasLastLocalAddressWhenQuicWorked(
            local_address_.address())) {
      has_quic_ever_worked_on_current_network_ = true;
      // Clear the persisted IP address, in case the network no longer supports
      // QUIC so the next restart will require confirmation. It will be
      // re-persisted when the first job completes successfully.
      http_server_properties_->ClearLastLocalAddressWhenQuicWorked();
    }
  }

  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&QuicSessionPool::DoCallback, weak_factory_.GetWeakPtr(),
                     std::move(callback), rv));
}

bool QuicSessionPool::CanWaiveIpMatching(
    const url::SchemeHostPort& destination,
    QuicChromiumClientSession* session) const {
  // Checks if `destination` matches the alias key of `session`.
  if (destination == session->session_alias_key().destination()) {
    return true;
  }

  if (ignore_ip_matching_when_finding_existing_sessions_ &&
      quic::ContainsQuicTag(session->received_connection_options(),
                            quic::kNOIP)) {
    return true;
  }

  // Check received origins.
  if (skip_dns_with_origin_frame_ &&
      session->received_origins().contains(destination)) {
    return true;
  }
  return false;
}

void QuicSessionPool::OnFinishConnectAndConfigureSocketError(
    CompletionOnceCallback callback,
    enum CreateSessionFailure error,
    int rv) {
  DCHECK(callback);
  HistogramCreateSessionFailure(error);
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&QuicSessionPool::DoCallback, weak_factory_.GetWeakPtr(),
                     std::move(callback), rv));
}

void QuicSessionPool::DoCallback(CompletionOnceCallback callback, int rv) {
  std::move(callback).Run(rv);
}

int QuicSessionPool::ConfigureSocket(DatagramClientSocket* socket,
                                     IPEndPoint addr,
                                     handles::NetworkHandle network,
                                     const SocketTag& socket_tag) {
  socket->UseNonBlockingIO();

  int rv;
  if (!params_.migrate_sessions_on_network_change_v2) {
    rv = socket->Connect(addr);
  } else if (network == handles::kInvalidNetworkHandle) {
    // If caller leaves network unspecified, use current default network.
    rv = socket->ConnectUsingDefaultNetwork(addr);
  } else {
    rv = socket->ConnectUsingNetwork(network, addr);
  }
  if (rv != OK) {
    HistogramCreateSessionFailure(CREATION_ERROR_CONNECTING_SOCKET);
    return rv;
  }

  socket->ApplySocketTag(socket_tag);

  rv = socket->SetReceiveBufferSize(kQuicSocketReceiveBufferSize);
  if (rv != OK) {
    HistogramCreateSessionFailure(CREATION_ERROR_SETTING_RECEIVE_BUFFER);
    return rv;
  }

  rv = socket->SetDoNotFragment();
  // SetDoNotFragment is not implemented on all platforms, so ignore errors.
  if (rv != OK && rv != ERR_NOT_IMPLEMENTED) {
    HistogramCreateSessionFailure(CREATION_ERROR_SETTING_DO_NOT_FRAGMENT);
    return rv;
  }

  rv = socket->SetRecvTos();
  if (rv != OK) {
    HistogramCreateSessionFailure(CREATION_ERROR_SETTING_RECEIVE_ECN);
    return rv;
  }

  // Set a buffer large enough to contain the initial CWND's worth of packet
  // to work around the problem with CHLO packets being sent out with the
  // wrong encryption level, when the send buffer is full.
  rv = socket->SetSendBufferSize(quic::kMaxOutgoingPacketSize * 20);
  if (rv != OK) {
    HistogramCreateSessionFailure(CREATION_ERROR_SETTING_SEND_BUFFER);
    return rv;
  }

  if (params_.ios_network_service_type > 0) {
    socket->SetIOSNetworkServiceType(params_.ios_network_service_type);
  }

  socket->GetLocalAddress(&local_address_);
  if (need_to_check_persisted_supports_quic_) {
    need_to_check_persisted_supports_quic_ = false;
    if (http_server_properties_->WasLastLocalAddressWhenQuicWorked(
            local_address_.address())) {
      has_quic_ever_worked_on_current_network_ = true;
      // Clear the persisted IP address, in case the network no longer supports
      // QUIC so the next restart will require confirmation. It will be
      // re-persisted when the first job completes successfully.
      http_server_properties_->ClearLastLocalAddressWhenQuicWorked();
    }
  }

  return OK;
}

handles::NetworkHandle QuicSessionPool::FindAlternateNetwork(
    handles::NetworkHandle old_network) {
  // Find a new network that sessions bound to |old_network| can be migrated to.
  NetworkChangeNotifier::NetworkList network_list;
  NetworkChangeNotifier::GetConnectedNetworks(&network_list);
  for (handles::NetworkHandle new_network : network_list) {
    if (new_network != old_network) {
      return new_network;
    }
  }
  return handles::kInvalidNetworkHandle;
}

std::unique_ptr<DatagramClientSocket> QuicSessionPool::CreateSocket(
    handles::NetworkHandle target_network,
    NetLog* net_log,
    const NetLogSource& source) {
  auto socket = client_socket_factory_->CreateDatagramClientSocket(
      DatagramSocket::DEFAULT_BIND, target_network, net_log, source);
  if (params_.enable_socket_recv_optimization) {
    socket->EnableRecvOptimization();
  }
  return socket;
}

void QuicSessionPool::OnIPAddressChanged(
    NetworkChangeNotifier::IPAddressChangeType change_type) {
  net_log_.AddEvent(NetLogEventType::QUIC_SESSION_POOL_ON_IP_ADDRESS_CHANGED);
  CollectDataOnPlatformNotification(NETWORK_IP_ADDRESS_CHANGED,
                                    handles::kInvalidNetworkHandle);
  // Do nothing if connection migration is turned on.
  if (params_.migrate_sessions_on_network_change_v2) {
    return;
  }

  // Ignore changes to randomly generated IPv6 temporary addresses.
  if (!base::FeatureList::IsEnabled(
          net::features::kMaintainConnectionsOnIpv6TempAddrChange) &&
      change_type == NetworkChangeNotifier::IP_ADDRESS_CHANGE_IPV6_TEMPADDR) {
    return;
  }

  connectivity_monitor_.OnIPAddressChanged();

  set_has_quic_ever_worked_on_current_network(false);
  if (params_.close_sessions_on_ip_change) {
    CloseAllSessions(ERR_NETWORK_CHANGED, quic::QUIC_IP_ADDRESS_CHANGED);
  } else {
    DCHECK(params_.goaway_sessions_on_ip_change);
    MarkAllActiveSessionsGoingAway(kIPAddressChanged);
  }
}

void QuicSessionPool::OnNetworkConnected(handles::NetworkHandle network) {
  CollectDataOnPlatformNotification(NETWORK_CONNECTED, network);
  if (params_.migrate_sessions_on_network_change_v2) {
    net_log_.AddEvent(NetLogEventType::QUIC_SESSION_POOL_PLATFORM_NOTIFICATION,
                      [&] {
                        base::DictValue dict;
                        dict.Set("signal", "OnNetworkConnected");
                        dict.Set("network", base::NumberToString(network));
                        return dict;
                      });
  }
  // Broadcast network connected to all sessions.
  // If migration is not turned on, session will not migrate but collect data.
  auto it = all_sessions_.begin();
  // Sessions may be deleted while iterating through the set.
  while (it != all_sessions_.end()) {
    QuicChromiumClientSession* session = it->get();
    ++it;
    session->OnNetworkConnected(network);
  }
  NotifyOnNetworkEvent(net::NetworkChangeEvent::kConnected);
}

void QuicSessionPool::OnNetworkDisconnected(handles::NetworkHandle network) {
  CollectDataOnPlatformNotification(NETWORK_DISCONNECTED, network);
  if (params_.migrate_sessions_on_network_change_v2) {
    net_log_.AddEvent(NetLogEventType::QUIC_SESSION_POOL_PLATFORM_NOTIFICATION,
                      [&] {
                        base::DictValue dict;
                        dict.Set("signal", "OnNetworkDisconnected");
                        dict.Set("network", base::NumberToString(network));
                        return dict;
                      });
  }
  // Broadcast network disconnected to all sessions.
  // If migration is not turned on, session will not migrate but collect data.
  auto it = all_sessions_.begin();
  // Sessions may be deleted while iterating through the set.
  while (it != all_sessions_.end()) {
    QuicChromiumClientSession* session = it->get();
    ++it;
    session->OnNetworkDisconnectedV2(/*disconnected_network*/ network);
  }
  NotifyOnNetworkEvent(net::NetworkChangeEvent::kDisconnected);
}

// This method is expected to only be called when migrating from Cellular to
// WiFi on Android, and should always be preceded by OnNetworkMadeDefault().
void QuicSessionPool::OnNetworkSoonToDisconnect(
    handles::NetworkHandle network) {
  CollectDataOnPlatformNotification(NETWORK_SOON_TO_DISCONNECT, network);
  NotifyOnNetworkEvent(net::NetworkChangeEvent::kSoonToDisconnect);
}

void QuicSessionPool::OnNetworkMadeDefault(handles::NetworkHandle network) {
  CollectDataOnPlatformNotification(NETWORK_MADE_DEFAULT, network);
  connectivity_monitor_.OnDefaultNetworkUpdated(network);

  // Clear alternative services that were marked as broken until default network
  // changes.
  if (params_.retry_on_alternate_network_before_handshake &&
      default_network_ != handles::kInvalidNetworkHandle &&
      network != default_network_) {
    http_server_properties_->OnDefaultNetworkChanged();
  }

  DCHECK_NE(handles::kInvalidNetworkHandle, network);
  default_network_ = network;

  if (params_.migrate_sessions_on_network_change_v2) {
    net_log_.AddEvent(NetLogEventType::QUIC_SESSION_POOL_PLATFORM_NOTIFICATION,
                      [&] {
                        base::DictValue dict;
                        dict.Set("signal", "OnNetworkMadeDefault");
                        dict.Set("network", base::NumberToString(network));
                        return dict;
                      });
  }

  auto it = all_sessions_.begin();
  // Sessions may be deleted while iterating through the set.
  while (it != all_sessions_.end()) {
    QuicChromiumClientSession* session = it->get();
    ++it;
    session->OnNetworkMadeDefault(network);
  }
  if (params_.migrate_sessions_on_network_change_v2) {
    set_has_quic_ever_worked_on_current_network(false);
  }
  NotifyOnNetworkEvent(net::NetworkChangeEvent::kDefaultNetworkChanged);
}

void QuicSessionPool::OnTrustStoreChanged() {
  // We should flush the sessions if we removed trust from a
  // cert, because a previously trusted server may have become
  // untrusted.
  //
  // We should not flush the sessions if we added trust to a cert.
  //
  // Since the OnTrustStoreChanged method doesn't tell us what
  // kind of change it is, we have to flush the socket
  // pools to be safe.
  MarkAllActiveSessionsGoingAway(kCertDBChanged);
}

void QuicSessionPool::OnCertVerifierChanged() {
  // Flush sessions if the CertCerifier configuration has changed.
  MarkAllActiveSessionsGoingAway(kCertVerifierChanged);
}

void QuicSessionPool::set_has_quic_ever_worked_on_current_network(
    bool has_quic_ever_worked_on_current_network) {
  has_quic_ever_worked_on_current_network_ =
      has_quic_ever_worked_on_current_network;
  if (!(local_address_ == IPEndPoint())) {
    if (has_quic_ever_worked_on_current_network_) {
      http_server_properties_->SetLastLocalAddressWhenQuicWorked(
          local_address_.address());
    } else {
      http_server_properties_->ClearLastLocalAddressWhenQuicWorked();
    }
  }
}

base::TimeDelta QuicSessionPool::GetTimeDelayForWaitingJob(
    const QuicSessionKey& session_key) {
  if (time_delay_for_waiting_job_for_testing_.has_value()) {
    return *time_delay_for_waiting_job_for_testing_;
  }

  // If |is_quic_known_to_work_on_current_network_| is false, then one of the
  // following is true:
  // 1) This is startup and QuicSessionPool::CreateSession() and
  // ConfigureSocket() have yet to be called, and it is not yet known
  // if the current network is the last one where QUIC worked.
  // 2) Startup has been completed, and QUIC has not been used
  // successfully since startup, or on this network before.
  if (!has_quic_ever_worked_on_current_network_) {
    // If |need_to_check_persisted_supports_quic_| is false, this is case 1)
    // above. If HasLastLocalAddressWhenQuicWorked() is also true, then there's
    // a chance the current network is the last one on which QUIC worked. So
    // only delay the request if there's no chance that is the case.
    if (!need_to_check_persisted_supports_quic_ ||
        !http_server_properties_->HasLastLocalAddressWhenQuicWorked()) {
      return base::TimeDelta();
    }
  }

  // QUIC was recently broken. Do not delay the main job.
  if (WasQuicRecentlyBroken(session_key)) {
    return base::TimeDelta();
  }

  int64_t srtt = 1.5 * GetServerNetworkStatsSmoothedRttInMicroseconds(
                           session_key.server_id(),
                           session_key.network_anonymization_key(),
                           session_key.proxy_chain());
  // Picked 300ms based on mean time from
  // Net.QuicSession.HostResolution.HandshakeConfirmedTime histogram.
  const int kDefaultRTT = 300 * quic::kNumMicrosPerMilli;
  if (!srtt) {
    srtt = kDefaultRTT;
  }
  return base::Microseconds(srtt) + GetAdditionalDelayForMainJob();
}

const std::set<std::string>& QuicSessionPool::GetDnsAliasesForSessionKey(
    const QuicSessionKey& key) const {
  auto it = dns_aliases_by_session_key_.find(key);

  if (it == dns_aliases_by_session_key_.end()) {
    static const base::NoDestructor<std::set<std::string>> emptyvector_result;
    return *emptyvector_result;
  }

  return it->second;
}

void QuicSessionPool::ActivateSessionForTesting(
    std::unique_ptr<QuicChromiumClientSession> new_session) {
  QuicChromiumClientSession* session = new_session.get();
  all_sessions_.insert(std::move(new_session));
  ActivateSession(session->session_alias_key(), session,
                  std::set<std::string>());
}

void QuicSessionPool::DeactivateSessionForTesting(
    QuicChromiumClientSession* session) {
  OnSessionGoingAway(session);
  auto it = all_sessions_.find(session);
  CHECK(it != all_sessions_.end());
  all_sessions_.erase(it);
}

void QuicSessionPool::SetTimeDelayForWaitingJobForTesting(
    base::TimeDelta delay) {
  time_delay_for_waiting_job_for_testing_ = delay;
}

quic::ParsedQuicVersion QuicSessionPool::SelectQuicVersion(
    const quic::ParsedQuicVersion& known_quic_version,
    const ConnectionEndpointMetadata& metadata,
    bool svcb_optional) const {
  if (metadata.supported_protocol_alpns.empty()) {
    // `metadata` doesn't contain QUIC ALPN. If we know the QUIC ALPN to use
    // externally, i.e. via Alt-Svc, use it in SVCB-optional mode. Otherwise,
    // the endpoint associated with `metadata` is not eligible for QUIC.
    return svcb_optional ? known_quic_version
                         : quic::ParsedQuicVersion::Unsupported();
  }

  // Otherwise, `metadata` came from an HTTPS/SVCB record. We can use
  // QUIC if a suitable match is found in the record's ALPN list.
  // Additionally, if this connection attempt came from Alt-Svc, the DNS
  // result must be consistent with it. See
  // https://datatracker.ietf.org/doc/html/rfc9460#name-interaction-with-alt-svc
  if (known_quic_version.IsKnown()) {
    std::string expected_alpn = quic::AlpnForVersion(known_quic_version);
    if (std::ranges::contains(metadata.supported_protocol_alpns,
                              quic::AlpnForVersion(known_quic_version))) {
      return known_quic_version;
    }
    return quic::ParsedQuicVersion::Unsupported();
  }

  for (const auto& alpn : metadata.supported_protocol_alpns) {
    for (const auto& supported_version : supported_versions()) {
      if (alpn == AlpnForVersion(supported_version)) {
        return supported_version;
      }
    }
  }

  return quic::ParsedQuicVersion::Unsupported();
}

// static
void QuicSessionPool::LogConnectionIpPooling(bool pooled) {
  base::UmaHistogramBoolean("Net.QuicSession.ConnectionIpPooled", pooled);
}

QuicChromiumClientSession* QuicSessionPool::HasMatchingIpSession(
    const QuicSessionAliasKey& key,
    const std::vector<IPEndPoint>& ip_endpoints,
    const std::set<std::string>& aliases,
    bool use_dns_aliases,
    bool log_negative_result) {
  const quic::QuicServerId& server_id(key.server_id());

  // There could be an existing session when HappyEyeballsV3 is enabled because
  // there could be multiple QuicSessionAttempts for the same destination at
  // a time and the attempt may complete after another attempt has already
  // activated a session.
  // TODO(crbug.com/416364483): Stop bypassing DCHECK in the HEv3 code path.
  // Instead, implement a way to notify session activation to
  // HttpStreamPool::AttemptManagers of which destinations match the session
  // key. Upon receiving notifications, AttemptManagers should cancel in flight
  // QUIC attempts.
  DCHECK(IsHappyEyeballsV3Enabled() || !HasActiveSession(key.session_key()));

  for (const auto& address : ip_endpoints) {
    if (!ip_aliases_.contains(address)) {
      continue;
    }

    const SessionSet& sessions = ip_aliases_[address];
    for (QuicChromiumClientSession* session : sessions) {
      if (!session->CanPool(server_id.host(), key.session_key())) {
        continue;
      }
      std::set<std::string> dns_aliases;
      if (use_dns_aliases) {
        dns_aliases = aliases;
      }
      ActivateAndMapSessionToAliasKey(session, key, std::move(dns_aliases));
      LogFindMatchingIpSessionResult(net_log_, MATCHING_IP_SESSION_FOUND,
                                     session, key.destination());
      return session;
    }
  }

  bool can_pool = false;
  static constexpr uint32_t kMaxLoopCount = 200;
  uint32_t loop_count = 0;
  for (const auto& entry : active_sessions_) {
    ++loop_count;
    if (loop_count >= kMaxLoopCount) {
      break;
    }
    QuicChromiumClientSession* session = entry.second;
    if (!session->CanPool(server_id.host(), key.session_key())) {
      continue;
    }
    can_pool = true;
    // TODO(fayang): consider to use CanWaiveIpMatching().
    if (session->received_origins().contains(key.destination()) ||
        (ignore_ip_matching_when_finding_existing_sessions_ &&
         quic::ContainsQuicTag(session->received_connection_options(),
                               quic::kNOIP))) {
      std::set<std::string> dns_aliases;
      if (use_dns_aliases) {
        dns_aliases = aliases;
      }
      ActivateAndMapSessionToAliasKey(session, key, std::move(dns_aliases));
      LogFindMatchingIpSessionResult(net_log_, POOLED_WITH_DIFFERENT_IP_SESSION,
                                     session, key.destination());
      return session;
    }
  }
  if (!log_negative_result) {
    return nullptr;
  }
  if (can_pool) {
    LogFindMatchingIpSessionResult(net_log_, CAN_POOL_BUT_DIFFERENT_IP,
                                   /*session=*/nullptr, key.destination());
  } else {
    LogFindMatchingIpSessionResult(net_log_, CANNOT_POOL_WITH_EXISTING_SESSIONS,
                                   /*session=*/nullptr, key.destination());
  }
  return nullptr;
}

void QuicSessionPool::OnJobComplete(
    Job* job,
    std::optional<base::TimeTicks> proxy_connect_start_time,
    int rv) {
  if (proxy_connect_start_time) {
    HttpProxyConnectJob::EmitConnectLatency(
        NextProto::kProtoQUIC, ProxyServer::Scheme::SCHEME_QUIC,
        rv == 0 ? HttpProxyConnectJob::HttpConnectResult::kSuccess
                : HttpProxyConnectJob::HttpConnectResult::kError,
        base::TimeTicks::Now() - *proxy_connect_start_time);
  }

  auto job_iter = active_jobs_.find(job->key().session_key());
  CHECK(job_iter != active_jobs_.end());
  CHECK_EQ(job_iter->second.get(), job);
  // Remove the job so it doesn't get reused by any new requests that are
  // added synchronously by QuicSessionRequest::OnRequestComplete() below.
  std::unique_ptr<Job> completed_job = std::move(job_iter->second);
  active_jobs_.erase(job_iter);

  job->set_is_deleting();

  if (rv == OK) {
    if (!has_quic_ever_worked_on_current_network_) {
      set_has_quic_ever_worked_on_current_network(true);
    }

    auto session_it = active_sessions_.find(job->key().session_key());
    CHECK(session_it != active_sessions_.end());
    QuicChromiumClientSession* session = session_it->second;
    for (QuicSessionRequest* request : job->requests()) {
      // Do not notify |request| yet.
      request->SetSession(session->CreateHandle(job->key().destination()));
    }
  } else if (rv < 0) {
    NotifyOnConnectionFailure(job->key().session_key());
  }

  for (QuicSessionRequest* request : job->requests()) {
    // Even though we're invoking callbacks here, we don't need to worry
    // about |this| being deleted, because the pool is owned by the
    // profile which can not be deleted via callbacks.
    if (rv < 0) {
      job->PopulateNetErrorDetails(request->net_error_details());
    }
    request->OnRequestComplete(rv);
  }
}

bool QuicSessionPool::IsSessionActive(
    const QuicChromiumClientSession* session) const {
  return std::ranges::any_of(active_sessions_, [session](const auto& entry) {
    return entry.second.get() == session;
  });
}

bool QuicSessionPool::HasActiveSession(
    const QuicSessionKey& session_key) const {
  return active_sessions_.contains(session_key);
}

bool QuicSessionPool::HasActiveJob(const QuicSessionKey& session_key) const {
  return active_jobs_.contains(session_key);
}

QuicConnectionReuseDetails QuicSessionPool::DetermineQuicConnectionReuseDetails(
    const QuicSessionKey& session_key) const {
  QuicConnectionReuseDetails details;
  bool has_preconnect = false;
  bool has_non_preconnect = false;
  bool has_goaway = false;
  bool has_disconnected = false;
  bool has_other_going_away = false;

  // Step 1: Check if one or more sessions with the exact matching
  // `quic_session_key()` already exist in `all_sessions_`.
  // Note: Since `DetermineQuicConnectionReuseDetails()` is only called when no
  // active session could be reused directly from `active_sessions_`, any
  // matching session found here must be in a non-active state (e.g. draining,
  // disconnected, or received a GOAWAY frame).
  for (const auto& session : all_sessions_) {
    if (session_key == session->quic_session_key()) {
      if (!session->OneRttKeysAvailable()) {
        // Ignore sessions that are still connecting / handshake in progress.
        continue;
      }
      if (session->session_creation_initiator() ==
          MultiplexedSessionCreationInitiator::kPreconnect) {
        has_preconnect = true;
      } else {
        has_non_preconnect = true;
      }
      if (session->goaway_received()) {
        has_goaway = true;
      } else if (!session->connection() ||
                 !session->connection()->connected()) {
        has_disconnected = true;
      } else {
        has_other_going_away = true;
      }
    }
  }

  int distinct_reasons = (has_goaway ? 1 : 0) + (has_disconnected ? 1 : 0) +
                         (has_other_going_away ? 1 : 0);
  if (distinct_reasons > 1) {
    details.non_reuse_reason =
        QuicSessionNonReuseReason::kSessionExisted_MultipleReasons;
  } else if (has_goaway) {
    details.non_reuse_reason =
        QuicSessionNonReuseReason::kSessionExisted_ServerGoaway;
  } else if (has_disconnected) {
    details.non_reuse_reason =
        QuicSessionNonReuseReason::kSessionExisted_Disconnected;
  } else if (has_other_going_away) {
    details.non_reuse_reason =
        QuicSessionNonReuseReason::kSessionExisted_OtherGoingAway;
  }

  if (has_preconnect && has_non_preconnect) {
    details.establishment_reason =
        QuicSessionEstablishmentReason::kSessionExistedBoth;
  } else if (has_preconnect) {
    details.establishment_reason =
        QuicSessionEstablishmentReason::kSessionExistedAndWasPreconnect;
  } else if (has_non_preconnect) {
    details.establishment_reason =
        QuicSessionEstablishmentReason::kSessionExistedButNotPreconnect;
  } else {
    details.establishment_reason =
        QuicSessionEstablishmentReason::kNoSessionExisted;
  }

  // If matching session(s) existed in `all_sessions_`, we have already
  // determined both the establishment reason and the non-reuse reason.
  if (details.establishment_reason !=
      QuicSessionEstablishmentReason::kNoSessionExisted) {
    return details;
  }

  // Step 2: No exact matching session existed in `all_sessions_`.
  // Check if there is an active session or active job targeting the same server
  // (ServerId / host & port). If so, identify which key fields caused the
  // partition mismatch preventing reuse.
  std::optional<QuicSessionKey> active_key =
      GetActiveSessionToServerId(session_key);
  if (!active_key) {
    active_key = GetActiveJobToServerId(session_key);
  }

  if (active_key) {
    int mismatch_count = 0;
    std::optional<QuicSessionNonReuseReason> single_mismatch_reason;

    auto check_mismatch = [&](bool differs, QuicSessionNonReuseReason reason) {
      if (differs) {
        mismatch_count++;
        single_mismatch_reason = reason;
      }
    };

    check_mismatch(
        session_key.socket_tag() != active_key->socket_tag(),
        QuicSessionNonReuseReason::kNoSessionExisted_KeyMismatch_SocketTag);
    check_mismatch(session_key.network_anonymization_key() !=
                       active_key->network_anonymization_key(),
                   QuicSessionNonReuseReason::
                       kNoSessionExisted_KeyMismatch_NetworkAnonymizationKey);
    check_mismatch(
        session_key.privacy_mode() != active_key->privacy_mode(),
        QuicSessionNonReuseReason::kNoSessionExisted_KeyMismatch_PrivacyMode);
    check_mismatch(
        session_key.secure_dns_policy() != active_key->secure_dns_policy(),
        QuicSessionNonReuseReason::
            kNoSessionExisted_KeyMismatch_SecureDnsPolicy);
    check_mismatch(
        session_key.proxy_chain() != active_key->proxy_chain() ||
            session_key.session_usage() != active_key->session_usage() ||
            session_key.require_dns_https_alpn() !=
                active_key->require_dns_https_alpn() ||
            session_key.disable_cert_verification_network_fetches() !=
                active_key->disable_cert_verification_network_fetches() ||
            session_key.target_network() != active_key->target_network(),
        QuicSessionNonReuseReason::kNoSessionExisted_KeyMismatch_Other);

    // If multiple key fields differ, categorize as MultipleFields rather than
    // attributing arbitrarily to a single field.
    if (mismatch_count > 1) {
      details.non_reuse_reason = QuicSessionNonReuseReason::
          kNoSessionExisted_KeyMismatch_MultipleFields;
    } else if (mismatch_count == 1) {
      details.non_reuse_reason = *single_mismatch_reason;
    } else {
      details.non_reuse_reason =
          QuicSessionNonReuseReason::kNoSessionExisted_KeyMismatch_Other;
    }
    return details;
  }

  // Step 3: No active session or job exists for this server at all (cold
  // start).
  details.non_reuse_reason =
      QuicSessionNonReuseReason::kNoSessionExisted_TrueColdStart;
  return details;
}

void QuicSessionPool::NotifyOnNetworkEvent(net::NetworkChangeEvent event) {
  for (auto& notifier : connection_change_notifier_) {
    notifier.second->OnNetworkEvent(event);
  }
}

void QuicSessionPool::NotifyOnSessionClosed(const QuicSessionKey& session_key,
                                            bool used) const {
  auto notifier = connection_change_notifier_.find(session_key);
  if (notifier != connection_change_notifier_.end()) {
    notifier->second->OnSessionClosed(used);
  }
}

void QuicSessionPool::NotifyOnConnectionFailure(
    const QuicSessionKey& session_key) const {
  auto notifier = connection_change_notifier_.find(session_key);
  if (notifier != connection_change_notifier_.end()) {
    notifier->second->OnConnectionFailed();
  }
}

int QuicSessionPool::CreateSessionSync(
    QuicSessionAliasKey key,
    quic::ParsedQuicVersion quic_version,
    int cert_verify_flags,
    bool require_confirmation,
    IPEndPoint peer_address,
    ConnectionEndpointMetadata metadata,
    base::TimeTicks dns_resolution_start_time,
    base::TimeTicks dns_resolution_end_time,
    std::optional<ResolutionDetails> resolution_details,
    const NetLogWithSource& net_log,
    raw_ptr<QuicChromiumClientSession>* session,
    handles::NetworkHandle* network,
    MultiplexedSessionCreationInitiator session_creation_initiator,
    QuicConnectionReuseDetails quic_connection_reuse_details,
    std::optional<ConnectionManagementConfig> connection_management_config) {
  *session = nullptr;
  // TODO(crbug.com/40256842): This logic only knows how to try one IP
  // endpoint.
  std::unique_ptr<DatagramClientSocket> socket(CreateSocket(
      key.session_key().target_network(), net_log.net_log(), net_log.source()));

  // If migrate_sessions_on_network_change_v2 is on, passing in
  // handles::kInvalidNetworkHandle will bind the socket to the default network.
  int rv = ConfigureSocket(
      socket.get(), peer_address,
      // TODO(crbug.com/518753285): Stop setting the network that should be used
      // due to connection migration via ConfigureSocket. Instead, rely on the
      // new parameter in `CreateSocket`.
      *network, key.session_key().socket_tag());
  if (rv != OK) {
    return rv;
  }

  base::expected<QuicSessionAttempt::CreateSessionResult, int> result =
      CreateSessionHelper(
          std::move(key), quic_version, cert_verify_flags, require_confirmation,
          std::move(peer_address), std::move(metadata),
          dns_resolution_start_time, dns_resolution_end_time,
          std::move(resolution_details),
          /*session_max_packet_length=*/0, net_log,
          // TODO(crbug.com/518753285): Stop passing the network explicitly,
          // instead rely on socket being already bound to the correct network.
          *network, std::move(socket), session_creation_initiator,
          quic_connection_reuse_details, connection_management_config);
  if (!result.has_value()) {
    return result.error();
  }

  *session = result->session;
  *network = result->network;
  return OK;
}

int QuicSessionPool::CreateSessionAsync(
    CreateSessionCallback callback,
    QuicSessionAliasKey key,
    quic::ParsedQuicVersion quic_version,
    int cert_verify_flags,
    bool require_confirmation,
    IPEndPoint peer_address,
    ConnectionEndpointMetadata metadata,
    base::TimeTicks dns_resolution_start_time,
    base::TimeTicks dns_resolution_end_time,
    std::optional<ResolutionDetails> resolution_details,
    const NetLogWithSource& net_log,
    handles::NetworkHandle network,
    MultiplexedSessionCreationInitiator session_creation_initiator,
    QuicConnectionReuseDetails quic_connection_reuse_details,
    std::optional<ConnectionManagementConfig> connection_management_config) {
  // TODO(crbug.com/40256842): This logic only knows how to try one IP
  // endpoint.
  std::unique_ptr<DatagramClientSocket> socket(CreateSocket(
      key.session_key().target_network(), net_log.net_log(), net_log.source()));
  DatagramClientSocket* socket_ptr = socket.get();
  CompletionOnceCallback connect_and_configure_callback = base::BindOnce(
      &QuicSessionPool::FinishCreateSession, weak_factory_.GetWeakPtr(),
      std::move(callback), std::move(key), quic_version, cert_verify_flags,
      require_confirmation, peer_address, std::move(metadata),
      dns_resolution_start_time, dns_resolution_end_time,
      std::move(resolution_details),
      /*session_max_packet_length=*/0, net_log,
      // TODO(crbug.com/518753285): Stop setting the network that should be used
      // due to connection migration via ConnectAndConfigureSocket. Instead,
      // rely on the new parameter in `CreateSocket`.
      network, std::move(socket), session_creation_initiator,
      quic_connection_reuse_details, connection_management_config);

  // If migrate_sessions_on_network_change_v2 is on, passing in
  // handles::kInvalidNetworkHandle will bind the socket to the default network.
  ConnectAndConfigureSocket(std::move(connect_and_configure_callback),
                            socket_ptr, std::move(peer_address),
                            // TODO(crbug.com/518753285): Stop setting the
                            // network that should be used due to connection
                            // migration via ConnectAndConfigureSocket. Instead,
                            // rely on the new parameter in `CreateSocket`.
                            network, key.session_key().socket_tag());
  return ERR_IO_PENDING;
}

int QuicSessionPool::CreateSessionOnProxyStream(
    CreateSessionCallback callback,
    QuicSessionAliasKey key,
    quic::ParsedQuicVersion quic_version,
    int cert_verify_flags,
    bool require_confirmation,
    IPEndPoint local_address,
    IPEndPoint proxy_peer_address,
    std::unique_ptr<QuicChromiumClientStream::Handle> proxy_stream,
    std::string user_agent,
    const NetLogWithSource& net_log,
    handles::NetworkHandle network,
    MultiplexedSessionCreationInitiator session_creation_initiator,
    QuicConnectionReuseDetails quic_connection_reuse_details) {
  // Use the host and port from the proxy server along with the example URI
  // template in https://datatracker.ietf.org/doc/html/rfc9298#section-2.
  const ProxyChain& proxy_chain = key.session_key().proxy_chain();
  const ProxyServer& last_proxy = proxy_chain.Last();
  const quic::QuicServerId& server_id = key.server_id();
  const std::string encocded_host =
      base::EscapeQueryParamValue(last_proxy.GetHost().c_str(), false);
  GURL url(base::StringPrintf("https://%s:%d/.well-known/masque/udp/%s/%d/",
                              last_proxy.GetHost().c_str(),
                              last_proxy.GetPort(), server_id.host().c_str(),
                              server_id.port()));

  auto socket = std::make_unique<QuicProxyDatagramClientSocket>(
      url, key.session_key().proxy_chain(), user_agent, net_log,
      proxy_delegate_);
  QuicProxyDatagramClientSocket* socket_ptr = socket.get();

  socket->ApplySocketTag(key.session_key().socket_tag());

  // No host resolution took place, so pass an empty metadata,
  // pretend resolution started and ended right now, and pass an
  // invalid network handle. Connections on an invalid network will
  // not be migrated due to network changes.
  ConnectionEndpointMetadata metadata;
  auto dns_resolution_time = base::TimeTicks::Now();

  // Maximum packet length for the session inside this stream is limited
  // by the largest message payload allowed, accounting for the quarter-stream
  // ID (up to 8 bytes) and the context ID (1 byte). If we cannot determine the
  // max payload size for the stream, or there is no room for the overhead, use
  // 0 as a sentinel value to use the default packet size.
  quic::QuicPacketLength quarter_stream_id_length =
      quiche::QuicheDataWriter::GetVarInt62Len(proxy_stream->id() / 4);
  constexpr quic::QuicPacketLength context_id_length = 1;
  quic::QuicPacketLength guaranteed_largest_message_payload =
      proxy_stream->GetGuaranteedLargestMessagePayload();
  quic::QuicPacketLength overhead =
      quarter_stream_id_length + context_id_length;
  quic::QuicPacketLength session_max_packet_length =
      guaranteed_largest_message_payload > overhead
          ? guaranteed_largest_message_payload - overhead
          : 0;

  auto [on_connected_via_stream_async, on_connected_via_stream_sync] =
      base::SplitOnceCallback(base::BindOnce(
          &QuicSessionPool::FinishCreateSession, weak_factory_.GetWeakPtr(),
          std::move(callback), std::move(key), quic_version, cert_verify_flags,
          require_confirmation, proxy_peer_address, std::move(metadata),
          dns_resolution_time, dns_resolution_time, std::nullopt,
          session_max_packet_length, net_log,
          // TODO(crbug.com/518753285): Stop setting the
          // network that should be used due to connection
          // migration via ConnectViaStream. Instead,
          // rely on the new parameter in `CreateSocket`.
          network, std::move(socket), session_creation_initiator,
          quic_connection_reuse_details,
          /*connection_management_config=*/std::nullopt));

  int rv = socket_ptr->ConnectViaStream(
      std::move(local_address), std::move(proxy_peer_address),
      std::move(proxy_stream), std::move(on_connected_via_stream_async));
  if (rv != ERR_IO_PENDING) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce([](CompletionOnceCallback callback,
                                     int rv) { std::move(callback).Run(rv); },
                                  std::move(on_connected_via_stream_sync), rv));
  }

  return ERR_IO_PENDING;
}

void QuicSessionPool::FinishCreateSession(
    CreateSessionCallback callback,
    QuicSessionAliasKey key,
    quic::ParsedQuicVersion quic_version,
    int cert_verify_flags,
    bool require_confirmation,
    IPEndPoint peer_address,
    ConnectionEndpointMetadata metadata,
    base::TimeTicks dns_resolution_start_time,
    base::TimeTicks dns_resolution_end_time,
    std::optional<ResolutionDetails> resolution_details,
    quic::QuicPacketLength session_max_packet_length,
    const NetLogWithSource& net_log,
    handles::NetworkHandle network,
    std::unique_ptr<DatagramClientSocket> socket,
    MultiplexedSessionCreationInitiator session_creation_initiator,
    QuicConnectionReuseDetails quic_connection_reuse_details,
    std::optional<ConnectionManagementConfig> connection_management_config,
    int rv) {
  if (rv != OK) {
    CHECK_NE(rv, ERR_IO_PENDING);
    std::move(callback).Run(base::unexpected(rv));
    return;
  }
  base::expected<QuicSessionAttempt::CreateSessionResult, int> result =
      CreateSessionHelper(
          std::move(key), quic_version, cert_verify_flags, require_confirmation,
          std::move(peer_address), std::move(metadata),
          dns_resolution_start_time, dns_resolution_end_time,
          std::move(resolution_details), session_max_packet_length, net_log,
          // TODO(crbug.com/518753285): Stop passing the network explicitly,
          // instead rely on socket being already bound to the correct network.
          network, std::move(socket), session_creation_initiator,
          quic_connection_reuse_details, connection_management_config);
  std::move(callback).Run(std::move(result));
}

base::expected<QuicSessionAttempt::CreateSessionResult, int>
QuicSessionPool::CreateSessionHelper(
    QuicSessionAliasKey key,
    quic::ParsedQuicVersion quic_version,
    int cert_verify_flags,
    bool require_confirmation,
    IPEndPoint peer_address,
    ConnectionEndpointMetadata metadata,
    base::TimeTicks dns_resolution_start_time,
    base::TimeTicks dns_resolution_end_time,
    std::optional<ResolutionDetails> resolution_details,
    quic::QuicPacketLength session_max_packet_length,
    const NetLogWithSource& net_log,
    handles::NetworkHandle network,
    std::unique_ptr<DatagramClientSocket> socket,
    MultiplexedSessionCreationInitiator session_creation_initiator,
    QuicConnectionReuseDetails quic_connection_reuse_details,
    std::optional<ConnectionManagementConfig> connection_management_config) {
  const quic::QuicServerId& server_id = key.server_id();

  if (params_.migrate_sessions_on_network_change_v2 &&
      network == handles::kInvalidNetworkHandle) {
    network = socket->GetBoundNetwork();
    if (default_network_ == handles::kInvalidNetworkHandle) {
      // QuicSessionPool may miss the default network signal before its
      // creation, update |default_network_| when the first socket is bound
      // to the default network.
      default_network_ = network;
      connectivity_monitor_.SetInitialDefaultNetwork(default_network_);
    } else {
      UMA_HISTOGRAM_BOOLEAN("Net.QuicStreamFactory.DefaultNetworkMatch",
                            default_network_ == network);
    }
  }

  if (!helper_.get()) {
    helper_ = std::make_unique<QuicChromiumConnectionHelper>(clock_,
                                                             random_generator_);
  }

  if (!alarm_factory_.get()) {
    alarm_factory_ = std::make_unique<QuicChromiumAlarmFactory>(
        base::SingleThreadTaskRunner::GetCurrentDefault().get(), clock_);
  }

  quic::QuicConnectionId connection_id =
      quic::QuicUtils::CreateRandomConnectionId(random_generator_);
  std::unique_ptr<QuicServerInfo> server_info;
  if (params_.max_server_configs_stored_in_properties > 0) {
    server_info = std::make_unique<PropertiesBasedQuicServerInfo>(
        server_id, key.session_key().privacy_mode(),
        key.session_key().network_anonymization_key(), http_server_properties_);
  }
  std::unique_ptr<CryptoClientConfigHandle> crypto_config_handle =
      CreateCryptoConfigHandle(QuicCryptoClientConfigKey(key.session_key()));
  InitializeCachedStateInCryptoConfig(*crypto_config_handle, server_id,
                                      server_info);

  QuicChromiumPacketWriter* writer =
      new QuicChromiumPacketWriter(socket.get(), task_runner_.get());
  quic::QuicConnection* connection = new quic::QuicConnection(
      connection_id, quic::QuicSocketAddress(),
      ToQuicSocketAddress(peer_address), helper_.get(), alarm_factory_.get(),
      writer, true /* owns_writer */, quic::Perspective::IS_CLIENT,
      {quic_version}, connection_id_generator_);

  // Calculate the max packet length for this connection. If the session is
  // carrying proxy traffic, add the `additional_proxy_packet_length`.
  size_t max_packet_length = params_.max_packet_length;
  if (key.session_key().session_usage() == SessionUsage::kProxy) {
    max_packet_length += params_.additional_proxy_packet_length;
  }
  // Restrict that length by the session maximum, if given.
  if (session_max_packet_length > 0) {
    max_packet_length = std::min(static_cast<size_t>(session_max_packet_length),
                                 max_packet_length);
  }
  DVLOG(1) << "Session to " << key.destination().Serialize()
           << " has max packet length " << max_packet_length;
  connection->SetMaxPacketLength(max_packet_length);

  quic::QuicConfig config = config_;
  ConfigureInitialRttEstimate(server_id,
                              key.session_key().network_anonymization_key(),
                              key.session_key().proxy_chain(), &config);

  if (params_.enable_debugging_sni_in_transport_param &&
      IsGoogleHost(server_id.host())) {
    config.SetDebuggingSniToSend(server_id.host());
    // Send debugging SNI even when ECH GREASE is enabled.
    config.AddConnectionOptionsToSend({quic::kDSNI});
  }

  if (base::FeatureList::IsEnabled(features::kTryQuicByDefault)) {
    config.AddConnectionOptionsToSend(
        quic::ParseQuicTagVector(features::kQuicOptions.Get()));
  }

  if (base::FeatureList::IsEnabled(features::kIgnoreIpMatching)) {
    config.AddConnectionOptionsToSend(
        quic::ParseQuicTagVector(features::kNoIPQuicOption.Get()));
  }

  auto keep_alive_timeout = ping_timeout_;
  bool enabled_connection_keep_alive = false;
  if (connection_management_config.has_value() &&
      connection_management_config->keep_alive_config.has_value()) {
    config.SetIdleNetworkTimeout(quic::QuicTime::Delta::FromSeconds(
        connection_management_config->keep_alive_config
            ->idle_timeout_in_seconds));
    config.AddConnectionOptionsToSend(
        quic::ParseQuicTagVector(connection_management_config->keep_alive_config
                                     ->quic_connection_options));
    keep_alive_timeout = quic::QuicTime::Delta::FromSeconds(
        connection_management_config->keep_alive_config
            ->ping_interval_in_seconds);
    enabled_connection_keep_alive =
        connection_management_config->keep_alive_config
            ->enable_connection_keep_alive;
  }
  connection->set_keep_alive_ping_timeout(keep_alive_timeout);

  // Use the factory to create a new socket performance watcher, and pass the
  // ownership to QuicChromiumClientSession.
  std::unique_ptr<SocketPerformanceWatcher> socket_performance_watcher;
  if (socket_performance_watcher_factory_) {
    socket_performance_watcher =
        socket_performance_watcher_factory_->CreateSocketPerformanceWatcher(
            SocketPerformanceWatcherFactory::PROTOCOL_QUIC,
            peer_address.address());
  }

  // Wait for handshake confirmation before allowing streams to be created if
  // either this session or the pool require confirmation.
  if (!has_quic_ever_worked_on_current_network_) {
    require_confirmation = true;
  }

  auto new_session = std::make_unique<QuicChromiumClientSession>(
      connection, std::move(socket), this, quic_crypto_client_stream_factory_,
      clock_, transport_security_state_, ssl_config_service_,
      std::move(server_info), std::move(key), require_confirmation,
      params_.migrate_sessions_early_v2,
      params_.migrate_sessions_on_network_change_v2, default_network_,
      retransmittable_on_wire_timeout_, params_.migrate_idle_sessions,
      params_.allow_port_migration, params_.idle_session_migration_period,
      params_.multi_port_probing_interval,
      params_.max_time_on_non_default_network,
      params_.max_migrations_to_non_default_network_on_write_error,
      params_.max_migrations_to_non_default_network_on_path_degrading,
      yield_after_packets_, yield_after_duration_, cert_verify_flags, config,
      std::move(crypto_config_handle),
      network_connection_.connection_description(), dns_resolution_start_time,
      dns_resolution_end_time, std::move(resolution_details), tick_clock_,
      task_runner_.get(), std::move(socket_performance_watcher), metadata,
      params_.enable_origin_frame, params_.allow_server_migration,
      session_creation_initiator, net_log, quic_connection_reuse_details);
  QuicChromiumClientSession* session = new_session.get();

  all_sessions_.insert(std::move(new_session));
  writer->set_delegate(session);
  session->AddConnectivityObserver(&connectivity_monitor_);

  net_log.AddEventReferencingSource(
      NetLogEventType::QUIC_SESSION_POOL_JOB_RESULT,
      session->net_log().source());

  session->Initialize();

  if (enabled_connection_keep_alive) {
    session->SetPeriodicConnectionKeepAlive(true);
  }
  bool closed_during_initialize =
      !all_sessions_.contains(session) || !session->connection()->connected();
  UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.ClosedDuringInitializeSession",
                        closed_during_initialize);
  if (closed_during_initialize) {
    DLOG(DFATAL) << "Session closed during initialize";
    return base::unexpected(ERR_CONNECTION_CLOSED);
  }
  return QuicSessionAttempt::CreateSessionResult{session, network};
}

void QuicSessionPool::ActivateSession(const QuicSessionAliasKey& key,
                                      QuicChromiumClientSession* session,
                                      std::set<std::string> dns_aliases) {
  DCHECK(!HasActiveSession(key.session_key()));
  UMA_HISTOGRAM_COUNTS_1M("Net.QuicActiveSessions", active_sessions_.size());
  ActivateAndMapSessionToAliasKey(session, key, std::move(dns_aliases));
  const IPEndPoint peer_address =
      ToIPEndPoint(session->connection()->peer_address());
  DCHECK(!ip_aliases_[peer_address].contains(session));
  ip_aliases_[peer_address].insert(session);
  DCHECK(!session_peer_ip_.contains(session));
  session_peer_ip_[session] = peer_address;
}

void QuicSessionPool::MarkAllActiveSessionsGoingAway(
    AllActiveSessionsGoingAwayReason reason) {
  net_log_.AddEvent(
      NetLogEventType::QUIC_SESSION_POOL_MARK_ALL_ACTIVE_SESSIONS_GOING_AWAY);
  base::UmaHistogramCounts10000(
      base::StrCat({"Net.QuicActiveSessionCount.",
                    AllActiveSessionsGoingAwayReasonToString(reason)}),
      active_sessions_.size());
  while (!active_sessions_.empty()) {
    QuicChromiumClientSession* session = active_sessions_.begin()->second;
    // If IP address change is detected, disable session's connectivity
    // monitoring by remove the Delegate.
    if (reason == kIPAddressChanged) {
      connectivity_monitor_.OnSessionGoingAwayOnIPAddressChange(session);
    }
    OnSessionGoingAway(session);
  }
}

void QuicSessionPool::ConfigureInitialRttEstimate(
    const quic::QuicServerId& server_id,
    const NetworkAnonymizationKey& network_anonymization_key,
    const ProxyChain& proxy_chain,
    quic::QuicConfig* config) {
  const base::TimeDelta* srtt = GetServerNetworkStatsSmoothedRtt(
      server_id, network_anonymization_key, proxy_chain);
  // Sometimes *srtt is negative. See https://crbug.com/1225616.
  // TODO(ricea): When the root cause of the negative value is fixed, change the
  // non-negative assertion to a DCHECK.
  if (srtt && srtt->is_positive()) {
    SetInitialRttEstimate(*srtt, INITIAL_RTT_CACHED, config);
    return;
  }

  NetworkChangeNotifier::ConnectionType type =
      network_connection_.connection_type();
  if (type == NetworkChangeNotifier::CONNECTION_2G) {
    SetInitialRttEstimate(base::Milliseconds(1200), INITIAL_RTT_CACHED, config);
    return;
  }

  if (type == NetworkChangeNotifier::CONNECTION_3G) {
    SetInitialRttEstimate(base::Milliseconds(400), INITIAL_RTT_CACHED, config);
    return;
  }

  if (params_.initial_rtt_for_handshake.is_positive()) {
    SetInitialRttEstimate(
        base::Microseconds(params_.initial_rtt_for_handshake.InMicroseconds()),
        INITIAL_RTT_DEFAULT, config);
    return;
  }

  SetInitialRttEstimate(base::TimeDelta(), INITIAL_RTT_DEFAULT, config);
}

int64_t QuicSessionPool::GetServerNetworkStatsSmoothedRttInMicroseconds(
    const quic::QuicServerId& server_id,
    const NetworkAnonymizationKey& network_anonymization_key,
    const ProxyChain& proxy_chain) const {
  const base::TimeDelta* srtt = GetServerNetworkStatsSmoothedRtt(
      server_id, network_anonymization_key, proxy_chain);
  return srtt == nullptr ? 0 : srtt->InMicroseconds();
}

const base::TimeDelta* QuicSessionPool::GetServerNetworkStatsSmoothedRtt(
    const quic::QuicServerId& server_id,
    const NetworkAnonymizationKey& network_anonymization_key,
    const ProxyChain& proxy_chain) const {
  // ServerNetworkStats are not partitioned by proxy chain, so only use them
  // for direct connections to avoid mixing measurements from different paths.
  if (!proxy_chain.is_direct()) {
    return nullptr;
  }
  url::SchemeHostPort server("https", server_id.host(), server_id.port());
  const ServerNetworkStats* stats =
      http_server_properties_->GetServerNetworkStats(server,
                                                     network_anonymization_key);
  if (stats == nullptr) {
    return nullptr;
  }
  return &(stats->srtt);
}

std::optional<base::TimeDelta> QuicSessionPool::GetSmoothedRtt(
    const quic::QuicServerId& server_id,
    const NetworkAnonymizationKey& network_anonymization_key,
    const ProxyChain& proxy_chain) const {
  const base::TimeDelta* srtt = GetServerNetworkStatsSmoothedRtt(
      server_id, network_anonymization_key, proxy_chain);
  if (srtt && srtt->is_positive()) {
    return *srtt;
  }
  if (network_quality_estimator_) {
    return network_quality_estimator_->GetTransportRTT();
  }
  return std::nullopt;
}

bool QuicSessionPool::WasQuicRecentlyBroken(
    const QuicSessionKey& session_key) const {
  const AlternativeService alternative_service(
      NextProto::kProtoQUIC, HostPortPair(session_key.server_id().host(),
                                          session_key.server_id().port()));
  return http_server_properties_->WasAlternativeServiceRecentlyBroken(
      alternative_service, session_key.network_anonymization_key());
}

void QuicSessionPool::InitializeMigrationOptions() {
  // The following list of options cannot be set immediately until
  // prerequisites are met. Cache the initial setting in local variables and
  // reset them in |params_|.
  bool migrate_sessions_on_network_change =
      params_.migrate_sessions_on_network_change_v2;
  bool migrate_sessions_early = params_.migrate_sessions_early_v2;
  bool retry_on_alternate_network_before_handshake =
      params_.retry_on_alternate_network_before_handshake;
  bool migrate_idle_sessions = params_.migrate_idle_sessions;
  bool allow_port_migration = params_.allow_port_migration;
  params_.migrate_sessions_on_network_change_v2 = false;
  params_.migrate_sessions_early_v2 = false;
  params_.allow_port_migration = false;
  params_.retry_on_alternate_network_before_handshake = false;
  params_.migrate_idle_sessions = false;

  // TODO(zhongyi): deprecate |goaway_sessions_on_ip_change| if the experiment
  // is no longer needed.
  // goaway_sessions_on_ip_change and close_sessions_on_ip_change should never
  // be simultaneously set to true.
  DCHECK(!(params_.close_sessions_on_ip_change &&
           params_.goaway_sessions_on_ip_change));

  bool handle_ip_change = params_.close_sessions_on_ip_change ||
                          params_.goaway_sessions_on_ip_change;
  // If IP address changes are handled explicitly, connection migration should
  // not be set.
  DCHECK(!(handle_ip_change && migrate_sessions_on_network_change));

  if (handle_ip_change) {
    NetworkChangeNotifier::AddIPAddressObserver(this);
  }

  if (allow_port_migration) {
    params_.allow_port_migration = true;
    if (migrate_idle_sessions) {
      params_.migrate_idle_sessions = true;
    }
  }

  if (!NetworkChangeNotifier::AreNetworkHandlesSupported()) {
    return;
  }

  NetworkChangeNotifier::AddNetworkObserver(this);
  // Perform checks on the connection migration options.
  if (!migrate_sessions_on_network_change) {
    DCHECK(!migrate_sessions_early);
    return;
  }

  // Enable migration on platform notifications.
  params_.migrate_sessions_on_network_change_v2 = true;

  if (!migrate_sessions_early) {
    DCHECK(!retry_on_alternate_network_before_handshake);
    return;
  }

  // Enable migration on path degrading.
  params_.migrate_sessions_early_v2 = true;
  // Set retransmittable on wire timeout for migration on path degrading if no
  // value is specified.
  if (retransmittable_on_wire_timeout_.IsZero()) {
    retransmittable_on_wire_timeout_ = quic::QuicTime::Delta::FromMicroseconds(
        kDefaultRetransmittableOnWireTimeout.InMicroseconds());
  }

  // Enable retry on alternate network before handshake.
  if (retry_on_alternate_network_before_handshake) {
    params_.retry_on_alternate_network_before_handshake = true;
  }

  // Enable migration for idle sessions.
  if (migrate_idle_sessions) {
    params_.migrate_idle_sessions = true;
  }
}

void QuicSessionPool::InitializeCachedStateInCryptoConfig(
    const CryptoClientConfigHandle& crypto_config_handle,
    const quic::QuicServerId& server_id,
    const std::unique_ptr<QuicServerInfo>& server_info) {
  quic::QuicCryptoClientConfig::CachedState* cached =
      crypto_config_handle.GetConfig()->LookupOrCreate(server_id);

  if (!cached->IsEmpty()) {
    return;
  }

  if (!server_info || !server_info->Load()) {
    return;
  }

  cached->Initialize(server_info->state().server_config,
                     server_info->state().source_address_token,
                     server_info->state().certs, server_info->state().cert_sct,
                     server_info->state().chlo_hash,
                     server_info->state().server_config_sig, clock_->WallNow(),
                     quic::QuicWallTime::Zero());
}

void QuicSessionPool::ProcessGoingAwaySession(
    QuicChromiumClientSession* session,
    const quic::QuicServerId& server_id,
    bool session_was_active) {
  if (!http_server_properties_) {
    return;
  }

  const quic::QuicConnectionStats& stats = session->connection()->GetStats();
  const AlternativeService alternative_service(
      NextProto::kProtoQUIC, HostPortPair(server_id.host(), server_id.port()));

  url::SchemeHostPort server("https", server_id.host(), server_id.port());
  // Do nothing if QUIC is currently marked as broken.
  if (http_server_properties_->IsAlternativeServiceBroken(
          alternative_service,
          session->quic_session_key().network_anonymization_key())) {
    return;
  }

  // ServerNetworkStats are not partitioned by proxy chain, so only record
  // them for direct connections to avoid mixing measurements from different
  // paths.
  const bool record_network_stats =
      session->quic_session_key().proxy_chain().is_direct();

  if (session->OneRttKeysAvailable()) {
    http_server_properties_->ConfirmAlternativeService(
        alternative_service,
        session->quic_session_key().network_anonymization_key());
    if (record_network_stats) {
      ServerNetworkStats network_stats;
      network_stats.srtt = base::Microseconds(stats.srtt_us);
      network_stats.bandwidth_estimate = stats.estimated_bandwidth;
      http_server_properties_->SetServerNetworkStats(
          server, session->quic_session_key().network_anonymization_key(),
          network_stats);
    }
    return;
  }

  if (record_network_stats) {
    http_server_properties_->ClearServerNetworkStats(
        server, session->quic_session_key().network_anonymization_key());
  }

  UMA_HISTOGRAM_COUNTS_1M("Net.QuicHandshakeNotConfirmedNumPacketsReceived",
                          stats.packets_received);

  if (!session_was_active) {
    return;
  }

  // TODO(rch):  In the special case where the session has received no packets
  // from the peer, we should consider blocking this differently so that we
  // still race TCP but we don't consider the session connected until the
  // handshake has been confirmed.
  HistogramBrokenAlternateProtocolLocation(
      BROKEN_ALTERNATE_PROTOCOL_LOCATION_QUIC_SESSION_POOL);

  // Since the session was active, there's no longer an HttpStreamFactory::Job
  // running which can mark it broken, unless the TCP job also fails. So to
  // avoid not using QUIC when we otherwise could, we mark it as recently
  // broken, which means that 0-RTT will be disabled but we'll still race.
  http_server_properties_->MarkAlternativeServiceRecentlyBroken(
      alternative_service,
      session->quic_session_key().network_anonymization_key());
}

void QuicSessionPool::ActivateAndMapSessionToAliasKey(
    QuicChromiumClientSession* session,
    QuicSessionAliasKey key,
    std::set<std::string> dns_aliases) {
  active_sessions_[key.session_key()] = session;
  dns_aliases_by_session_key_[key.session_key()] = std::move(dns_aliases);
  session_aliases_[session].insert(std::move(key));
}

void QuicSessionPool::UnmapSessionFromSessionAliases(
    QuicChromiumClientSession* session) {
  for (const auto& key : session_aliases_[session]) {
    dns_aliases_by_session_key_.erase(key.session_key());
  }
  session_aliases_.erase(session);
}

std::unique_ptr<QuicSessionPool::CryptoClientConfigHandle>
QuicSessionPool::CreateCryptoConfigHandle(QuicCryptoClientConfigKey key) {
  if (!use_network_anonymization_key_for_crypto_configs_) {
    key.network_anonymization_key = NetworkAnonymizationKey();
  }

  // If there's a matching entry in |active_crypto_config_map_|, create a
  // CryptoClientConfigHandle for it.
  auto map_iterator = active_crypto_config_map_.find(key);
  if (map_iterator != active_crypto_config_map_.end()) {
    DCHECK_GT(map_iterator->second->num_refs(), 0);

    // If there's an active matching crypto config, there shouldn't also be an
    // inactive matching crypto config.
    DCHECK(recent_crypto_config_map_.Peek(key) ==
           recent_crypto_config_map_.end());

    return std::make_unique<CryptoClientConfigHandle>(map_iterator);
  }

  // If there's a matching entry in |recent_crypto_config_map_|, move it to
  // |active_crypto_config_map_| and create a CryptoClientConfigHandle for it.
  auto mru_iterator = recent_crypto_config_map_.Peek(key);
  if (mru_iterator != recent_crypto_config_map_.end()) {
    DCHECK_EQ(mru_iterator->second->num_refs(), 0);

    map_iterator =
        active_crypto_config_map_.emplace(key, std::move(mru_iterator->second))
            .first;
    recent_crypto_config_map_.Erase(mru_iterator);
    return std::make_unique<CryptoClientConfigHandle>(map_iterator);
  }

  std::set<std::string> hostnames_to_allow_unknown_roots =
      HostsFromSchemeHostPorts(params_.origins_to_force_quic_on);
  if (params_.force_quic_everywhere) {
    hostnames_to_allow_unknown_roots.insert("");
  }

  // Otherwise, create a new QuicCryptoClientConfigOwner and add it to
  // |active_crypto_config_map_|.
  std::unique_ptr<QuicCryptoClientConfigOwner> crypto_config_owner =
      std::make_unique<QuicCryptoClientConfigOwner>(
          key.network_anonymization_key,
          std::make_unique<ProofVerifierChromium>(
              cert_verifier_, transport_security_state_, sct_auditing_delegate_,
              std::move(hostnames_to_allow_unknown_roots),
              key.network_anonymization_key),
          std::make_unique<quic::QuicClientSessionCache>(
              kDefaultQuicSessionCacheSize),
          kDefaultQuicSessionCacheSize, this);

  quic::QuicCryptoClientConfig* crypto_config = crypto_config_owner->config();
  crypto_config->AddCanonicalSuffix(".c.youtube.com");
  crypto_config->AddCanonicalSuffix(".ggpht.com");
  crypto_config->AddCanonicalSuffix(".googlevideo.com");
  crypto_config->AddCanonicalSuffix(".googleusercontent.com");
  crypto_config->AddCanonicalSuffix(".gvt1.com");
  crypto_config->set_alps_use_new_codepoint(params_.use_new_alps_codepoint);

  ConfigureQuicCryptoClientConfig(*crypto_config);

  if (!prefer_aes_gcm_recorded_) {
    bool prefer_aes_gcm =
        !crypto_config->aead.empty() && (crypto_config->aead[0] == quic::kAESG);
    UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.PreferAesGcm", prefer_aes_gcm);
    prefer_aes_gcm_recorded_ = true;
  }

  map_iterator =
      active_crypto_config_map_.emplace(key, std::move(crypto_config_owner))
          .first;
  return std::make_unique<CryptoClientConfigHandle>(map_iterator);
}

void QuicSessionPool::OnAllCryptoClientRefReleased(
    QuicCryptoClientConfigMap::iterator& map_iterator) {
  DCHECK_EQ(0, map_iterator->second->num_refs());
  recent_crypto_config_map_.Put(map_iterator->first,
                                std::move(map_iterator->second));
  active_crypto_config_map_.erase(map_iterator);
}

void QuicSessionPool::CollectDataOnPlatformNotification(
    enum QuicPlatformNotification notification,
    handles::NetworkHandle affected_network) const {
  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.PlatformNotification",
                            notification, NETWORK_NOTIFICATION_MAX);
  connectivity_monitor_.RecordConnectivityStatsToHistograms(
      QuicPlatformNotificationToString(notification), affected_network);
}

void QuicSessionPool::CheckQuicSessionKeyMismatch(
    const QuicSessionKey& session_key,
    url::SchemeHostPort destination) const {
  // Check and record if we have some sort of partially matched results for the
  // `session_key`.
  QuicSessionKeyPartialMatchResult partial_match_result =
      QuicSessionKeyPartialMatchResult::kNoMatch;
  std::optional<QuicSessionKey> active_key = std::nullopt;
  if (auto active_session_key = GetActiveSessionToServerId(session_key)) {
    partial_match_result =
        QuicSessionKeyPartialMatchResult::kMatchedToActiveSession;
    active_key = active_session_key;
  } else if (auto active_job_key = GetActiveJobToServerId(session_key)) {
    partial_match_result =
        QuicSessionKeyPartialMatchResult::kMatchedToActiveJob;
    active_key = active_job_key;
  }

  // PostTask the histogram recording since we do not want to incur many
  // overheads for the execution of the session creation with checking field
  // mismatches.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&LogSessionKeyMismatch, partial_match_result,
                                destination, session_key, active_key));
}

std::unique_ptr<QuicCryptoClientConfigHandle>
QuicSessionPool::GetCryptoConfigForTesting(QuicCryptoClientConfigKey key) {
  return CreateCryptoConfigHandle(std::move(key));
}

bool QuicSessionPool::CryptoConfigCacheIsEmptyForTesting(
    const quic::QuicServerId& server_id,
    QuicCryptoClientConfigKey key) {
  quic::QuicCryptoClientConfig::CachedState* cached = nullptr;
  if (!use_network_anonymization_key_for_crypto_configs_) {
    key.network_anonymization_key = NetworkAnonymizationKey();
  }
  auto map_iterator = active_crypto_config_map_.find(key);
  if (map_iterator != active_crypto_config_map_.end()) {
    cached = map_iterator->second->config()->LookupOrCreate(server_id);
  } else {
    auto mru_iterator = recent_crypto_config_map_.Peek(key);
    if (mru_iterator != recent_crypto_config_map_.end()) {
      cached = mru_iterator->second->config()->LookupOrCreate(server_id);
    }
  }
  return !cached || cached->IsEmpty();
}

bool QuicSessionPool::CryptoConfigSessionCacheIsEmptyForTesting(
    QuicCryptoClientConfigKey key) {
  if (!use_network_anonymization_key_for_crypto_configs_) {
    key.network_anonymization_key = NetworkAnonymizationKey();
  }
  quic::QuicCryptoClientConfig* config = nullptr;
  auto map_iterator = active_crypto_config_map_.find(key);
  if (map_iterator != active_crypto_config_map_.end()) {
    config = map_iterator->second->config();
  } else {
    auto mru_iterator = recent_crypto_config_map_.Peek(key);
    if (mru_iterator != recent_crypto_config_map_.end()) {
      config = mru_iterator->second->config();
    }
  }

  if (!config || !config->session_cache()) {
    return true;
  }
  return config->session_cache()->GetSize() == 0;
}

#if BUILDFLAG(IS_ANDROID)
void QuicSessionPool::RegisterPreFreezeTask() {
  if (base::FeatureList::IsEnabled(features::kCloseQuicSessionsOnPreFreeze) &&
      base::android::PreFreezeBackgroundMemoryTrimmer::SupportsModernTrim()) {
    base::PostOnFreezeTask(task_runner_, FROM_HERE,
                           base::BindOnce(&QuicSessionPool::OnPreFreeze,
                                          weak_factory_.GetWeakPtr()));
  }
}

void QuicSessionPool::OnPreFreeze() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CloseAllSessions(ERR_ABORTED, quic::QUIC_PEER_GOING_AWAY);
  RegisterPreFreezeTask();
}
#endif

}  // namespace net
