// Copyright 2026 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/http/http_stream_pool_attempt.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <ranges>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/memory/raw_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_id_helper.h"
#include "base/types/expected.h"
#include "net/base/completion_once_callback.h"
#include "net/base/ip_endpoint.h"
#include "net/base/load_timing_info.h"
#include "net/base/net_errors.h"
#include "net/base/net_export.h"
#include "net/dns/host_resolver.h"
#include "net/dns/public/host_resolver_results.h"
#include "net/http/http_basic_stream.h"
#include "net/http/http_network_session.h"
#include "net/http/http_stream_key.h"
#include "net/http/http_stream_pool.h"
#include "net/log/net_log_with_source.h"
#include "net/socket/stream_attempt.h"
#include "net/socket/tcp_stream_attempt.h"
#include "net/socket/tls_stream_attempt.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_info.h"

namespace net {

// A single inner TCP-based attempt.
// TODO(crbug.com/457478038): Figure out better name for this class. Too many
// "Attempt" classes.
class HttpStreamPool::Attempt::TcpAttempt : public TlsStreamAttempt::Delegate {
 public:
  TcpAttempt(Attempt& owner, IPEndPoint ip_endpoint)
      : owner_(owner), ip_endpoint_(std::move(ip_endpoint)) {
    if (owner_->using_tls_) {
      attempt_ = std::make_unique<TlsStreamAttempt>(
          &owner_->stream_attempt_params_.get(), ip_endpoint_,
          delegate().GetHttpStreamKey().target_network(), owner_->track_,
          HostPortPair::FromSchemeHostPort(
              delegate().GetHttpStreamKey().destination()),
          delegate().GetBaseSSLConfig(),
          /*delegate=*/this);
    } else {
      attempt_ = std::make_unique<TcpStreamAttempt>(
          &owner_->stream_attempt_params_.get(), ip_endpoint_,
          delegate().GetHttpStreamKey().target_network(), owner_->track_);
    }
  }

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

  ~TcpAttempt() override = default;

  const IPEndPoint& ip_endpoint() const { return ip_endpoint_; }

  StreamAttempt& stream_attempt() const { return *attempt_.get(); }

  int Start(CompletionOnceCallback callback) {
    start_time_ = base::TimeTicks::Now();
    int rv = attempt_->Start(std::move(callback));
    if (rv == ERR_IO_PENDING && !owner_->observed_slow_attempt_) {
      slow_timer_.Start(FROM_HERE, HttpStreamPool::GetConnectionAttemptDelay(),
                        base::BindOnce(&TcpAttempt::OnSlowTimerFired,
                                       base::Unretained(this)));
    }
    return rv;
  }

  // Called when the delegate's service endpoint request is updated or finished
  // and endpoints are ready to start the TLS handshake. Note that this method
  // can be called multiple times, even after the TLS handshake has already
  // started.
  void MaybeStartTlsHandshake() {
    DCHECK(delegate().GetServiceEndpointRequest().EndpointsCryptoReady());
    if (!tls_handshake_ready_callback_) {
      return;
    }

    // Resume the slow timer if it was stopped.
    CHECK(!slow_timer_.IsRunning());
    if (!owner_->observed_slow_attempt_) {
      CHECK(!is_slow_);
      CHECK_GE(tcp_handshake_complete_time_, start_time_);
      // Timer is not guaranteed to fire exactly on time, so we need to check
      // if we are already past the delay.
      base::TimeDelta tcp_handshake_duration =
          tcp_handshake_complete_time_ - start_time_;
      if (HttpStreamPool::GetConnectionAttemptDelay() >
          tcp_handshake_duration) {
        slow_timer_.Start(FROM_HERE,
                          HttpStreamPool::GetConnectionAttemptDelay() -
                              tcp_handshake_duration,
                          base::BindOnce(&TcpAttempt::OnSlowTimerFired,
                                         base::Unretained(this)));
      } else {
        OnSlowTimerFired();
      }
    }

    std::move(tls_handshake_ready_callback_).Run(OK);
  }

  // TlsStreamAttempt::Delegate implementation.

  void OnTcpHandshakeComplete() override {
    tcp_handshake_complete_time_ = base::TimeTicks::Now();
  }

  // Called from TlsStreamAttempt when it is ready to start the TLS handshake
  // (i.e., TCP handshake is complete). If the endpoint is ready to start TLS
  // handshake, returns OK. Otherwise, returns ERR_IO_PENDING and stores the
  // callback to be called when the endpoint is ready to start TLS handshake.
  int WaitForTlsHandshakeReady(CompletionOnceCallback callback) override {
    if (owner_->is_crypto_ready_) {
      return OK;
    }

    // Pause the slow timer while waiting for the endpoints to be ready for TLS
    // handshake.
    if (slow_timer_.IsRunning()) {
      slow_timer_.Stop();
    }

    tls_handshake_ready_callback_ = std::move(callback);
    return ERR_IO_PENDING;
  }

  // Called from TlsStreamAttempt to get the service endpoint for TLS
  // handshake. Always called after the endpoint is ready to start TLS
  // handshake.
  base::expected<ServiceEndpoint, TlsStreamAttempt::GetServiceEndpointError>
  GetServiceEndpointForTlsHandshake() override {
    CHECK(delegate().GetServiceEndpointRequest().EndpointsCryptoReady());
    return owner_->GetServiceEndpointForTlsHandshake(ip_endpoint_);
  }

 private:
  Attempt::Delegate& delegate() const { return owner_->delegate_.get(); }

  void OnSlowTimerFired() {
    is_slow_ = true;
    owner_->OnTcpAttemptSlow(this);
  }

  const raw_ref<Attempt> owner_;
  const IPEndPoint ip_endpoint_;

  base::OneShotTimer slow_timer_;
  bool is_slow_ = false;

  base::TimeTicks start_time_;
  base::TimeTicks tcp_handshake_complete_time_;

  std::unique_ptr<StreamAttempt> attempt_;
  CompletionOnceCallback tls_handshake_ready_callback_;
};

HttpStreamPool::Attempt::Attempt(
    Delegate& delegate,
    const StreamAttemptParams& stream_attempt_params,
    NetLogWithSource net_log)
    : delegate_(delegate),
      stream_attempt_params_(stream_attempt_params),
      using_tls_(GURL::SchemeIsCryptographic(
          delegate_->GetHttpStreamKey().destination().scheme())),
      track_(base::trace_event::GetNextGlobalTraceId()),
      net_log_(std::move(net_log)) {
  if (delegate_->GetServiceEndpointRequest().EndpointsCryptoReady()) {
    is_crypto_ready_ = true;
  }
}

HttpStreamPool::Attempt::~Attempt() = default;

void HttpStreamPool::Attempt::Start() {
  MaybeAttempt();
}

void HttpStreamPool::Attempt::ProcessServiceEndpointChanges() {
  // First, trigger TLS handshakes for existing attempts if endpoints are ready
  // to start TLS handshakes.
  HostResolver::ServiceEndpointRequest& service_endpoint_request =
      delegate_->GetServiceEndpointRequest();
  if (!is_crypto_ready_ && service_endpoint_request.EndpointsCryptoReady()) {
    is_crypto_ready_ = true;
    // Starting TLS handshake may fail synchronously (also may succeed
    // synchronously, unlikely to happen in reality though). Synchronous
    // completion of an attempt could end up deleting `this`. Use WeakPtr to
    // detect whether `this` is deleted. Alternatively, we can use PostTask to
    // schedule `this` to be deleted when an attempt succeeds or fails. However,
    // scheduling tasks does have some overhead so we use WeakPtr here.
    base::WeakPtr<Attempt> weak_self = weak_ptr_factory_.GetWeakPtr();
    for (auto& attempt : {ipv6_attempt_.get(), ipv4_attempt_.get()}) {
      if (!attempt) {
        continue;
      }
      attempt->MaybeStartTlsHandshake();
      if (!weak_self) {
        // `this` is deleted. Have to return immediately.
        return;
      }
    }
  }

  // Second, try to start new attempts if possible.
  MaybeAttempt();
}

void HttpStreamPool::Attempt::MaybeAttempt() {
  std::optional<IPEndPoint> ip_endpoint = GetIPEndPointToAttempt();
  if (!ip_endpoint.has_value()) {
    // If all endpoints are failed, notify the delegate. Otherwise, wait for
    // further endpoints update, and/or completing in-flight attempts.
    const bool all_endpoints_failed =
        !ipv4_attempt_ && !ipv6_attempt_ &&
        delegate_->IsServiceEndpointRequestFinished();
    if (all_endpoints_failed) {
      base::WeakPtr<Attempt> weak_this = weak_ptr_factory_.GetWeakPtr();
      int error = most_recent_attempt_failure_.has_value()
                      ? most_recent_attempt_failure_->error
                      : ERR_NAME_NOT_RESOLVED;
      delegate_->OnAttemptFailure(this, error);
      // Do not add code, `this` is deleted.
      CHECK(!weak_this);
    }
    return;
  }

  StartAttempt(std::move(*ip_endpoint));
  // `this` may be deleted at this point. For example, if the attempt fails
  // synchronously and it's the last attempt.
}

void HttpStreamPool::Attempt::StartAttempt(IPEndPoint ip_endpoint) {
  const bool is_ipv4 = ip_endpoint.address().IsIPv4();
  DCHECK(is_ipv4 || ip_endpoint.address().IsIPv6());
  auto& attempt = is_ipv4 ? ipv4_attempt_ : ipv6_attempt_;
  CHECK(!attempt);

  auto [_, inserted] = attempted_endpoints_.insert(ip_endpoint);
  CHECK(inserted);

  attempt = std::make_unique<TcpAttempt>(*this, std::move(ip_endpoint));
  TcpAttempt* raw_attempt = attempt.get();
  net_log_.AddEvent(
      NetLogEventType::HTTP_STREAM_POOL_TCP_BASED_ATTEMPT_START, [&] {
        base::DictValue dict;
        dict.Set("ip_endpoint", raw_attempt->ip_endpoint().ToString());
        raw_attempt->stream_attempt().net_log().source().AddToEventParameters(
            dict);
        return dict;
      });
  raw_attempt->stream_attempt().net_log().AddEventReferencingSource(
      NetLogEventType::TCP_BASED_ATTEMPT_BOUND_TO_POOL, net_log_.source());

  int rv = attempt->Start(base::BindOnce(&Attempt::OnTcpAttemptComplete,
                                         weak_ptr_factory_.GetWeakPtr(),
                                         raw_attempt));
  if (rv != ERR_IO_PENDING) {
    OnTcpAttemptComplete(raw_attempt, rv);
  }
}

bool HttpStreamPool::Attempt::IsEndpointUsable(const ServiceEndpoint& endpoint,
                                               bool svcb_optional) const {
  // No ALPNs means that the endpoint is an authority A/AAAA endpoint, even if
  // we are still in the middle of DNS resolution.
  if (endpoint.metadata.supported_protocol_alpns.empty()) {
    return svcb_optional;
  }

  // See https://www.rfc-editor.org/rfc/rfc9460.html#section-9.3. Endpoints are
  // usable if there is an overlap between the endpoint's ALPNs and the
  // configured ones.
  return std::ranges::any_of(
      endpoint.metadata.supported_protocol_alpns, [&](const auto& alpn) {
        NextProto next_proto = NextProtoFromString(alpn);
        if (!kTcpBasedProtocols.Has(next_proto)) {
          return false;
        }
        return std::ranges::contains(delegate_->GetAlpnProtos(), next_proto);
      });
}

std::optional<IPEndPoint> HttpStreamPool::Attempt::GetIPEndPointToAttempt()
    const {
  // Don't attempt more when the first attempt already exists and it's not slow.
  const bool has_attempt = ipv4_attempt_ || ipv6_attempt_;
  if (has_attempt && !observed_slow_attempt_) {
    return std::nullopt;
  }

  HostResolver::ServiceEndpointRequest& service_endpoint_request =
      delegate_->GetServiceEndpointRequest();
  if (service_endpoint_request.GetEndpointResults().empty()) {
    return std::nullopt;
  }

  // If there is a previous failed attempt, prefer the other address family.
  // Otherwise prefer IPv6.
  const bool prefer_ipv6 =
      !most_recent_attempt_failure_ ||
      !most_recent_attempt_failure_->ip_endpoint.address().IsIPv6();

  const bool svcb_optional = delegate_->IsSvcbOptional();
  const base::span<const ServiceEndpoint>& endpoint_results =
      service_endpoint_request.GetEndpointResults();

  for (bool use_ipv6 : {prefer_ipv6, !prefer_ipv6}) {
    if ((use_ipv6 && ipv6_attempt_) || (!use_ipv6 && ipv4_attempt_)) {
      continue;
    }

    for (const auto& service_endpoint : endpoint_results) {
      if (!IsEndpointUsable(service_endpoint, svcb_optional)) {
        continue;
      }

      const std::vector<IPEndPoint>& ip_endpoints =
          use_ipv6 ? service_endpoint.ipv6_endpoints
                   : service_endpoint.ipv4_endpoints;

      for (const auto& ip_endpoint : ip_endpoints) {
        if (attempted_endpoints_.contains(ip_endpoint)) {
          continue;
        }
        return ip_endpoint;
      }
    }
  }

  return std::nullopt;
}

base::expected<ServiceEndpoint, TlsStreamAttempt::GetServiceEndpointError>
HttpStreamPool::Attempt::GetServiceEndpointForTlsHandshake(
    const IPEndPoint& ip_endpoint) const {
  HostResolver::ServiceEndpointRequest& service_endpoint_request =
      delegate_->GetServiceEndpointRequest();

  const bool svcb_optional = delegate_->IsSvcbOptional();
  for (const auto& service_endpoint :
       service_endpoint_request.GetEndpointResults()) {
    if (!IsEndpointUsable(service_endpoint, svcb_optional)) {
      continue;
    }
    const std::vector<IPEndPoint>& ip_endpoints =
        ip_endpoint.address().IsIPv4() ? service_endpoint.ipv4_endpoints
                                       : service_endpoint.ipv6_endpoints;

    if (!std::ranges::contains(ip_endpoints, ip_endpoint)) {
      continue;
    }
    return service_endpoint;
  }

  return base::unexpected(TlsStreamAttempt::GetServiceEndpointError::kAbort);
}

void HttpStreamPool::Attempt::OnTcpAttemptSlow(TcpAttempt* attempt) {
  CHECK(attempt == ipv4_attempt_.get() || attempt == ipv6_attempt_.get());
  net_log_.AddEvent(
      NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_TCP_BASED_ATTEMPT_SLOW,
      [&] {
        base::DictValue dict;
        dict.Set("ip_endpoint", attempt->ip_endpoint().ToString());
        attempt->stream_attempt().net_log().source().AddToEventParameters(dict);
        return dict;
      });
  observed_slow_attempt_ = true;
  MaybeAttempt();
}

void HttpStreamPool::Attempt::OnTcpAttemptComplete(TcpAttempt* attempt,
                                                   int rv) {
  net_log_.AddEvent(
      NetLogEventType::HTTP_STREAM_POOL_TCP_BASED_ATTEMPT_END, [&] {
        base::DictValue dict;
        dict.Set("ip_endpoint", attempt->ip_endpoint().ToString());
        dict.Set("net_error", rv);
        attempt->stream_attempt().net_log().source().AddToEventParameters(dict);
        return dict;
      });

  std::unique_ptr<TcpAttempt> completed_attempt;
  if (attempt->ip_endpoint().address().IsIPv4()) {
    completed_attempt = std::move(ipv4_attempt_);
  } else {
    completed_attempt = std::move(ipv6_attempt_);
  }
  CHECK_EQ(attempt, completed_attempt.get());

  if (rv != OK) {
    HandleSingleFailure(std::move(completed_attempt), rv);
    // `this` may be deleted at this point.
    return;
  }

  std::unique_ptr<StreamSocket> stream_socket =
      completed_attempt->stream_attempt().ReleaseStreamSocket();
  CHECK(stream_socket);
  LoadTimingInfo::ConnectTiming connect_timing =
      completed_attempt->stream_attempt().connect_timing();
  HostResolver::ServiceEndpointRequest& service_endpoint_request =
      delegate_->GetServiceEndpointRequest();
  stream_socket->SetDnsAliases(service_endpoint_request.GetDnsAliasResults());

  // Reset `completed_attempt` before notifying delegate to prevent dangling
  // pointer.
  completed_attempt.reset();
  base::WeakPtr<Attempt> weak_this = weak_ptr_factory_.GetWeakPtr();
  delegate_->OnStreamSocketReady(this, std::move(stream_socket),
                                 std::move(connect_timing));
  // `this` is deleted.
  CHECK(!weak_this);
}

void HttpStreamPool::Attempt::HandleSingleFailure(
    std::unique_ptr<TcpAttempt> attempt,
    int rv) {
  CHECK_NE(rv, OK);
  CHECK_NE(rv, ERR_IO_PENDING);

  most_recent_attempt_failure_ = {rv, attempt->ip_endpoint()};

  // If the error is fatal, i.e., if the attempt requires a client certificate
  // or a certificate error, we should notify the delegate immediately instead
  // of trying further endpoints.
  if (rv == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
    scoped_refptr<SSLCertRequestInfo> ssl_cert_request_info =
        attempt->stream_attempt().GetCertRequestInfo();
    CHECK(ssl_cert_request_info);
    attempt.reset();

    base::WeakPtr<Attempt> weak_this = weak_ptr_factory_.GetWeakPtr();
    delegate_->OnNeedsClientCertificate(this, std::move(ssl_cert_request_info));
    // `this` is deleted.
    CHECK(!weak_this);
    return;
  }

  if (IsCertificateError(rv)) {
    CHECK(attempt->stream_attempt().stream_socket());
    SSLInfo ssl_info;
    bool has_ssl_info =
        attempt->stream_attempt().stream_socket()->GetSSLInfo(&ssl_info);
    CHECK(has_ssl_info);
    attempt.reset();

    base::WeakPtr<Attempt> weak_this = weak_ptr_factory_.GetWeakPtr();
    delegate_->OnCertificateError(this, rv, ssl_info);
    // `this` is deleted.
    CHECK(!weak_this);
    return;
  }

  attempt.reset();

  MaybeAttempt();
  // Be careful of `this` may be deleted at this point.
}

}  // namespace net
