// 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 <set>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "net/base/completion_once_callback.h"
#include "net/base/connection_endpoint_metadata.h"
#include "net/base/http_user_agent_settings.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_error_details.h"
#include "net/base/net_export.h"
#include "net/base/network_handle.h"
#include "net/dns/public/resolution_details.h"
#include "net/quic/quic_chromium_client_session.h"
#include "net/quic/quic_session_alias_key.h"
#include "net/spdy/multiplexed_session_creation_initiator.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"

#ifndef NET_QUIC_QUIC_SESSION_ATTEMPT_H_
#define NET_QUIC_QUIC_SESSION_ATTEMPT_H_

namespace net {

class QuicSessionPool;

// Handles a single attempt to create a new QUIC session for an endpoint.
// On success, the new session is activated unless another session has been
// activated for the same endpoint. When failed on the default network, it may
// retry on an alternate network if the system supports non-default networks.
class NET_EXPORT_PRIVATE QuicSessionAttempt {
 public:
  // Represents a successful QUIC session creation. Used for QUIC session
  // creations that could complete asynchronously.
  struct CreateSessionResult {
    raw_ptr<QuicChromiumClientSession> session;
    handles::NetworkHandle network = handles::kInvalidNetworkHandle;
  };

  class Delegate {
   public:
    virtual ~Delegate() = default;

    // Returns the QuicSessionPool that the attempt will use.
    virtual QuicSessionPool* GetQuicSessionPool() = 0;

    // Returns the QuicSessionAliasKey that the attempt will use to identify
    // the session.
    virtual const QuicSessionAliasKey& GetKey() = 0;

    // Returns the NetLogWithSource that the attempt should use.
    virtual const NetLogWithSource& GetNetLog() = 0;

    // Called when the attempt is failed on the default network.
    virtual void OnConnectionFailedOnDefaultNetwork() {}

    // Called when the attempt completed creating the session.
    virtual void OnQuicSessionCreationComplete(int rv) {}
  };

  // Create a SessionAttempt for a direct connection.
  // The `crypto_client_config_handle` is retained to keep the corresponding
  // CryptoClientConfig alive until `this` completes. Call sites can pass
  // nullptr to `crypto_client_config_handle` if the corresponding
  // CryptoClientConfig is guaranteed to be alive.
  QuicSessionAttempt(
      Delegate* delegate,
      IPEndPoint ip_endpoint,
      ConnectionEndpointMetadata metadata,
      quic::ParsedQuicVersion quic_version,
      int cert_verify_flags,
      base::TimeTicks dns_resolution_start_time,
      base::TimeTicks dns_resolution_end_time,
      std::optional<ResolutionDetails> resolution_details,
      bool retry_on_alternate_network_before_handshake,
      bool use_dns_aliases,
      std::set<std::string> dns_aliases,
      std::unique_ptr<QuicCryptoClientConfigHandle> crypto_client_config_handle,
      MultiplexedSessionCreationInitiator session_creation_initiator,
      QuicConnectionReuseDetails quic_connection_reuse_details,
      std::optional<ConnectionManagementConfig> connection_management_config =
          std::nullopt,
      bool is_stale = false);
  // Create a SessionAttempt for a connection proxied over the given stream.
  QuicSessionAttempt(
      Delegate* delegate,
      IPEndPoint local_endpoint,
      IPEndPoint proxy_peer_endpoint,
      quic::ParsedQuicVersion quic_version,
      int cert_verify_flags,
      std::unique_ptr<QuicChromiumClientStream::Handle> proxy_stream,
      const HttpUserAgentSettings* http_user_agent_settings,
      MultiplexedSessionCreationInitiator session_creation_initiator,
      QuicConnectionReuseDetails quic_connection_reuse_details,
      std::optional<ConnectionManagementConfig> connection_management_config =
          std::nullopt,
      bool is_stale = false);

  ~QuicSessionAttempt();

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

  int Start(CompletionOnceCallback callback);

  // Cancels an in-flight attempt without invoking its completion callback.
  // Must only be called after Start() returned ERR_IO_PENDING.
  //
  // While an attempt is in flight, a non-null `session_` is the candidate
  // created for this attempt. It has not been activated or exposed to a
  // request. DoConfirmConnection() may replace it with an existing shared
  // session, but only immediately before completing synchronously; a completed
  // attempt must never be cancelled.
  //
  // If the candidate session already exists, closes it immediately. If
  // asynchronous session creation is still in flight, the completion path
  // closes any session it creates instead of handing it to this attempt.
  void Cancel();

  bool session_creation_finished() const { return session_creation_finished_; }

  const quic::ParsedQuicVersion& quic_version() const { return quic_version_; }

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

  const ConnectionEndpointMetadata& metadata() const { return metadata_; }

  QuicChromiumClientSession* session() const { return session_.get(); }

  void PopulateNetErrorDetails(NetErrorDetails* details) const;

 private:
  enum class State {
    kNone,
    kCreateSession,
    kCreateSessionComplete,
    kCryptoConnect,
    kConfirmConnection,
  };

  // Static so this callback still runs after the attempt is destroyed. It
  // forwards the result while the attempt is alive and otherwise silently
  // closes any session that was created.
  static void HandleCreateSessionResult(
      base::WeakPtr<QuicSessionAttempt> attempt,
      base::expected<CreateSessionResult, int> result);

  QuicSessionPool* pool() { return delegate_->GetQuicSessionPool(); }
  const QuicSessionAliasKey& key() { return delegate_->GetKey(); }
  const NetLogWithSource& net_log() { return delegate_->GetNetLog(); }

  int DoLoop(int rv);

  int DoCreateSession();
  int DoCreateSessionComplete(int rv);
  int DoCryptoConnect(int rv);
  int DoConfirmConnection(int rv);

  void OnCreateSessionComplete(base::expected<CreateSessionResult, int> result);
  void OnCryptoConnectComplete(int rv);

  void MaybeInvokeCallback(int rv);

  void ResetSession();

  const raw_ptr<Delegate> delegate_;

  const base::TimeTicks start_time_;
  const IPEndPoint ip_endpoint_;
  const ConnectionEndpointMetadata metadata_;
  const quic::ParsedQuicVersion quic_version_;
  const int cert_verify_flags_;
  const base::TimeTicks dns_resolution_start_time_;
  const base::TimeTicks dns_resolution_end_time_;
  const std::optional<ResolutionDetails> resolution_details_;
  const bool is_stale_;
  const bool was_alternative_service_recently_broken_;
  const bool retry_on_alternate_network_before_handshake_;
  const bool use_dns_aliases_;
  std::set<std::string> dns_aliases_;
  std::unique_ptr<QuicCryptoClientConfigHandle> crypto_client_config_handle_;

  // Fields only used for session attempts to a proxy.
  std::unique_ptr<QuicChromiumClientStream::Handle> proxy_stream_;
  const raw_ptr<const HttpUserAgentSettings> http_user_agent_settings_;
  const IPEndPoint local_endpoint_;

  const MultiplexedSessionCreationInitiator session_creation_initiator_;
  const QuicConnectionReuseDetails quic_connection_reuse_details_;
  std::optional<ConnectionManagementConfig> connection_management_config_;

  State next_state_ = State::kNone;
  bool in_loop_ = false;

  raw_ptr<QuicChromiumClientSession> session_ = nullptr;
  bool session_creation_finished_ = false;
  bool connection_retried_ = false;

  // Used to populate NetErrorDetails after we reset `session_`.
  HttpConnectionInfo connection_info_ = HttpConnectionInfo::kUNKNOWN;
  quic::QuicErrorCode quic_connection_error_ = quic::QUIC_NO_ERROR;

  base::TimeTicks quic_connection_start_time_;

  // If connection migraiton is supported, |network_| denotes the network on
  // which |session_| is created.
  handles::NetworkHandle network_ = handles::kInvalidNetworkHandle;

  CompletionOnceCallback callback_;

  base::WeakPtrFactory<QuicSessionAttempt> weak_ptr_factory_{this};
};

}  // namespace net

#endif  // NET_QUIC_QUIC_SESSION_ATTEMPT_H_
