Project import generated by Copybara.

PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/tools/quic_client_base.cc b/quic/tools/quic_client_base.cc
new file mode 100644
index 0000000..2d8c307
--- /dev/null
+++ b/quic/tools/quic_client_base.cc
@@ -0,0 +1,353 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/tools/quic_client_base.h"
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_server_id.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+namespace quic {
+
+QuicClientBase::NetworkHelper::~NetworkHelper() = default;
+
+QuicClientBase::QuicClientBase(
+    const QuicServerId& server_id,
+    const ParsedQuicVersionVector& supported_versions,
+    const QuicConfig& config,
+    QuicConnectionHelperInterface* helper,
+    QuicAlarmFactory* alarm_factory,
+    std::unique_ptr<NetworkHelper> network_helper,
+    std::unique_ptr<ProofVerifier> proof_verifier)
+    : server_id_(server_id),
+      initialized_(false),
+      local_port_(0),
+      config_(config),
+      crypto_config_(std::move(proof_verifier),
+                     TlsClientHandshaker::CreateSslCtx()),
+      helper_(helper),
+      alarm_factory_(alarm_factory),
+      supported_versions_(supported_versions),
+      initial_max_packet_length_(0),
+      num_stateless_rejects_received_(0),
+      num_sent_client_hellos_(0),
+      connection_error_(QUIC_NO_ERROR),
+      connected_or_attempting_connect_(false),
+      network_helper_(std::move(network_helper)) {}
+
+QuicClientBase::~QuicClientBase() = default;
+
+bool QuicClientBase::Initialize() {
+  num_sent_client_hellos_ = 0;
+  num_stateless_rejects_received_ = 0;
+  connection_error_ = QUIC_NO_ERROR;
+  connected_or_attempting_connect_ = false;
+
+  // If an initial flow control window has not explicitly been set, then use the
+  // same values that Chrome uses.
+  const uint32_t kSessionMaxRecvWindowSize = 15 * 1024 * 1024;  // 15 MB
+  const uint32_t kStreamMaxRecvWindowSize = 6 * 1024 * 1024;    //  6 MB
+  if (config()->GetInitialStreamFlowControlWindowToSend() ==
+      kMinimumFlowControlSendWindow) {
+    config()->SetInitialStreamFlowControlWindowToSend(kStreamMaxRecvWindowSize);
+  }
+  if (config()->GetInitialSessionFlowControlWindowToSend() ==
+      kMinimumFlowControlSendWindow) {
+    config()->SetInitialSessionFlowControlWindowToSend(
+        kSessionMaxRecvWindowSize);
+  }
+
+  if (!network_helper_->CreateUDPSocketAndBind(server_address_,
+                                               bind_to_address_, local_port_)) {
+    return false;
+  }
+
+  initialized_ = true;
+  return true;
+}
+
+bool QuicClientBase::Connect() {
+  // Attempt multiple connects until the maximum number of client hellos have
+  // been sent.
+  while (!connected() &&
+         GetNumSentClientHellos() <= QuicCryptoClientStream::kMaxClientHellos) {
+    StartConnect();
+    while (EncryptionBeingEstablished()) {
+      WaitForEvents();
+    }
+    if (GetQuicReloadableFlag(enable_quic_stateless_reject_support) &&
+        connected()) {
+      // Resend any previously queued data.
+      ResendSavedData();
+    }
+    ParsedQuicVersion version = UnsupportedQuicVersion();
+    if (session() != nullptr &&
+        session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT &&
+        !CanReconnectWithDifferentVersion(&version)) {
+      // We've successfully created a session but we're not connected, and there
+      // is no stateless reject to recover from and cannot try to reconnect with
+      // different version.  Give up trying.
+      break;
+    }
+  }
+  if (!connected() &&
+      GetNumSentClientHellos() > QuicCryptoClientStream::kMaxClientHellos &&
+      session() != nullptr &&
+      session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
+    // The overall connection failed due too many stateless rejects.
+    set_connection_error(QUIC_CRYPTO_TOO_MANY_REJECTS);
+  }
+  return session()->connection()->connected();
+}
+
+void QuicClientBase::StartConnect() {
+  DCHECK(initialized_);
+  DCHECK(!connected());
+  QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
+  ParsedQuicVersion mutual_version = UnsupportedQuicVersion();
+  const bool can_reconnect_with_different_version =
+      CanReconnectWithDifferentVersion(&mutual_version);
+  if (connected_or_attempting_connect()) {
+    // If the last error was not a stateless reject, then the queued up data
+    // does not need to be resent.
+    // Keep queued up data if client can try to connect with a different
+    // version.
+    if (session()->error() != QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT &&
+        !can_reconnect_with_different_version) {
+      ClearDataToResend();
+    }
+    // Before we destroy the last session and create a new one, gather its stats
+    // and update the stats for the overall connection.
+    UpdateStats();
+  }
+
+  session_ = CreateQuicClientSession(
+      supported_versions(),
+      new QuicConnection(GetNextConnectionId(), server_address(), helper(),
+                         alarm_factory(), writer,
+                         /* owns_writer= */ false, Perspective::IS_CLIENT,
+                         can_reconnect_with_different_version
+                             ? ParsedQuicVersionVector{mutual_version}
+                             : supported_versions()));
+  if (initial_max_packet_length_ != 0) {
+    session()->connection()->SetMaxPacketLength(initial_max_packet_length_);
+  }
+  // Reset |writer()| after |session()| so that the old writer outlives the old
+  // session.
+  set_writer(writer);
+  InitializeSession();
+  set_connected_or_attempting_connect(true);
+}
+
+void QuicClientBase::InitializeSession() {
+  session()->Initialize();
+}
+
+void QuicClientBase::Disconnect() {
+  DCHECK(initialized_);
+
+  initialized_ = false;
+  if (connected()) {
+    session()->connection()->CloseConnection(
+        QUIC_PEER_GOING_AWAY, "Client disconnecting",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  }
+
+  ClearDataToResend();
+
+  network_helper_->CleanUpAllUDPSockets();
+}
+
+ProofVerifier* QuicClientBase::proof_verifier() const {
+  return crypto_config_.proof_verifier();
+}
+
+bool QuicClientBase::EncryptionBeingEstablished() {
+  return !session_->IsEncryptionEstablished() &&
+         session_->connection()->connected();
+}
+
+bool QuicClientBase::WaitForEvents() {
+  DCHECK(connected());
+
+  network_helper_->RunEventLoop();
+
+  DCHECK(session() != nullptr);
+  ParsedQuicVersion version = UnsupportedQuicVersion();
+  if (!connected() &&
+      (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT ||
+       CanReconnectWithDifferentVersion(&version))) {
+    if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
+      DCHECK(GetQuicReloadableFlag(enable_quic_stateless_reject_support));
+      QUIC_DLOG(INFO) << "Detected stateless reject while waiting for events.  "
+                      << "Attempting to reconnect.";
+    } else {
+      QUIC_DLOG(INFO) << "Can reconnect with version: " << version
+                      << ", attempting to reconnect.";
+    }
+    Connect();
+  }
+
+  return session()->num_active_requests() != 0;
+}
+
+bool QuicClientBase::MigrateSocket(const QuicIpAddress& new_host) {
+  return MigrateSocketWithSpecifiedPort(new_host, local_port_);
+}
+
+bool QuicClientBase::MigrateSocketWithSpecifiedPort(
+    const QuicIpAddress& new_host,
+    int port) {
+  if (!connected()) {
+    return false;
+  }
+
+  network_helper_->CleanUpAllUDPSockets();
+
+  set_bind_to_address(new_host);
+  if (!network_helper_->CreateUDPSocketAndBind(server_address_,
+                                               bind_to_address_, port)) {
+    return false;
+  }
+
+  session()->connection()->SetSelfAddress(
+      network_helper_->GetLatestClientAddress());
+
+  QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
+  set_writer(writer);
+  session()->connection()->SetQuicPacketWriter(writer, false);
+
+  return true;
+}
+
+bool QuicClientBase::ChangeEphemeralPort() {
+  auto current_host = network_helper_->GetLatestClientAddress().host();
+  return MigrateSocketWithSpecifiedPort(current_host, 0 /*any ephemeral port*/);
+}
+
+QuicSession* QuicClientBase::session() {
+  return session_.get();
+}
+
+QuicClientBase::NetworkHelper* QuicClientBase::network_helper() {
+  return network_helper_.get();
+}
+
+const QuicClientBase::NetworkHelper* QuicClientBase::network_helper() const {
+  return network_helper_.get();
+}
+
+void QuicClientBase::WaitForStreamToClose(QuicStreamId id) {
+  DCHECK(connected());
+
+  while (connected() && !session_->IsClosedStream(id)) {
+    WaitForEvents();
+  }
+}
+
+bool QuicClientBase::WaitForCryptoHandshakeConfirmed() {
+  DCHECK(connected());
+
+  while (connected() && !session_->IsCryptoHandshakeConfirmed()) {
+    WaitForEvents();
+  }
+
+  // If the handshake fails due to a timeout, the connection will be closed.
+  QUIC_LOG_IF(ERROR, !connected()) << "Handshake with server failed.";
+  return connected();
+}
+
+bool QuicClientBase::connected() const {
+  return session_.get() && session_->connection() &&
+         session_->connection()->connected();
+}
+
+bool QuicClientBase::goaway_received() const {
+  return session_ != nullptr && session_->goaway_received();
+}
+
+int QuicClientBase::GetNumSentClientHellos() {
+  // If we are not actively attempting to connect, the session object
+  // corresponds to the previous connection and should not be used.
+  const int current_session_hellos = !connected_or_attempting_connect_
+                                         ? 0
+                                         : GetNumSentClientHellosFromSession();
+  return num_sent_client_hellos_ + current_session_hellos;
+}
+
+void QuicClientBase::UpdateStats() {
+  num_sent_client_hellos_ += GetNumSentClientHellosFromSession();
+  if (session()->error() == QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT) {
+    ++num_stateless_rejects_received_;
+  }
+}
+
+int QuicClientBase::GetNumReceivedServerConfigUpdates() {
+  // If we are not actively attempting to connect, the session object
+  // corresponds to the previous connection and should not be used.
+  // We do not need to take stateless rejects into account, since we
+  // don't expect any scup messages to be sent during a
+  // statelessly-rejected connection.
+  return !connected_or_attempting_connect_
+             ? 0
+             : GetNumReceivedServerConfigUpdatesFromSession();
+}
+
+QuicErrorCode QuicClientBase::connection_error() const {
+  // Return the high-level error if there was one.  Otherwise, return the
+  // connection error from the last session.
+  if (connection_error_ != QUIC_NO_ERROR) {
+    return connection_error_;
+  }
+  if (session_ == nullptr) {
+    return QUIC_NO_ERROR;
+  }
+  return session_->error();
+}
+
+QuicConnectionId QuicClientBase::GetNextConnectionId() {
+  QuicConnectionId server_designated_id = GetNextServerDesignatedConnectionId();
+  return !server_designated_id.IsEmpty() ? server_designated_id
+                                         : GenerateNewConnectionId();
+}
+
+QuicConnectionId QuicClientBase::GetNextServerDesignatedConnectionId() {
+  QuicCryptoClientConfig::CachedState* cached =
+      crypto_config_.LookupOrCreate(server_id_);
+  // If the cached state indicates that we should use a server-designated
+  // connection ID, then return that connection ID.
+  CHECK(cached != nullptr) << "QuicClientCryptoConfig::LookupOrCreate returned "
+                           << "unexpected nullptr.";
+  return cached->has_server_designated_connection_id()
+             ? cached->GetNextServerDesignatedConnectionId()
+             : EmptyQuicConnectionId();
+}
+
+QuicConnectionId QuicClientBase::GenerateNewConnectionId() {
+  return QuicUtils::CreateRandomConnectionId();
+}
+
+bool QuicClientBase::CanReconnectWithDifferentVersion(
+    ParsedQuicVersion* version) const {
+  if (session_ == nullptr || session_->connection() == nullptr ||
+      session_->error() != QUIC_INVALID_VERSION ||
+      session_->connection()->server_supported_versions().empty()) {
+    return false;
+  }
+  for (const auto& client_version : supported_versions_) {
+    if (QuicContainsValue(session_->connection()->server_supported_versions(),
+                          client_version)) {
+      *version = client_version;
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace quic