// Copyright 2024 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_quic_attempt.h"

#include <memory>
#include <vector>

#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_id_helper.h"
#include "base/values.h"
#include "net/base/connection_endpoint_metadata.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_error_details.h"
#include "net/base/net_errors.h"
#include "net/dns/host_resolver.h"
#include "net/dns/public/host_resolver_results.h"
#include "net/http/http_network_session.h"
#include "net/http/http_stream_pool.h"
#include "net/http/http_stream_pool_attempt_manager.h"
#include "net/http/http_stream_pool_group.h"
#include "net/log/net_log_source_type.h"
#include "net/log/net_log_with_source.h"
#include "net/quic/quic_session_alias_key.h"
#include "net/quic/quic_session_attempt_manager.h"
#include "net/quic/quic_session_attempt_request.h"
#include "net/quic/quic_session_key.h"
#include "net/quic/quic_session_pool.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"

namespace net {

HttpStreamPool::QuicAttempt::QuicAttempt(AttemptManager* manager,
                                         QuicEndpoint quic_endpoint)
    : manager_(manager),
      quic_endpoint_(std::move(quic_endpoint)),
      start_time_(base::TimeTicks::Now()),
      net_log_(NetLogWithSource::Make(
          manager->net_log().net_log(),
          NetLogSourceType::HTTP_STREAM_POOL_QUIC_ATTEMPT)),
      track_(base::trace_event::GetNextGlobalTraceId()),
      flow_(perfetto::Flow::ProcessScoped(
          base::trace_event::GetNextGlobalTraceId())) {
  CHECK(manager_);
  TRACE_EVENT_INSTANT("net.stream", "QuicAttemptStart", manager_->track(),
                      flow_);
  TRACE_EVENT_BEGIN("net.stream", "QuicAttempt::QuicAttempt", track_, flow_,
                    "ip_endpoint", quic_endpoint_.ip_endpoint.ToString());

  net_log_.BeginEvent(
      NetLogEventType::HTTP_STREAM_POOL_QUIC_ATTEMPT_ALIVE, [&] {
        base::DictValue dict;
        dict.Set("quic_version",
                 quic::ParsedQuicVersionToString(quic_endpoint_.quic_version));
        dict.Set("ip_endpoint", quic_endpoint_.ip_endpoint.ToString());
        dict.Set("metadata", quic_endpoint_.metadata.ToValue());
        manager_->net_log().source().AddToEventParameters(dict);
        return dict;
      });
  manager_->net_log().AddEventReferencingSource(
      NetLogEventType::HTTP_STREAM_POOL_ATTEMPT_MANAGER_QUIC_ATTEMPT_BOUND,
      net_log_.source());

  request_ = quic_session_pool()->session_attempt_manager()->CreateRequest(
      quic_session_alias_key());
}

HttpStreamPool::QuicAttempt::~QuicAttempt() {
  net_log_.EndEventWithNetErrorCode(
      NetLogEventType::HTTP_STREAM_POOL_QUIC_ATTEMPT_ALIVE,
      result_.value_or(ERR_ABORTED));
  TRACE_EVENT_END("net.stream", track_, "result",
                  result_.value_or(ERR_ABORTED));
  TRACE_EVENT_INSTANT("net.stream", "QuicAttemptEnd", manager_->track(), flow_);
}

void HttpStreamPool::QuicAttempt::Start() {
  if (GetTcpBasedAttemptDelayBehavior() ==
      TcpBasedAttemptDelayBehavior::kStartTimerOnFirstQuicAttempt) {
    manager_->MaybeRunTcpBasedAttemptDelayTimer();
  }

  SSLConfig ssl_config;
  ssl_config.disable_cert_verification_network_fetches =
      stream_key().disable_cert_network_fetches();
  int cert_verify_flags = ssl_config.GetCertVerifyFlags();

  base::TimeTicks dns_resolution_start_time =
      manager_->dns_resolution_start_time();
  // The DNS resolution end time could be null when the resolution is still
  // ongoing. In that case, use the current time to make sure the connect
  // start time is already greater than the DNS resolution end time.
  base::TimeTicks dns_resolution_end_time =
      manager_->dns_resolution_end_time().is_null()
          ? base::TimeTicks::Now()
          : manager_->dns_resolution_end_time();

  std::set<std::string> dns_aliases =
      manager_->service_endpoint_request()->GetDnsAliasResults();
  int rv = request_->RequestSession(
      quic_endpoint_, cert_verify_flags, dns_resolution_start_time,
      dns_resolution_end_time,
      manager_->service_endpoint_request()->GetResolutionDetails(),
      /*use_dns_aliases=*/true, std::move(dns_aliases),
      manager_->CalculateMultiplexedSessionCreationInitiator(),
      /*connection_management_config=*/std::nullopt, net_log_,
      base::BindOnce(&QuicAttempt::OnSessionAttemptComplete,
                     weak_ptr_factory_.GetWeakPtr()));

  if (rv == ERR_IO_PENDING) {
    slow_timer_.Start(FROM_HERE, HttpStreamPool::GetConnectionAttemptDelay(),
                      base::BindOnce(&QuicAttempt::OnSessionAttemptSlow,
                                     base::Unretained(this)));
  } else {
    OnSessionAttemptComplete(rv);
  }
}

base::DictValue HttpStreamPool::QuicAttempt::GetInfoAsValue() const {
  base::DictValue dict;
  dict.Set("quic_version",
           quic::ParsedQuicVersionToString(quic_endpoint_.quic_version));
  dict.Set("ip_endpoint", quic_endpoint_.ip_endpoint.ToString());
  base::TimeDelta elapsed = base::TimeTicks::Now() - start_time_;
  dict.Set("elapsed_ms", static_cast<int>(elapsed.InMilliseconds()));
  if (result_.has_value()) {
    dict.Set("result", *result_);
  }
  return dict;
}

const HttpStreamKey& HttpStreamPool::QuicAttempt::stream_key() const {
  return manager_->group()->stream_key();
}

const QuicSessionAliasKey& HttpStreamPool::QuicAttempt::quic_session_alias_key()
    const {
  return manager_->group()->quic_session_alias_key();
}

QuicSessionPool* HttpStreamPool::QuicAttempt::quic_session_pool() {
  return manager_->group()->http_network_session()->quic_session_pool();
}

void HttpStreamPool::QuicAttempt::OnSessionAttemptSlow() {
  CHECK(!is_slow_);
  is_slow_ = true;
  manager_->OnQuicAttemptSlow();
}

void HttpStreamPool::QuicAttempt::OnSessionAttemptComplete(int rv) {
  slow_timer_.Stop();
  if (rv == OK) {
    if (!manager_->CanUseExistingQuicSession()) {
      // QUIC session is closed or marked broken before stream can be created.
      rv = ERR_CONNECTION_CLOSED;
    }
  }

  if (rv == OK &&
      !quic_session_pool()->has_quic_ever_worked_on_current_network()) {
    quic_session_pool()->set_has_quic_ever_worked_on_current_network(true);
  }

  result_ = rv;
  QuicAttemptOutcome outcome(rv);
  if (request_) {
    outcome.session = request_->session();
    outcome.error_details = request_->error_details();
  }
  request_.reset();
  manager_->OnQuicAttemptComplete(std::move(outcome));
  // `this` is deleted.
}

}  // namespace net
