Relocate QUICHE files into quiche/ directory within the quiche repo, and change the relative include paths accordingly.

PiperOrigin-RevId: 440164720
Change-Id: I64d8a975d08888a3a86f6c51908e63d5cd45fa35
diff --git a/quiche/quic/tools/fake_proof_verifier.h b/quiche/quic/tools/fake_proof_verifier.h
new file mode 100644
index 0000000..62ab87a
--- /dev/null
+++ b/quiche/quic/tools/fake_proof_verifier.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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.
+
+#ifndef QUICHE_QUIC_TOOLS_FAKE_PROOF_VERIFIER_H_
+#define QUICHE_QUIC_TOOLS_FAKE_PROOF_VERIFIER_H_
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/proof_verifier.h"
+
+namespace quic {
+
+// ProofVerifier implementation which always returns success.
+class FakeProofVerifier : public ProofVerifier {
+ public:
+  ~FakeProofVerifier() override {}
+  QuicAsyncStatus VerifyProof(
+      const std::string& /*hostname*/,
+      const uint16_t /*port*/,
+      const std::string& /*server_config*/,
+      QuicTransportVersion /*quic_version*/,
+      absl::string_view /*chlo_hash*/,
+      const std::vector<std::string>& /*certs*/,
+      const std::string& /*cert_sct*/,
+      const std::string& /*signature*/,
+      const ProofVerifyContext* /*context*/,
+      std::string* /*error_details*/,
+      std::unique_ptr<ProofVerifyDetails>* /*details*/,
+      std::unique_ptr<ProofVerifierCallback> /*callback*/) override {
+    return QUIC_SUCCESS;
+  }
+  QuicAsyncStatus VerifyCertChain(
+      const std::string& /*hostname*/,
+      const uint16_t /*port*/,
+      const std::vector<std::string>& /*certs*/,
+      const std::string& /*ocsp_response*/,
+      const std::string& /*cert_sct*/,
+      const ProofVerifyContext* /*context*/,
+      std::string* /*error_details*/,
+      std::unique_ptr<ProofVerifyDetails>* /*details*/,
+      uint8_t* /*out_alert*/,
+      std::unique_ptr<ProofVerifierCallback> /*callback*/) override {
+    return QUIC_SUCCESS;
+  }
+  std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override {
+    return nullptr;
+  }
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_FAKE_PROOF_VERIFIER_H_
diff --git a/quiche/quic/tools/quic_backend_response.cc b/quiche/quic/tools/quic_backend_response.cc
new file mode 100644
index 0000000..d079d63
--- /dev/null
+++ b/quiche/quic/tools/quic_backend_response.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_backend_response.h"
+
+namespace quic {
+
+QuicBackendResponse::ServerPushInfo::ServerPushInfo(
+    QuicUrl request_url,
+    spdy::Http2HeaderBlock headers,
+    spdy::SpdyPriority priority,
+    std::string body)
+    : request_url(request_url),
+      headers(std::move(headers)),
+      priority(priority),
+      body(body) {}
+
+QuicBackendResponse::ServerPushInfo::ServerPushInfo(const ServerPushInfo& other)
+    : request_url(other.request_url),
+      headers(other.headers.Clone()),
+      priority(other.priority),
+      body(other.body) {}
+
+QuicBackendResponse::QuicBackendResponse() : response_type_(REGULAR_RESPONSE) {}
+
+QuicBackendResponse::~QuicBackendResponse() = default;
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_backend_response.h b/quiche/quic/tools/quic_backend_response.h
new file mode 100644
index 0000000..e67e186
--- /dev/null
+++ b/quiche/quic/tools/quic_backend_response.h
@@ -0,0 +1,92 @@
+// Copyright 2017 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_BACKEND_RESPONSE_H_
+#define QUICHE_QUIC_TOOLS_QUIC_BACKEND_RESPONSE_H_
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/spdy/core/spdy_protocol.h"
+
+namespace quic {
+
+// Container for HTTP response header/body pairs
+// fetched by the QuicSimpleServerBackend
+class QuicBackendResponse {
+ public:
+  // A ServerPushInfo contains path of the push request and everything needed in
+  // comprising a response for the push request.
+  // TODO(b/171463363): Remove.
+  struct ServerPushInfo {
+    ServerPushInfo(QuicUrl request_url,
+                   spdy::Http2HeaderBlock headers,
+                   spdy::SpdyPriority priority,
+                   std::string body);
+    ServerPushInfo(const ServerPushInfo& other);
+
+    QuicUrl request_url;
+    spdy::Http2HeaderBlock headers;
+    spdy::SpdyPriority priority;
+    std::string body;
+  };
+
+  enum SpecialResponseType {
+    REGULAR_RESPONSE,      // Send the headers and body like a server should.
+    CLOSE_CONNECTION,      // Close the connection (sending the close packet).
+    IGNORE_REQUEST,        // Do nothing, expect the client to time out.
+    BACKEND_ERR_RESPONSE,  // There was an error fetching the response from
+                           // the backend, for example as a TCP connection
+                           // error.
+    INCOMPLETE_RESPONSE,   // The server will act as if there is a non-empty
+                           // trailer but it will not be sent, as a result, FIN
+                           // will not be sent too.
+    GENERATE_BYTES         // Sends a response with a length equal to the number
+                           // of bytes in the URL path.
+  };
+  QuicBackendResponse();
+
+  QuicBackendResponse(const QuicBackendResponse& other) = delete;
+  QuicBackendResponse& operator=(const QuicBackendResponse& other) = delete;
+
+  ~QuicBackendResponse();
+
+  const std::vector<spdy::Http2HeaderBlock>& early_hints() const {
+    return early_hints_;
+  }
+  SpecialResponseType response_type() const { return response_type_; }
+  const spdy::Http2HeaderBlock& headers() const { return headers_; }
+  const spdy::Http2HeaderBlock& trailers() const { return trailers_; }
+  const absl::string_view body() const { return absl::string_view(body_); }
+
+  void AddEarlyHints(const spdy::Http2HeaderBlock& headers) {
+    spdy::Http2HeaderBlock hints = headers.Clone();
+    hints[":status"] = "103";
+    early_hints_.push_back(std::move(hints));
+  }
+
+  void set_response_type(SpecialResponseType response_type) {
+    response_type_ = response_type;
+  }
+
+  void set_headers(spdy::Http2HeaderBlock headers) {
+    headers_ = std::move(headers);
+  }
+  void set_trailers(spdy::Http2HeaderBlock trailers) {
+    trailers_ = std::move(trailers);
+  }
+  void set_body(absl::string_view body) {
+    body_.assign(body.data(), body.size());
+  }
+
+ private:
+  std::vector<spdy::Http2HeaderBlock> early_hints_;
+  SpecialResponseType response_type_;
+  spdy::Http2HeaderBlock headers_;
+  spdy::Http2HeaderBlock trailers_;
+  std::string body_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_BACKEND_RESPONSE_H_
diff --git a/quiche/quic/tools/quic_client.cc b/quiche/quic/tools/quic_client.cc
new file mode 100644
index 0000000..a493e9c
--- /dev/null
+++ b/quiche/quic/tools/quic_client.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_client.h"
+
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <utility>
+
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_data_reader.h"
+#include "quiche/quic/core/quic_epoll_alarm_factory.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_simple_client_session.h"
+
+namespace quic {
+
+namespace tools {
+
+QuicSocketAddress LookupAddress(int address_family_for_lookup,
+                                std::string host,
+                                std::string port) {
+  addrinfo hint;
+  memset(&hint, 0, sizeof(hint));
+  hint.ai_family = address_family_for_lookup;
+  hint.ai_protocol = IPPROTO_UDP;
+
+  addrinfo* info_list = nullptr;
+  int result = getaddrinfo(host.c_str(), port.c_str(), &hint, &info_list);
+  if (result != 0) {
+    QUIC_LOG(ERROR) << "Failed to look up " << host << ": "
+                    << gai_strerror(result);
+    return QuicSocketAddress();
+  }
+
+  QUICHE_CHECK(info_list != nullptr);
+  std::unique_ptr<addrinfo, void (*)(addrinfo*)> info_list_owned(info_list,
+                                                                 freeaddrinfo);
+  return QuicSocketAddress(info_list->ai_addr, info_list->ai_addrlen);
+}
+
+}  // namespace tools
+
+QuicClient::QuicClient(QuicSocketAddress server_address,
+                       const QuicServerId& server_id,
+                       const ParsedQuicVersionVector& supported_versions,
+                       QuicEpollServer* epoll_server,
+                       std::unique_ptr<ProofVerifier> proof_verifier)
+    : QuicClient(
+          server_address,
+          server_id,
+          supported_versions,
+          QuicConfig(),
+          epoll_server,
+          std::make_unique<QuicClientEpollNetworkHelper>(epoll_server, this),
+          std::move(proof_verifier),
+          nullptr) {}
+
+QuicClient::QuicClient(QuicSocketAddress server_address,
+                       const QuicServerId& server_id,
+                       const ParsedQuicVersionVector& supported_versions,
+                       QuicEpollServer* epoll_server,
+                       std::unique_ptr<ProofVerifier> proof_verifier,
+                       std::unique_ptr<SessionCache> session_cache)
+    : QuicClient(
+          server_address,
+          server_id,
+          supported_versions,
+          QuicConfig(),
+          epoll_server,
+          std::make_unique<QuicClientEpollNetworkHelper>(epoll_server, this),
+          std::move(proof_verifier),
+          std::move(session_cache)) {}
+
+QuicClient::QuicClient(QuicSocketAddress server_address,
+                       const QuicServerId& server_id,
+                       const ParsedQuicVersionVector& supported_versions,
+                       const QuicConfig& config,
+                       QuicEpollServer* epoll_server,
+                       std::unique_ptr<ProofVerifier> proof_verifier,
+                       std::unique_ptr<SessionCache> session_cache)
+    : QuicClient(
+          server_address,
+          server_id,
+          supported_versions,
+          config,
+          epoll_server,
+          std::make_unique<QuicClientEpollNetworkHelper>(epoll_server, this),
+          std::move(proof_verifier),
+          std::move(session_cache)) {}
+
+QuicClient::QuicClient(
+    QuicSocketAddress server_address,
+    const QuicServerId& server_id,
+    const ParsedQuicVersionVector& supported_versions,
+    QuicEpollServer* epoll_server,
+    std::unique_ptr<QuicClientEpollNetworkHelper> network_helper,
+    std::unique_ptr<ProofVerifier> proof_verifier)
+    : QuicClient(server_address,
+                 server_id,
+                 supported_versions,
+                 QuicConfig(),
+                 epoll_server,
+                 std::move(network_helper),
+                 std::move(proof_verifier),
+                 nullptr) {}
+
+QuicClient::QuicClient(
+    QuicSocketAddress server_address,
+    const QuicServerId& server_id,
+    const ParsedQuicVersionVector& supported_versions,
+    const QuicConfig& config,
+    QuicEpollServer* epoll_server,
+    std::unique_ptr<QuicClientEpollNetworkHelper> network_helper,
+    std::unique_ptr<ProofVerifier> proof_verifier)
+    : QuicClient(server_address,
+                 server_id,
+                 supported_versions,
+                 config,
+                 epoll_server,
+                 std::move(network_helper),
+                 std::move(proof_verifier),
+                 nullptr) {}
+
+QuicClient::QuicClient(
+    QuicSocketAddress server_address,
+    const QuicServerId& server_id,
+    const ParsedQuicVersionVector& supported_versions,
+    const QuicConfig& config,
+    QuicEpollServer* epoll_server,
+    std::unique_ptr<QuicClientEpollNetworkHelper> network_helper,
+    std::unique_ptr<ProofVerifier> proof_verifier,
+    std::unique_ptr<SessionCache> session_cache)
+    : QuicSpdyClientBase(
+          server_id,
+          supported_versions,
+          config,
+          new QuicEpollConnectionHelper(epoll_server, QuicAllocator::SIMPLE),
+          new QuicEpollAlarmFactory(epoll_server),
+          std::move(network_helper),
+          std::move(proof_verifier),
+          std::move(session_cache)) {
+  set_server_address(server_address);
+}
+
+QuicClient::~QuicClient() = default;
+
+std::unique_ptr<QuicSession> QuicClient::CreateQuicClientSession(
+    const ParsedQuicVersionVector& supported_versions,
+    QuicConnection* connection) {
+  return std::make_unique<QuicSimpleClientSession>(
+      *config(), supported_versions, connection, server_id(), crypto_config(),
+      push_promise_index(), drop_response_body(), enable_web_transport(),
+      use_datagram_contexts());
+}
+
+QuicClientEpollNetworkHelper* QuicClient::epoll_network_helper() {
+  return static_cast<QuicClientEpollNetworkHelper*>(network_helper());
+}
+
+const QuicClientEpollNetworkHelper* QuicClient::epoll_network_helper() const {
+  return static_cast<const QuicClientEpollNetworkHelper*>(network_helper());
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_client.h b/quiche/quic/tools/quic_client.h
new file mode 100644
index 0000000..446e9c6
--- /dev/null
+++ b/quiche/quic/tools/quic_client.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 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.
+
+// A toy client, which connects to a specified port and sends QUIC
+// request to that endpoint.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_CLIENT_H_
+#define QUICHE_QUIC_TOOLS_QUIC_CLIENT_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "quiche/quic/core/http/quic_client_push_promise_index.h"
+#include "quiche/quic/core/http/quic_spdy_client_session.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/core/quic_packet_reader.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/tools/quic_client_epoll_network_helper.h"
+#include "quiche/quic/tools/quic_spdy_client_base.h"
+
+namespace quic {
+
+class QuicServerId;
+
+namespace test {
+class QuicClientPeer;
+}  // namespace test
+
+namespace tools {
+
+QuicSocketAddress LookupAddress(int address_family_for_lookup,
+                                std::string host,
+                                std::string port);
+
+inline QuicSocketAddress LookupAddress(std::string host, std::string port) {
+  return LookupAddress(0, host, port);
+}
+
+}  // namespace tools
+
+class QuicClient : public QuicSpdyClientBase {
+ public:
+  // These will create their own QuicClientEpollNetworkHelper.
+  QuicClient(QuicSocketAddress server_address,
+             const QuicServerId& server_id,
+             const ParsedQuicVersionVector& supported_versions,
+             QuicEpollServer* epoll_server,
+             std::unique_ptr<ProofVerifier> proof_verifier);
+  QuicClient(QuicSocketAddress server_address,
+             const QuicServerId& server_id,
+             const ParsedQuicVersionVector& supported_versions,
+             QuicEpollServer* epoll_server,
+             std::unique_ptr<ProofVerifier> proof_verifier,
+             std::unique_ptr<SessionCache> session_cache);
+  QuicClient(QuicSocketAddress server_address,
+             const QuicServerId& server_id,
+             const ParsedQuicVersionVector& supported_versions,
+             const QuicConfig& config,
+             QuicEpollServer* epoll_server,
+             std::unique_ptr<ProofVerifier> proof_verifier,
+             std::unique_ptr<SessionCache> session_cache);
+  // This will take ownership of a passed in network primitive.
+  QuicClient(QuicSocketAddress server_address,
+             const QuicServerId& server_id,
+             const ParsedQuicVersionVector& supported_versions,
+             QuicEpollServer* epoll_server,
+             std::unique_ptr<QuicClientEpollNetworkHelper> network_helper,
+             std::unique_ptr<ProofVerifier> proof_verifier);
+  QuicClient(QuicSocketAddress server_address,
+             const QuicServerId& server_id,
+             const ParsedQuicVersionVector& supported_versions,
+             const QuicConfig& config,
+             QuicEpollServer* epoll_server,
+             std::unique_ptr<QuicClientEpollNetworkHelper> network_helper,
+             std::unique_ptr<ProofVerifier> proof_verifier);
+  QuicClient(QuicSocketAddress server_address,
+             const QuicServerId& server_id,
+             const ParsedQuicVersionVector& supported_versions,
+             const QuicConfig& config,
+             QuicEpollServer* epoll_server,
+             std::unique_ptr<QuicClientEpollNetworkHelper> network_helper,
+             std::unique_ptr<ProofVerifier> proof_verifier,
+             std::unique_ptr<SessionCache> session_cache);
+  QuicClient(const QuicClient&) = delete;
+  QuicClient& operator=(const QuicClient&) = delete;
+
+  ~QuicClient() override;
+
+  std::unique_ptr<QuicSession> CreateQuicClientSession(
+      const ParsedQuicVersionVector& supported_versions,
+      QuicConnection* connection) override;
+
+  // Exposed for the quic client test.
+  int GetLatestFD() const { return epoll_network_helper()->GetLatestFD(); }
+
+  QuicClientEpollNetworkHelper* epoll_network_helper();
+  const QuicClientEpollNetworkHelper* epoll_network_helper() const;
+
+ private:
+  friend class test::QuicClientPeer;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_CLIENT_H_
diff --git a/quiche/quic/tools/quic_client_base.cc b/quiche/quic/tools/quic_client_base.cc
new file mode 100644
index 0000000..8cabb60
--- /dev/null
+++ b/quiche/quic/tools/quic_client_base.cc
@@ -0,0 +1,514 @@
+// 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 "quiche/quic/tools/quic_client_base.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_packet_writer.h"
+#include "quiche/quic/core/quic_path_validator.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+
+namespace quic {
+
+// A path context which owns the writer.
+class QUIC_EXPORT_PRIVATE PathMigrationContext
+    : public QuicPathValidationContext {
+ public:
+  PathMigrationContext(std::unique_ptr<QuicPacketWriter> writer,
+                       const QuicSocketAddress& self_address,
+                       const QuicSocketAddress& peer_address)
+      : QuicPathValidationContext(self_address, peer_address),
+        alternative_writer_(std::move(writer)) {}
+
+  QuicPacketWriter* WriterToUse() override { return alternative_writer_.get(); }
+
+  QuicPacketWriter* ReleaseWriter() { return alternative_writer_.release(); }
+
+ private:
+  std::unique_ptr<QuicPacketWriter> alternative_writer_;
+};
+
+// Implements the basic feature of a result delegate for path validation for
+// connection migration. If the validation succeeds, migrate to the alternative
+// path. Otherwise, stay on the current path.
+class QuicClientSocketMigrationValidationResultDelegate
+    : public QuicPathValidator::ResultDelegate {
+ public:
+  QuicClientSocketMigrationValidationResultDelegate(QuicClientBase* client)
+      : QuicPathValidator::ResultDelegate(), client_(client) {}
+
+  // QuicPathValidator::ResultDelegate
+  // Overridden to start migration and takes the ownership of the writer in the
+  // context.
+  void OnPathValidationSuccess(
+      std::unique_ptr<QuicPathValidationContext> context) override {
+    QUIC_DLOG(INFO) << "Successfully validated path from " << *context
+                    << ". Migrate to it now.";
+    auto migration_context = std::unique_ptr<PathMigrationContext>(
+        static_cast<PathMigrationContext*>(context.release()));
+    client_->session()->MigratePath(
+        migration_context->self_address(), migration_context->peer_address(),
+        migration_context->WriterToUse(), /*owns_writer=*/false);
+    QUICHE_DCHECK(migration_context->WriterToUse() != nullptr);
+    // Hand the ownership of the alternative writer to the client.
+    client_->set_writer(migration_context->ReleaseWriter());
+  }
+
+  void OnPathValidationFailure(
+      std::unique_ptr<QuicPathValidationContext> context) override {
+    QUIC_LOG(WARNING) << "Fail to validate path " << *context
+                      << ", stop migrating.";
+    client_->session()->connection()->OnPathValidationFailureAtClient();
+  }
+
+ private:
+  QuicClientBase* client_;
+};
+
+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,
+    std::unique_ptr<SessionCache> session_cache)
+    : server_id_(server_id),
+      initialized_(false),
+      local_port_(0),
+      config_(config),
+      crypto_config_(std::move(proof_verifier), std::move(session_cache)),
+      helper_(helper),
+      alarm_factory_(alarm_factory),
+      supported_versions_(supported_versions),
+      initial_max_packet_length_(0),
+      num_sent_client_hellos_(0),
+      connection_error_(QUIC_NO_ERROR),
+      connected_or_attempting_connect_(false),
+      network_helper_(std::move(network_helper)),
+      connection_debug_visitor_(nullptr),
+      server_connection_id_length_(kQuicDefaultConnectionIdLength),
+      client_connection_id_length_(0) {}
+
+QuicClientBase::~QuicClientBase() = default;
+
+bool QuicClientBase::Initialize() {
+  num_sent_client_hellos_ = 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() ==
+      kDefaultFlowControlSendWindow) {
+    config()->SetInitialStreamFlowControlWindowToSend(kStreamMaxRecvWindowSize);
+  }
+  if (config()->GetInitialSessionFlowControlWindowToSend() ==
+      kDefaultFlowControlSendWindow) {
+    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.
+  int num_attempts = 0;
+  while (!connected() &&
+         num_attempts <= QuicCryptoClientStream::kMaxClientHellos) {
+    StartConnect();
+    while (EncryptionBeingEstablished()) {
+      WaitForEvents();
+    }
+    ParsedQuicVersion version = UnsupportedQuicVersion();
+    if (session() != nullptr && !CanReconnectWithDifferentVersion(&version)) {
+      // We've successfully created a session but we're not connected, and we
+      // cannot reconnect with a different version.  Give up trying.
+      break;
+    }
+    num_attempts++;
+  }
+  if (session() == nullptr) {
+    QUIC_BUG(quic_bug_10906_1) << "Missing session after Connect";
+    return false;
+  }
+  return session()->connection()->connected();
+}
+
+void QuicClientBase::StartConnect() {
+  QUICHE_DCHECK(initialized_);
+  QUICHE_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()) {
+    // Clear queued up data if client can not try to connect with a different
+    // version.
+    if (!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();
+  }
+
+  const quic::ParsedQuicVersionVector client_supported_versions =
+      can_reconnect_with_different_version
+          ? ParsedQuicVersionVector{mutual_version}
+          : supported_versions();
+
+  session_ = CreateQuicClientSession(
+      client_supported_versions,
+      new QuicConnection(GetNextConnectionId(), QuicSocketAddress(),
+                         server_address(), helper(), alarm_factory(), writer,
+                         /* owns_writer= */ false, Perspective::IS_CLIENT,
+                         client_supported_versions));
+  if (can_reconnect_with_different_version) {
+    session()->set_client_original_supported_versions(supported_versions());
+  }
+  if (connection_debug_visitor_ != nullptr) {
+    session()->connection()->set_debug_visitor(connection_debug_visitor_);
+  }
+  session()->connection()->set_client_connection_id(GetClientConnectionId());
+  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();
+  if (can_reconnect_with_different_version) {
+    // This is a reconnect using server supported |mutual_version|.
+    session()->connection()->SetVersionNegotiated();
+  }
+  set_connected_or_attempting_connect(true);
+}
+
+void QuicClientBase::InitializeSession() {
+  session()->Initialize();
+}
+
+void QuicClientBase::Disconnect() {
+  QUICHE_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() {
+  if (!connected()) {
+    QUIC_BUG(quic_bug_10906_2)
+        << "Cannot call WaitForEvents on non-connected client";
+    return false;
+  }
+
+  network_helper_->RunEventLoop();
+
+  QUICHE_DCHECK(session() != nullptr);
+  ParsedQuicVersion version = UnsupportedQuicVersion();
+  if (!connected() && CanReconnectWithDifferentVersion(&version)) {
+    QUIC_DLOG(INFO) << "Can reconnect with version: " << version
+                    << ", attempting to reconnect.";
+
+    Connect();
+  }
+
+  return HasActiveRequests();
+}
+
+bool QuicClientBase::MigrateSocket(const QuicIpAddress& new_host) {
+  return MigrateSocketWithSpecifiedPort(new_host, local_port_);
+}
+
+bool QuicClientBase::MigrateSocketWithSpecifiedPort(
+    const QuicIpAddress& new_host,
+    int port) {
+  if (!connected()) {
+    QUICHE_DVLOG(1)
+        << "MigrateSocketWithSpecifiedPort failed as connection has closed";
+    return false;
+  }
+
+  network_helper_->CleanUpAllUDPSockets();
+  std::unique_ptr<QuicPacketWriter> writer =
+      CreateWriterForNewNetwork(new_host, port);
+  if (writer == nullptr) {
+    QUICHE_DVLOG(1)
+        << "MigrateSocketWithSpecifiedPort failed from writer creation";
+    return false;
+  }
+  if (!session()->MigratePath(network_helper_->GetLatestClientAddress(),
+                              session()->connection()->peer_address(),
+                              writer.get(), false)) {
+    QUICHE_DVLOG(1)
+        << "MigrateSocketWithSpecifiedPort failed from session()->MigratePath";
+    return false;
+  }
+  set_writer(writer.release());
+  return true;
+}
+
+bool QuicClientBase::ValidateAndMigrateSocket(const QuicIpAddress& new_host) {
+  QUICHE_DCHECK(VersionHasIetfQuicFrames(
+                    session_->connection()->version().transport_version) &&
+                session_->connection()->use_path_validator());
+  if (!connected()) {
+    return false;
+  }
+
+  std::unique_ptr<QuicPacketWriter> writer =
+      CreateWriterForNewNetwork(new_host, local_port_);
+  if (writer == nullptr) {
+    return false;
+  }
+  // Asynchronously start migration.
+  session_->ValidatePath(
+      std::make_unique<PathMigrationContext>(
+          std::move(writer), network_helper_->GetLatestClientAddress(),
+          session_->peer_address()),
+      std::make_unique<QuicClientSocketMigrationValidationResultDelegate>(
+          this));
+  return true;
+}
+
+std::unique_ptr<QuicPacketWriter> QuicClientBase::CreateWriterForNewNetwork(
+    const QuicIpAddress& new_host,
+    int port) {
+  set_bind_to_address(new_host);
+  set_local_port(port);
+  if (!network_helper_->CreateUDPSocketAndBind(server_address_,
+                                               bind_to_address_, port)) {
+    return nullptr;
+  }
+
+  QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
+  QUIC_LOG_IF(WARNING, writer == writer_.get())
+      << "The new writer is wrapped in the same wrapper as the old one, thus "
+         "appearing to have the same address as the old one.";
+  return std::unique_ptr<QuicPacketWriter>(writer);
+}
+
+bool QuicClientBase::ChangeEphemeralPort() {
+  auto current_host = network_helper_->GetLatestClientAddress().host();
+  return MigrateSocketWithSpecifiedPort(current_host, 0 /*any ephemeral port*/);
+}
+
+QuicSession* QuicClientBase::session() {
+  return session_.get();
+}
+
+const QuicSession* QuicClientBase::session() const {
+  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) {
+  if (!connected()) {
+    QUIC_BUG(quic_bug_10906_3)
+        << "Cannot WaitForStreamToClose on non-connected client";
+    return;
+  }
+
+  while (connected() && !session_->IsClosedStream(id)) {
+    WaitForEvents();
+  }
+}
+
+bool QuicClientBase::WaitForOneRttKeysAvailable() {
+  if (!connected()) {
+    QUIC_BUG(quic_bug_10906_4)
+        << "Cannot WaitForOneRttKeysAvailable on non-connected client";
+    return false;
+  }
+
+  while (connected() && !session_->OneRttKeysAvailable()) {
+    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::WaitForHandshakeConfirmed() {
+  if (!session_->connection()->version().UsesTls()) {
+    return WaitForOneRttKeysAvailable();
+  }
+  // Otherwise, wait for receipt of HANDSHAKE_DONE frame.
+  while (connected() && session_->GetHandshakeState() < HANDSHAKE_CONFIRMED) {
+    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_->transport_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();
+}
+
+int QuicClientBase::GetNumReceivedServerConfigUpdates() {
+  // If we are not actively attempting to connect, the session object
+  // corresponds to the previous connection and should not be used.
+  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() {
+  return GenerateNewConnectionId();
+}
+
+QuicConnectionId QuicClientBase::GenerateNewConnectionId() {
+  return QuicUtils::CreateRandomConnectionId(server_connection_id_length_);
+}
+
+QuicConnectionId QuicClientBase::GetClientConnectionId() {
+  return QuicUtils::CreateRandomConnectionId(client_connection_id_length_);
+}
+
+bool QuicClientBase::CanReconnectWithDifferentVersion(
+    ParsedQuicVersion* version) const {
+  if (session_ == nullptr || session_->connection() == nullptr ||
+      session_->error() != QUIC_INVALID_VERSION) {
+    return false;
+  }
+
+  const auto& server_supported_versions =
+      session_->connection()->server_supported_versions();
+  if (server_supported_versions.empty()) {
+    return false;
+  }
+
+  for (const auto& client_version : supported_versions_) {
+    if (std::find(server_supported_versions.begin(),
+                  server_supported_versions.end(),
+                  client_version) != server_supported_versions.end()) {
+      *version = client_version;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool QuicClientBase::HasPendingPathValidation() {
+  return session()->HasPendingPathValidation();
+}
+
+class ValidationResultDelegate : public QuicPathValidator::ResultDelegate {
+ public:
+  ValidationResultDelegate(QuicClientBase* client)
+      : QuicPathValidator::ResultDelegate(), client_(client) {}
+
+  void OnPathValidationSuccess(
+      std::unique_ptr<QuicPathValidationContext> context) override {
+    QUIC_DLOG(INFO) << "Successfully validated path from " << *context;
+    client_->AddValidatedPath(std::move(context));
+  }
+  void OnPathValidationFailure(
+      std::unique_ptr<QuicPathValidationContext> context) override {
+    QUIC_LOG(WARNING) << "Fail to validate path " << *context
+                      << ", stop migrating.";
+    client_->session()->connection()->OnPathValidationFailureAtClient();
+  }
+
+ private:
+  QuicClientBase* client_;
+};
+
+void QuicClientBase::ValidateNewNetwork(const QuicIpAddress& host) {
+  std::unique_ptr<QuicPacketWriter> writer =
+      CreateWriterForNewNetwork(host, local_port_);
+  auto result_delegate = std::make_unique<ValidationResultDelegate>(this);
+  if (writer == nullptr) {
+    result_delegate->OnPathValidationFailure(
+        std::make_unique<PathMigrationContext>(
+            nullptr, network_helper_->GetLatestClientAddress(),
+            session_->peer_address()));
+    return;
+  }
+  session()->ValidatePath(
+      std::make_unique<PathMigrationContext>(
+          std::move(writer), network_helper_->GetLatestClientAddress(),
+          session_->peer_address()),
+      std::move(result_delegate));
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_client_base.h b/quiche/quic/tools/quic_client_base.h
new file mode 100644
index 0000000..9b568eb
--- /dev/null
+++ b/quiche/quic/tools/quic_client_base.h
@@ -0,0 +1,408 @@
+// 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.
+
+// A base class for the toy client, which connects to a specified port and sends
+// QUIC request to that endpoint.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_CLIENT_BASE_H_
+#define QUICHE_QUIC_TOOLS_QUIC_CLIENT_BASE_H_
+
+#include <memory>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/crypto_handshake.h"
+#include "quiche/quic/core/http/quic_client_push_promise_index.h"
+#include "quiche/quic/core/http/quic_spdy_client_session.h"
+#include "quiche/quic/core/http/quic_spdy_client_stream.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+
+namespace quic {
+
+class ProofVerifier;
+class QuicServerId;
+class SessionCache;
+
+// QuicClientBase handles establishing a connection to the passed in
+// server id, including ensuring that it supports the passed in versions
+// and config.
+// Subclasses derived from this class are responsible for creating the
+// actual QuicSession instance, as well as defining functions that
+// create and run the underlying network transport.
+class QuicClientBase {
+ public:
+  // An interface to various network events that the QuicClient will need to
+  // interact with.
+  class NetworkHelper {
+   public:
+    virtual ~NetworkHelper();
+
+    // Runs one iteration of the event loop.
+    virtual void RunEventLoop() = 0;
+
+    // Used during initialization: creates the UDP socket FD, sets socket
+    // options, and binds the socket to our address.
+    virtual bool CreateUDPSocketAndBind(QuicSocketAddress server_address,
+                                        QuicIpAddress bind_to_address,
+                                        int bind_to_port) = 0;
+
+    // Unregister and close all open UDP sockets.
+    virtual void CleanUpAllUDPSockets() = 0;
+
+    // If the client has at least one UDP socket, return address of the latest
+    // created one. Otherwise, return an empty socket address.
+    virtual QuicSocketAddress GetLatestClientAddress() const = 0;
+
+    // Creates a packet writer to be used for the next connection.
+    virtual QuicPacketWriter* CreateQuicPacketWriter() = 0;
+  };
+
+  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,
+                 std::unique_ptr<SessionCache> session_cache);
+  QuicClientBase(const QuicClientBase&) = delete;
+  QuicClientBase& operator=(const QuicClientBase&) = delete;
+
+  virtual ~QuicClientBase();
+
+  // Initializes the client to create a connection. Should be called exactly
+  // once before calling StartConnect or Connect. Returns true if the
+  // initialization succeeds, false otherwise.
+  virtual bool Initialize();
+
+  // "Connect" to the QUIC server, including performing synchronous crypto
+  // handshake.
+  bool Connect();
+
+  // Start the crypto handshake.  This can be done in place of the synchronous
+  // Connect(), but callers are responsible for making sure the crypto handshake
+  // completes.
+  void StartConnect();
+
+  // Calls session()->Initialize(). Subclasses may override this if any extra
+  // initialization needs to be done. Subclasses should expect that session()
+  // is non-null and valid.
+  virtual void InitializeSession();
+
+  // Disconnects from the QUIC server.
+  void Disconnect();
+
+  // Returns true if the crypto handshake has yet to establish encryption.
+  // Returns false if encryption is active (even if the server hasn't confirmed
+  // the handshake) or if the connection has been closed.
+  bool EncryptionBeingEstablished();
+
+  // Wait for events until the stream with the given ID is closed.
+  void WaitForStreamToClose(QuicStreamId id);
+
+  // Wait for 1-RTT keys become available.
+  // Returns true once 1-RTT keys are available, false otherwise.
+  ABSL_MUST_USE_RESULT bool WaitForOneRttKeysAvailable();
+
+  // Wait for handshake state proceeds to HANDSHAKE_CONFIRMED.
+  // In QUIC crypto, this does the same as WaitForOneRttKeysAvailable, while in
+  // TLS, this waits for HANDSHAKE_DONE frame is received.
+  ABSL_MUST_USE_RESULT bool WaitForHandshakeConfirmed();
+
+  // Wait up to 50ms, and handle any events which occur.
+  // Returns true if there are any outstanding requests.
+  bool WaitForEvents();
+
+  // Migrate to a new socket (new_host) during an active connection.
+  bool MigrateSocket(const QuicIpAddress& new_host);
+
+  // Migrate to a new socket (new_host, port) during an active connection.
+  bool MigrateSocketWithSpecifiedPort(const QuicIpAddress& new_host, int port);
+
+  // Validate the new socket and migrate to it if the validation succeeds.
+  // Otherwise stay on the current socket. Return true if the validation has
+  // started.
+  bool ValidateAndMigrateSocket(const QuicIpAddress& new_host);
+
+  // Open a new socket to change to a new ephemeral port.
+  bool ChangeEphemeralPort();
+
+  QuicSession* session();
+  const QuicSession* session() const;
+
+  bool connected() const;
+  virtual bool goaway_received() const;
+
+  const QuicServerId& server_id() const { return server_id_; }
+
+  // This should only be set before the initial Connect()
+  void set_server_id(const QuicServerId& server_id) { server_id_ = server_id; }
+
+  void SetUserAgentID(const std::string& user_agent_id) {
+    crypto_config_.set_user_agent_id(user_agent_id);
+  }
+
+  void SetTlsSignatureAlgorithms(std::string signature_algorithms) {
+    crypto_config_.set_tls_signature_algorithms(
+        std::move(signature_algorithms));
+  }
+
+  const ParsedQuicVersionVector& supported_versions() const {
+    return supported_versions_;
+  }
+
+  void SetSupportedVersions(const ParsedQuicVersionVector& versions) {
+    supported_versions_ = versions;
+  }
+
+  QuicConfig* config() { return &config_; }
+
+  QuicCryptoClientConfig* crypto_config() { return &crypto_config_; }
+
+  // Change the initial maximum packet size of the connection.  Has to be called
+  // before Connect()/StartConnect() in order to have any effect.
+  void set_initial_max_packet_length(QuicByteCount initial_max_packet_length) {
+    initial_max_packet_length_ = initial_max_packet_length;
+  }
+
+  // The number of client hellos sent.
+  int GetNumSentClientHellos();
+
+  // Returns true if early data (0-RTT data) was sent and the server accepted
+  // it.
+  virtual bool EarlyDataAccepted() = 0;
+
+  // Returns true if the handshake was delayed one round trip by the server
+  // because the server wanted proof the client controls its source address
+  // before progressing further. In Google QUIC, this would be due to an
+  // inchoate REJ in the QUIC Crypto handshake; in IETF QUIC this would be due
+  // to a Retry packet.
+  // TODO(nharper): Consider a better name for this method.
+  virtual bool ReceivedInchoateReject() = 0;
+
+  // Gather the stats for the last session and update the stats for the overall
+  // connection.
+  void UpdateStats();
+
+  // The number of server config updates received.
+  int GetNumReceivedServerConfigUpdates();
+
+  // Returns any errors that occurred at the connection-level.
+  QuicErrorCode connection_error() const;
+  void set_connection_error(QuicErrorCode connection_error) {
+    connection_error_ = connection_error;
+  }
+
+  bool connected_or_attempting_connect() const {
+    return connected_or_attempting_connect_;
+  }
+  void set_connected_or_attempting_connect(
+      bool connected_or_attempting_connect) {
+    connected_or_attempting_connect_ = connected_or_attempting_connect;
+  }
+
+  QuicPacketWriter* writer() { return writer_.get(); }
+  void set_writer(QuicPacketWriter* writer) {
+    if (writer_.get() != writer) {
+      writer_.reset(writer);
+    }
+  }
+
+  void reset_writer() { writer_.reset(); }
+
+  ProofVerifier* proof_verifier() const;
+
+  void set_bind_to_address(QuicIpAddress address) {
+    bind_to_address_ = address;
+  }
+
+  QuicIpAddress bind_to_address() const { return bind_to_address_; }
+
+  void set_local_port(int local_port) { local_port_ = local_port; }
+
+  int local_port() const { return local_port_; }
+
+  const QuicSocketAddress& server_address() const { return server_address_; }
+
+  void set_server_address(const QuicSocketAddress& server_address) {
+    server_address_ = server_address;
+  }
+
+  QuicConnectionHelperInterface* helper() { return helper_.get(); }
+
+  NetworkHelper* network_helper();
+  const NetworkHelper* network_helper() const;
+
+  bool initialized() const { return initialized_; }
+
+  void SetPreSharedKey(absl::string_view key) {
+    crypto_config_.set_pre_shared_key(key);
+  }
+
+  void set_connection_debug_visitor(
+      QuicConnectionDebugVisitor* connection_debug_visitor) {
+    connection_debug_visitor_ = connection_debug_visitor;
+  }
+
+  void set_server_connection_id_length(uint8_t server_connection_id_length) {
+    server_connection_id_length_ = server_connection_id_length;
+  }
+
+  void set_client_connection_id_length(uint8_t client_connection_id_length) {
+    client_connection_id_length_ = client_connection_id_length;
+  }
+
+  bool HasPendingPathValidation();
+
+  void ValidateNewNetwork(const QuicIpAddress& host);
+
+  void AddValidatedPath(std::unique_ptr<QuicPathValidationContext> context) {
+    validated_paths_.push_back(std::move(context));
+  }
+
+  const std::vector<std::unique_ptr<QuicPathValidationContext>>&
+  validated_paths() const {
+    return validated_paths_;
+  }
+
+ protected:
+  // TODO(rch): Move GetNumSentClientHellosFromSession and
+  // GetNumReceivedServerConfigUpdatesFromSession into a new/better
+  // QuicSpdyClientSession class. The current inherits dependencies from
+  // Spdy. When that happens this class and all its subclasses should
+  // work with QuicSpdyClientSession instead of QuicSession.
+  // That will obviate the need for the pure virtual functions below.
+
+  // Extract the number of sent client hellos from the session.
+  virtual int GetNumSentClientHellosFromSession() = 0;
+
+  // The number of server config updates received.
+  virtual int GetNumReceivedServerConfigUpdatesFromSession() = 0;
+
+  // If this client supports buffering data, resend it.
+  virtual void ResendSavedData() = 0;
+
+  // If this client supports buffering data, clear it.
+  virtual void ClearDataToResend() = 0;
+
+  // Takes ownership of |connection|. If you override this function,
+  // you probably want to call ResetSession() in your destructor.
+  // TODO(rch): Change the connection parameter to take in a
+  // std::unique_ptr<QuicConnection> instead.
+  virtual std::unique_ptr<QuicSession> CreateQuicClientSession(
+      const ParsedQuicVersionVector& supported_versions,
+      QuicConnection* connection) = 0;
+
+  // Generates the next ConnectionId for |server_id_|.  By default, if the
+  // cached server config contains a server-designated ID, that ID will be
+  // returned.  Otherwise, the next random ID will be returned.
+  QuicConnectionId GetNextConnectionId();
+
+  // Generates a new, random connection ID (as opposed to a server-designated
+  // connection ID).
+  virtual QuicConnectionId GenerateNewConnectionId();
+
+  // Returns the client connection ID to use.
+  virtual QuicConnectionId GetClientConnectionId();
+
+  QuicAlarmFactory* alarm_factory() { return alarm_factory_.get(); }
+
+  // Subclasses may need to explicitly clear the session on destruction
+  // if they create it with objects that will be destroyed before this is.
+  // You probably want to call this if you override CreateQuicSpdyClientSession.
+  void ResetSession() { session_.reset(); }
+
+  // Returns true if the corresponding of this client has active requests.
+  virtual bool HasActiveRequests() = 0;
+
+ private:
+  // Returns true and set |version| if client can reconnect with a different
+  // version.
+  bool CanReconnectWithDifferentVersion(ParsedQuicVersion* version) const;
+
+  std::unique_ptr<QuicPacketWriter> CreateWriterForNewNetwork(
+      const QuicIpAddress& new_host,
+      int port);
+
+  // |server_id_| is a tuple (hostname, port, is_https) of the server.
+  QuicServerId server_id_;
+
+  // Tracks if the client is initialized to connect.
+  bool initialized_;
+
+  // Address of the server.
+  QuicSocketAddress server_address_;
+
+  // If initialized, the address to bind to.
+  QuicIpAddress bind_to_address_;
+
+  // Local port to bind to. Initialize to 0.
+  int local_port_;
+
+  // config_ and crypto_config_ contain configuration and cached state about
+  // servers.
+  QuicConfig config_;
+  QuicCryptoClientConfig crypto_config_;
+
+  // Helper to be used by created connections. Must outlive |session_|.
+  std::unique_ptr<QuicConnectionHelperInterface> helper_;
+
+  // Alarm factory to be used by created connections. Must outlive |session_|.
+  std::unique_ptr<QuicAlarmFactory> alarm_factory_;
+
+  // Writer used to actually send packets to the wire. Must outlive |session_|.
+  std::unique_ptr<QuicPacketWriter> writer_;
+
+  // Session which manages streams.
+  std::unique_ptr<QuicSession> session_;
+
+  // This vector contains QUIC versions which we currently support.
+  // This should be ordered such that the highest supported version is the first
+  // element, with subsequent elements in descending order (versions can be
+  // skipped as necessary). We will always pick supported_versions_[0] as the
+  // initial version to use.
+  ParsedQuicVersionVector supported_versions_;
+
+  // The initial value of maximum packet size of the connection.  If set to
+  // zero, the default is used.
+  QuicByteCount initial_max_packet_length_;
+
+  // The number of hellos sent during the current/latest connection.
+  int num_sent_client_hellos_;
+
+  // Used to store any errors that occurred with the overall connection (as
+  // opposed to that associated with the last session object).
+  QuicErrorCode connection_error_;
+
+  // True when the client is attempting to connect.  Set to false between a call
+  // to Disconnect() and the subsequent call to StartConnect().  When
+  // connected_or_attempting_connect_ is false, the session object corresponds
+  // to the previous client-level connection.
+  bool connected_or_attempting_connect_;
+
+  // The network helper used to create sockets and manage the event loop.
+  // Not owned by this class.
+  std::unique_ptr<NetworkHelper> network_helper_;
+
+  // The debug visitor set on the connection right after it is constructed.
+  // Not owned, must be valid for the lifetime of the QuicClientBase instance.
+  QuicConnectionDebugVisitor* connection_debug_visitor_;
+
+  // GenerateNewConnectionId creates a random connection ID of this length.
+  // Defaults to 8.
+  uint8_t server_connection_id_length_;
+
+  // GetClientConnectionId creates a random connection ID of this length.
+  // Defaults to 0.
+  uint8_t client_connection_id_length_;
+
+  // Stores validated paths.
+  std::vector<std::unique_ptr<QuicPathValidationContext>> validated_paths_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_CLIENT_BASE_H_
diff --git a/quiche/quic/tools/quic_client_bin.cc b/quiche/quic/tools/quic_client_bin.cc
new file mode 100644
index 0000000..ad2acf9
--- /dev/null
+++ b/quiche/quic/tools/quic_client_bin.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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.
+
+// A binary wrapper for QuicClient.
+// Connects to a host using QUIC, sends a request to the provided URL, and
+// displays the response.
+//
+// Some usage examples:
+//
+// Standard request/response:
+//   quic_client www.google.com
+//   quic_client www.google.com --quiet
+//   quic_client www.google.com --port=443
+//
+// Use a specific version:
+//   quic_client www.google.com --quic_version=23
+//
+// Send a POST instead of a GET:
+//   quic_client www.google.com --body="this is a POST body"
+//
+// Append additional headers to the request:
+//   quic_client www.google.com --headers="header-a: 1234; header-b: 5678"
+//
+// Connect to a host different to the URL being requested:
+//   quic_client mail.google.com --host=www.google.com
+//
+// Connect to a specific IP:
+//   IP=`dig www.google.com +short | head -1`
+//   quic_client www.google.com --host=${IP}
+//
+// Send repeated requests and change ephemeral port between requests
+//   quic_client www.google.com --num_requests=10
+//
+// Try to connect to a host which does not speak QUIC:
+//   quic_client www.example.com
+//
+// This tool is available as a built binary at:
+// /google/data/ro/teams/quic/tools/quic_client
+// After submitting changes to this file, you will need to follow the
+// instructions at go/quic_client_binary_update
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "quiche/quic/tools/quic_epoll_client_factory.h"
+#include "quiche/quic/tools/quic_toy_client.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/platform/api/quiche_system_event_loop.h"
+
+int main(int argc, char* argv[]) {
+  quiche::QuicheSystemEventLoop event_loop("quic_client");
+  const char* usage = "Usage: quic_client [options] <url>";
+
+  // All non-flag arguments should be interpreted as URLs to fetch.
+  std::vector<std::string> urls =
+      quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+  if (urls.size() != 1) {
+    quiche::QuichePrintCommandLineFlagHelp(usage);
+    exit(0);
+  }
+
+  quic::QuicEpollClientFactory factory;
+  quic::QuicToyClient client(&factory);
+  return client.SendRequestsAndPrintResponses(urls);
+}
diff --git a/quiche/quic/tools/quic_client_epoll_network_helper.cc b/quiche/quic/tools/quic_client_epoll_network_helper.cc
new file mode 100644
index 0000000..f42c4ed
--- /dev/null
+++ b/quiche/quic/tools/quic_client_epoll_network_helper.cc
@@ -0,0 +1,227 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_client_epoll_network_helper.h"
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_data_reader.h"
+#include "quiche/quic/core/quic_epoll_alarm_factory.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/core/quic_udp_socket.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/common/platform/api/quiche_system_event_loop.h"
+
+namespace quic {
+
+namespace {
+const int kEpollFlags = EPOLLIN | EPOLLOUT | EPOLLET;
+}  // namespace
+
+QuicClientEpollNetworkHelper::QuicClientEpollNetworkHelper(
+    QuicEpollServer* epoll_server,
+    QuicClientBase* client)
+    : epoll_server_(epoll_server),
+      packets_dropped_(0),
+      overflow_supported_(false),
+      packet_reader_(new QuicPacketReader()),
+      client_(client),
+      max_reads_per_epoll_loop_(std::numeric_limits<int>::max()) {}
+
+QuicClientEpollNetworkHelper::~QuicClientEpollNetworkHelper() {
+  if (client_->connected()) {
+    client_->session()->connection()->CloseConnection(
+        QUIC_PEER_GOING_AWAY, "Client being torn down",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  }
+
+  CleanUpAllUDPSockets();
+}
+
+std::string QuicClientEpollNetworkHelper::Name() const {
+  return "QuicClientEpollNetworkHelper";
+}
+
+bool QuicClientEpollNetworkHelper::CreateUDPSocketAndBind(
+    QuicSocketAddress server_address,
+    QuicIpAddress bind_to_address,
+    int bind_to_port) {
+  epoll_server_->set_timeout_in_us(50 * 1000);
+
+  int fd = CreateUDPSocket(server_address, &overflow_supported_);
+  if (fd < 0) {
+    return false;
+  }
+
+  QuicSocketAddress client_address;
+  if (bind_to_address.IsInitialized()) {
+    client_address = QuicSocketAddress(bind_to_address, client_->local_port());
+  } else if (server_address.host().address_family() == IpAddressFamily::IP_V4) {
+    client_address = QuicSocketAddress(QuicIpAddress::Any4(), bind_to_port);
+  } else {
+    client_address = QuicSocketAddress(QuicIpAddress::Any6(), bind_to_port);
+  }
+
+  // Some platforms expect that the addrlen given to bind() exactly matches the
+  // size of the associated protocol family's sockaddr struct.
+  // TODO(b/179430548): Revert this when affected platforms are updated to
+  // to support binding with an addrelen of sizeof(sockaddr_storage)
+  socklen_t addrlen;
+  switch (client_address.host().address_family()) {
+    case IpAddressFamily::IP_V4:
+      addrlen = sizeof(sockaddr_in);
+      break;
+    case IpAddressFamily::IP_V6:
+      addrlen = sizeof(sockaddr_in6);
+      break;
+    case IpAddressFamily::IP_UNSPEC:
+      addrlen = 0;
+      break;
+  }
+
+  sockaddr_storage addr = client_address.generic_address();
+  int rc = bind(fd, reinterpret_cast<sockaddr*>(&addr), addrlen);
+  if (rc < 0) {
+    QUIC_LOG(ERROR) << "Bind failed: " << strerror(errno)
+                    << " bind_to_address:" << bind_to_address
+                    << ", bind_to_port:" << bind_to_port
+                    << ", client_address:" << client_address;
+    return false;
+  }
+
+  if (client_address.FromSocket(fd) != 0) {
+    QUIC_LOG(ERROR) << "Unable to get self address.  Error: "
+                    << strerror(errno);
+  }
+
+  fd_address_map_[fd] = client_address;
+  epoll_server_->RegisterFD(fd, this, kEpollFlags);
+  return true;
+}
+
+void QuicClientEpollNetworkHelper::CleanUpUDPSocket(int fd) {
+  CleanUpUDPSocketImpl(fd);
+  fd_address_map_.erase(fd);
+}
+
+void QuicClientEpollNetworkHelper::CleanUpAllUDPSockets() {
+  for (std::pair<int, QuicSocketAddress> fd_address : fd_address_map_) {
+    CleanUpUDPSocketImpl(fd_address.first);
+  }
+  fd_address_map_.clear();
+}
+
+void QuicClientEpollNetworkHelper::CleanUpUDPSocketImpl(int fd) {
+  if (fd > -1) {
+    epoll_server_->UnregisterFD(fd);
+    int rc = close(fd);
+    QUICHE_DCHECK_EQ(0, rc);
+  }
+}
+
+void QuicClientEpollNetworkHelper::RunEventLoop() {
+  quiche::QuicheRunSystemEventLoopIteration();
+  epoll_server_->WaitForEventsAndExecuteCallbacks();
+}
+
+void QuicClientEpollNetworkHelper::OnRegistration(QuicEpollServer* /*eps*/,
+                                                  int /*fd*/,
+                                                  int /*event_mask*/) {}
+void QuicClientEpollNetworkHelper::OnModification(int /*fd*/,
+                                                  int /*event_mask*/) {}
+void QuicClientEpollNetworkHelper::OnUnregistration(int /*fd*/,
+                                                    bool /*replaced*/) {}
+void QuicClientEpollNetworkHelper::OnShutdown(QuicEpollServer* /*eps*/,
+                                              int /*fd*/) {}
+
+void QuicClientEpollNetworkHelper::OnEvent(int fd, QuicEpollEvent* event) {
+  if (event->in_events & EPOLLIN) {
+    QUIC_DVLOG(1) << "Read packets on EPOLLIN";
+    int times_to_read = max_reads_per_epoll_loop_;
+    bool more_to_read = true;
+    QuicPacketCount packets_dropped = 0;
+    while (client_->connected() && more_to_read && times_to_read > 0) {
+      more_to_read = packet_reader_->ReadAndDispatchPackets(
+          fd, GetLatestClientAddress().port(), *client_->helper()->GetClock(),
+          this, overflow_supported_ ? &packets_dropped : nullptr);
+      --times_to_read;
+    }
+    if (packets_dropped_ < packets_dropped) {
+      QUIC_LOG(ERROR)
+          << packets_dropped - packets_dropped_
+          << " more packets are dropped in the socket receive buffer.";
+      packets_dropped_ = packets_dropped;
+    }
+    if (client_->connected() && more_to_read) {
+      event->out_ready_mask |= EPOLLIN;
+    }
+  }
+  if (client_->connected() && (event->in_events & EPOLLOUT)) {
+    client_->writer()->SetWritable();
+    client_->session()->connection()->OnCanWrite();
+  }
+  if (event->in_events & EPOLLERR) {
+    QUIC_DLOG(INFO) << "Epollerr";
+  }
+}
+
+QuicPacketWriter* QuicClientEpollNetworkHelper::CreateQuicPacketWriter() {
+  return new QuicDefaultPacketWriter(GetLatestFD());
+}
+
+void QuicClientEpollNetworkHelper::SetClientPort(int port) {
+  fd_address_map_.back().second =
+      QuicSocketAddress(GetLatestClientAddress().host(), port);
+}
+
+QuicSocketAddress QuicClientEpollNetworkHelper::GetLatestClientAddress() const {
+  if (fd_address_map_.empty()) {
+    return QuicSocketAddress();
+  }
+
+  return fd_address_map_.back().second;
+}
+
+int QuicClientEpollNetworkHelper::GetLatestFD() const {
+  if (fd_address_map_.empty()) {
+    return -1;
+  }
+
+  return fd_address_map_.back().first;
+}
+
+void QuicClientEpollNetworkHelper::ProcessPacket(
+    const QuicSocketAddress& self_address,
+    const QuicSocketAddress& peer_address,
+    const QuicReceivedPacket& packet) {
+  client_->session()->ProcessUdpPacket(self_address, peer_address, packet);
+}
+
+int QuicClientEpollNetworkHelper::CreateUDPSocket(
+    QuicSocketAddress server_address,
+    bool* overflow_supported) {
+  QuicUdpSocketApi api;
+  int fd = api.Create(server_address.host().AddressFamilyToInt(),
+                      /*receive_buffer_size =*/kDefaultSocketReceiveBuffer,
+                      /*send_buffer_size =*/kDefaultSocketReceiveBuffer);
+  if (fd < 0) {
+    return fd;
+  }
+
+  *overflow_supported = api.EnableDroppedPacketCount(fd);
+  api.EnableReceiveTimestamp(fd);
+  return fd;
+}
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_client_epoll_network_helper.h b/quiche/quic/tools/quic_client_epoll_network_helper.h
new file mode 100644
index 0000000..637af45
--- /dev/null
+++ b/quiche/quic/tools/quic_client_epoll_network_helper.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2012 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.
+
+// An implementation of the QuicClientBase::NetworkHelper
+// that is based off the epoll server.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_CLIENT_EPOLL_NETWORK_HELPER_H_
+#define QUICHE_QUIC_TOOLS_QUIC_CLIENT_EPOLL_NETWORK_HELPER_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "quiche/quic/core/http/quic_client_push_promise_index.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/core/quic_packet_reader.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/tools/quic_client_base.h"
+#include "quiche/common/quiche_linked_hash_map.h"
+
+namespace quic {
+
+namespace test {
+class QuicClientPeer;
+}  // namespace test
+
+// An implementation of the QuicClientBase::NetworkHelper based off
+// the epoll server.
+class QuicClientEpollNetworkHelper : public QuicClientBase::NetworkHelper,
+                                     public QuicEpollCallbackInterface,
+                                     public ProcessPacketInterface {
+ public:
+  // Create a quic client, which will have events managed by an externally owned
+  // EpollServer.
+  QuicClientEpollNetworkHelper(QuicEpollServer* epoll_server,
+                               QuicClientBase* client);
+  QuicClientEpollNetworkHelper(const QuicClientEpollNetworkHelper&) = delete;
+  QuicClientEpollNetworkHelper& operator=(const QuicClientEpollNetworkHelper&) =
+      delete;
+
+  ~QuicClientEpollNetworkHelper() override;
+
+  // Return a name describing the class for use in debug/error reporting.
+  std::string Name() const override;
+
+  // From EpollCallbackInterface
+  void OnRegistration(QuicEpollServer* eps, int fd, int event_mask) override;
+  void OnModification(int fd, int event_mask) override;
+  void OnEvent(int fd, QuicEpollEvent* event) override;
+  // |fd_| can be unregistered without the client being disconnected. This
+  // happens in b3m QuicProber where we unregister |fd_| to feed in events to
+  // the client from the SelectServer.
+  void OnUnregistration(int fd, bool replaced) override;
+  void OnShutdown(QuicEpollServer* eps, int fd) override;
+
+  // From ProcessPacketInterface. This will be called for each received
+  // packet.
+  void ProcessPacket(const QuicSocketAddress& self_address,
+                     const QuicSocketAddress& peer_address,
+                     const QuicReceivedPacket& packet) override;
+
+  // From NetworkHelper.
+  void RunEventLoop() override;
+  bool CreateUDPSocketAndBind(QuicSocketAddress server_address,
+                              QuicIpAddress bind_to_address,
+                              int bind_to_port) override;
+  void CleanUpAllUDPSockets() override;
+  QuicSocketAddress GetLatestClientAddress() const override;
+  QuicPacketWriter* CreateQuicPacketWriter() override;
+
+  // Accessors provided for convenience, not part of any interface.
+
+  QuicEpollServer* epoll_server() { return epoll_server_; }
+
+  const quiche::QuicheLinkedHashMap<int, QuicSocketAddress>& fd_address_map()
+      const {
+    return fd_address_map_;
+  }
+
+  // If the client has at least one UDP socket, return the latest created one.
+  // Otherwise, return -1.
+  int GetLatestFD() const;
+
+  // Create socket for connection to |server_address| with default socket
+  // options.
+  // Return fd index.
+  virtual int CreateUDPSocket(QuicSocketAddress server_address,
+                              bool* overflow_supported);
+
+  QuicClientBase* client() { return client_; }
+
+  void set_max_reads_per_epoll_loop(int num_reads) {
+    max_reads_per_epoll_loop_ = num_reads;
+  }
+  // If |fd| is an open UDP socket, unregister and close it. Otherwise, do
+  // nothing.
+  void CleanUpUDPSocket(int fd);
+
+ private:
+  friend class test::QuicClientPeer;
+
+  // Used for testing.
+  void SetClientPort(int port);
+
+  // Actually clean up |fd|.
+  void CleanUpUDPSocketImpl(int fd);
+
+  // Listens for events on the client socket.
+  QuicEpollServer* epoll_server_;
+
+  // Map mapping created UDP sockets to their addresses. By using linked hash
+  // map, the order of socket creation can be recorded.
+  quiche::QuicheLinkedHashMap<int, QuicSocketAddress> fd_address_map_;
+
+  // If overflow_supported_ is true, this will be the number of packets dropped
+  // during the lifetime of the server.
+  QuicPacketCount packets_dropped_;
+
+  // True if the kernel supports SO_RXQ_OVFL, the number of packets dropped
+  // because the socket would otherwise overflow.
+  bool overflow_supported_;
+
+  // Point to a QuicPacketReader object on the heap. The reader allocates more
+  // space than allowed on the stack.
+  std::unique_ptr<QuicPacketReader> packet_reader_;
+
+  QuicClientBase* client_;
+
+  int max_reads_per_epoll_loop_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_CLIENT_EPOLL_NETWORK_HELPER_H_
diff --git a/quiche/quic/tools/quic_client_interop_test_bin.cc b/quiche/quic/tools/quic_client_interop_test_bin.cc
new file mode 100644
index 0000000..ece457f
--- /dev/null
+++ b/quiche/quic/tools/quic_client_interop_test_bin.cc
@@ -0,0 +1,467 @@
+// Copyright (c) 2019 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 <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/strings/str_cat.h"
+#include "quiche/quic/core/crypto/quic_client_session_cache.h"
+#include "quiche/quic/core/quic_epoll_clock.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/test_tools/quic_connection_peer.h"
+#include "quiche/quic/test_tools/quic_session_peer.h"
+#include "quiche/quic/tools/fake_proof_verifier.h"
+#include "quiche/quic/tools/quic_client.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/platform/api/quiche_system_event_loop.h"
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, host, "",
+                                "The IP or hostname to connect to.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, quic_version, "",
+    "The QUIC version to use. Defaults to most recent IETF QUIC version.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
+
+namespace quic {
+
+enum class Feature {
+  // First row of features ("table stakes")
+  // A version negotiation response is elicited and acted on.
+  kVersionNegotiation,
+  // The handshake completes successfully.
+  kHandshake,
+  // Stream data is being exchanged and ACK'ed.
+  kStreamData,
+  // The connection close procedcure completes with a zero error code.
+  kConnectionClose,
+  // The connection was established using TLS resumption.
+  kResumption,
+  // 0-RTT data is being sent and acted on.
+  kZeroRtt,
+  // A RETRY packet was successfully processed.
+  kRetry,
+  // A handshake using a ClientHello that spans multiple packets completed
+  // successfully.
+  kQuantum,
+
+  // Second row of features (anything else protocol-related)
+  // We switched to a different port and the server migrated to it.
+  kRebinding,
+  // One endpoint can update keys and its peer responds correctly.
+  kKeyUpdate,
+
+  // Third row of features (H3 tests)
+  // An H3 transaction succeeded.
+  kHttp3,
+  // One or both endpoints insert entries into dynamic table and subsequenly
+  // reference them from header blocks.
+  kDynamicEntryReferenced,
+};
+
+char MatrixLetter(Feature f) {
+  switch (f) {
+    case Feature::kVersionNegotiation:
+      return 'V';
+    case Feature::kHandshake:
+      return 'H';
+    case Feature::kStreamData:
+      return 'D';
+    case Feature::kConnectionClose:
+      return 'C';
+    case Feature::kResumption:
+      return 'R';
+    case Feature::kZeroRtt:
+      return 'Z';
+    case Feature::kRetry:
+      return 'S';
+    case Feature::kQuantum:
+      return 'Q';
+    case Feature::kRebinding:
+      return 'B';
+    case Feature::kKeyUpdate:
+      return 'U';
+    case Feature::kHttp3:
+      return '3';
+    case Feature::kDynamicEntryReferenced:
+      return 'd';
+  }
+}
+
+class QuicClientInteropRunner : QuicConnectionDebugVisitor {
+ public:
+  QuicClientInteropRunner() {}
+
+  void InsertFeature(Feature feature) { features_.insert(feature); }
+
+  std::set<Feature> features() const { return features_; }
+
+  // Attempts a resumption using |client| by disconnecting and reconnecting. If
+  // resumption is successful, |features_| is modified to add
+  // Feature::kResumption to it, otherwise it is left unmodified.
+  void AttemptResumption(QuicClient* client, const std::string& authority);
+
+  void AttemptRequest(QuicSocketAddress addr,
+                      std::string authority,
+                      QuicServerId server_id,
+                      ParsedQuicVersion version,
+                      bool test_version_negotiation,
+                      bool attempt_rebind,
+                      bool attempt_multi_packet_chlo,
+                      bool attempt_key_update);
+
+  // Constructs a SpdyHeaderBlock containing the pseudo-headers needed to make a
+  // GET request to "/" on the hostname |authority|.
+  spdy::Http2HeaderBlock ConstructHeaderBlock(const std::string& authority);
+
+  // Sends an HTTP request represented by |header_block| using |client|.
+  void SendRequest(QuicClient* client,
+                   const spdy::Http2HeaderBlock& header_block);
+
+  void OnConnectionCloseFrame(const QuicConnectionCloseFrame& frame) override {
+    switch (frame.close_type) {
+      case GOOGLE_QUIC_CONNECTION_CLOSE:
+        QUIC_LOG(ERROR) << "Received unexpected GoogleQUIC connection close";
+        break;
+      case IETF_QUIC_TRANSPORT_CONNECTION_CLOSE:
+        if (frame.wire_error_code == NO_IETF_QUIC_ERROR) {
+          InsertFeature(Feature::kConnectionClose);
+        } else {
+          QUIC_LOG(ERROR) << "Received transport connection close "
+                          << QuicIetfTransportErrorCodeString(
+                                 static_cast<QuicIetfTransportErrorCodes>(
+                                     frame.wire_error_code));
+        }
+        break;
+      case IETF_QUIC_APPLICATION_CONNECTION_CLOSE:
+        if (frame.wire_error_code == 0) {
+          InsertFeature(Feature::kConnectionClose);
+        } else {
+          QUIC_LOG(ERROR) << "Received application connection close "
+                          << frame.wire_error_code;
+        }
+        break;
+    }
+  }
+
+  void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& /*packet*/) override {
+    InsertFeature(Feature::kVersionNegotiation);
+  }
+
+ private:
+  std::set<Feature> features_;
+};
+
+void QuicClientInteropRunner::AttemptResumption(QuicClient* client,
+                                                const std::string& authority) {
+  client->Disconnect();
+  if (!client->Initialize()) {
+    QUIC_LOG(ERROR) << "Failed to reinitialize client";
+    return;
+  }
+  if (!client->Connect()) {
+    return;
+  }
+
+  bool zero_rtt_attempt = !client->session()->OneRttKeysAvailable();
+
+  spdy::Http2HeaderBlock header_block = ConstructHeaderBlock(authority);
+  SendRequest(client, header_block);
+
+  if (!client->session()->OneRttKeysAvailable()) {
+    return;
+  }
+
+  if (static_cast<QuicCryptoClientStream*>(
+          test::QuicSessionPeer::GetMutableCryptoStream(client->session()))
+          ->IsResumption()) {
+    InsertFeature(Feature::kResumption);
+  }
+  if (static_cast<QuicCryptoClientStream*>(
+          test::QuicSessionPeer::GetMutableCryptoStream(client->session()))
+          ->EarlyDataAccepted() &&
+      zero_rtt_attempt && client->latest_response_code() != -1) {
+    InsertFeature(Feature::kZeroRtt);
+  }
+}
+
+void QuicClientInteropRunner::AttemptRequest(QuicSocketAddress addr,
+                                             std::string authority,
+                                             QuicServerId server_id,
+                                             ParsedQuicVersion version,
+                                             bool test_version_negotiation,
+                                             bool attempt_rebind,
+                                             bool attempt_multi_packet_chlo,
+                                             bool attempt_key_update) {
+  ParsedQuicVersionVector versions = {version};
+  if (test_version_negotiation) {
+    versions.insert(versions.begin(), QuicVersionReservedForNegotiation());
+  }
+
+  auto proof_verifier = std::make_unique<FakeProofVerifier>();
+  auto session_cache = std::make_unique<QuicClientSessionCache>();
+  QuicEpollServer epoll_server;
+  QuicEpollClock epoll_clock(&epoll_server);
+  QuicConfig config;
+  QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(20);
+  config.SetIdleNetworkTimeout(timeout);
+  if (attempt_multi_packet_chlo) {
+    // Make the ClientHello span multiple packets by adding a custom transport
+    // parameter.
+    constexpr auto kCustomParameter =
+        static_cast<TransportParameters::TransportParameterId>(0x173E);
+    std::string custom_value(2000, '?');
+    config.custom_transport_parameters_to_send()[kCustomParameter] =
+        custom_value;
+  }
+  auto client = std::make_unique<QuicClient>(
+      addr, server_id, versions, config, &epoll_server,
+      std::move(proof_verifier), std::move(session_cache));
+  client->set_connection_debug_visitor(this);
+  if (!client->Initialize()) {
+    QUIC_LOG(ERROR) << "Failed to initialize client";
+    return;
+  }
+  const bool connect_result = client->Connect();
+  QuicConnection* connection = client->session()->connection();
+  if (connection == nullptr) {
+    QUIC_LOG(ERROR) << "No QuicConnection object";
+    return;
+  }
+  QuicConnectionStats client_stats = connection->GetStats();
+  if (client_stats.retry_packet_processed) {
+    InsertFeature(Feature::kRetry);
+  }
+  if (test_version_negotiation && connection->version() == version) {
+    InsertFeature(Feature::kVersionNegotiation);
+  }
+  if (test_version_negotiation && !connect_result) {
+    // Failed to negotiate version, retry without version negotiation.
+    AttemptRequest(addr, authority, server_id, version,
+                   /*test_version_negotiation=*/false, attempt_rebind,
+                   attempt_multi_packet_chlo, attempt_key_update);
+    return;
+  }
+  if (!client->session()->OneRttKeysAvailable()) {
+    if (attempt_multi_packet_chlo) {
+      // Failed to handshake with multi-packet client hello, retry without it.
+      AttemptRequest(addr, authority, server_id, version,
+                     test_version_negotiation, attempt_rebind,
+                     /*attempt_multi_packet_chlo=*/false, attempt_key_update);
+      return;
+    }
+    return;
+  }
+  InsertFeature(Feature::kHandshake);
+  if (attempt_multi_packet_chlo) {
+    InsertFeature(Feature::kQuantum);
+  }
+
+  spdy::Http2HeaderBlock header_block = ConstructHeaderBlock(authority);
+  SendRequest(client.get(), header_block);
+
+  if (!client->connected()) {
+    return;
+  }
+
+  if (client->latest_response_code() != -1) {
+    InsertFeature(Feature::kHttp3);
+
+    if (client->client_session()->dynamic_table_entry_referenced()) {
+      InsertFeature(Feature::kDynamicEntryReferenced);
+    }
+
+    if (attempt_rebind) {
+      // Now make a second request after switching to a different client port.
+      if (client->ChangeEphemeralPort()) {
+        client->SendRequestAndWaitForResponse(header_block, "", /*fin=*/true);
+        if (!client->connected()) {
+          // Rebinding does not work, retry without attempting it.
+          AttemptRequest(addr, authority, server_id, version,
+                         test_version_negotiation, /*attempt_rebind=*/false,
+                         attempt_multi_packet_chlo, attempt_key_update);
+          return;
+        }
+        InsertFeature(Feature::kRebinding);
+
+        if (client->client_session()->dynamic_table_entry_referenced()) {
+          InsertFeature(Feature::kDynamicEntryReferenced);
+        }
+      } else {
+        QUIC_LOG(ERROR) << "Failed to change ephemeral port";
+      }
+    }
+
+    if (attempt_key_update) {
+      if (connection->IsKeyUpdateAllowed()) {
+        if (connection->InitiateKeyUpdate(
+                KeyUpdateReason::kLocalForInteropRunner)) {
+          client->SendRequestAndWaitForResponse(header_block, "", /*fin=*/true);
+          if (!client->connected()) {
+            // Key update does not work, retry without attempting it.
+            AttemptRequest(addr, authority, server_id, version,
+                           test_version_negotiation, attempt_rebind,
+                           attempt_multi_packet_chlo,
+                           /*attempt_key_update=*/false);
+            return;
+          }
+          InsertFeature(Feature::kKeyUpdate);
+        } else {
+          QUIC_LOG(ERROR) << "Failed to initiate key update";
+        }
+      } else {
+        QUIC_LOG(ERROR) << "Key update not allowed";
+      }
+    }
+  }
+
+  if (connection->connected()) {
+    connection->CloseConnection(
+        QUIC_NO_ERROR, "Graceful close",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    InsertFeature(Feature::kConnectionClose);
+  }
+
+  AttemptResumption(client.get(), authority);
+}
+
+spdy::Http2HeaderBlock QuicClientInteropRunner::ConstructHeaderBlock(
+    const std::string& authority) {
+  // Construct and send a request.
+  spdy::Http2HeaderBlock header_block;
+  header_block[":method"] = "GET";
+  header_block[":scheme"] = "https";
+  header_block[":authority"] = authority;
+  header_block[":path"] = "/";
+  return header_block;
+}
+
+void QuicClientInteropRunner::SendRequest(
+    QuicClient* client,
+    const spdy::Http2HeaderBlock& header_block) {
+  client->set_store_response(true);
+  client->SendRequestAndWaitForResponse(header_block, "", /*fin=*/true);
+
+  QuicConnection* connection = client->session()->connection();
+  if (connection == nullptr) {
+    QUIC_LOG(ERROR) << "No QuicConnection object";
+    return;
+  }
+  QuicConnectionStats client_stats = connection->GetStats();
+  QuicSentPacketManager* sent_packet_manager =
+      test::QuicConnectionPeer::GetSentPacketManager(connection);
+  const bool received_forward_secure_ack =
+      sent_packet_manager != nullptr &&
+      sent_packet_manager->GetLargestAckedPacket(ENCRYPTION_FORWARD_SECURE)
+          .IsInitialized();
+  if (client_stats.stream_bytes_received > 0 && received_forward_secure_ack) {
+    InsertFeature(Feature::kStreamData);
+  }
+}
+
+std::set<Feature> ServerSupport(std::string dns_host,
+                                std::string url_host,
+                                int port,
+                                ParsedQuicVersion version) {
+  std::cout << "Attempting interop with version " << version << std::endl;
+
+  // Build the client, and try to connect.
+  QuicSocketAddress addr = tools::LookupAddress(dns_host, absl::StrCat(port));
+  if (!addr.IsInitialized()) {
+    QUIC_LOG(ERROR) << "Failed to resolve " << dns_host;
+    return std::set<Feature>();
+  }
+  QuicServerId server_id(url_host, port, false);
+  std::string authority = absl::StrCat(url_host, ":", port);
+
+  QuicClientInteropRunner runner;
+
+  runner.AttemptRequest(addr, authority, server_id, version,
+                        /*test_version_negotiation=*/true,
+                        /*attempt_rebind=*/true,
+                        /*attempt_multi_packet_chlo=*/true,
+                        /*attempt_key_update=*/true);
+
+  return runner.features();
+}
+
+}  // namespace quic
+
+int main(int argc, char* argv[]) {
+  quiche::QuicheSystemEventLoop event_loop("quic_client");
+  const char* usage = "Usage: quic_client_interop_test [options] [url]";
+
+  std::vector<std::string> args =
+      quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+  if (args.size() > 1) {
+    quiche::QuichePrintCommandLineFlagHelp(usage);
+    exit(1);
+  }
+  std::string dns_host = GetQuicFlag(FLAGS_host);
+  std::string url_host = "";
+  int port = GetQuicFlag(FLAGS_port);
+
+  if (!args.empty()) {
+    quic::QuicUrl url(args[0], "https");
+    url_host = url.host();
+    if (dns_host.empty()) {
+      dns_host = url_host;
+    }
+    if (port == 0) {
+      port = url.port();
+    }
+  }
+  if (port == 0) {
+    port = 443;
+  }
+  if (dns_host.empty()) {
+    quiche::QuichePrintCommandLineFlagHelp(usage);
+    exit(1);
+  }
+  if (url_host.empty()) {
+    url_host = dns_host;
+  }
+
+  // Pick QUIC version to use.
+  quic::QuicVersionInitializeSupportForIetfDraft();
+  quic::ParsedQuicVersion version = quic::UnsupportedQuicVersion();
+  std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);
+  if (!quic_version_string.empty()) {
+    version = quic::ParseQuicVersionString(quic_version_string);
+  } else {
+    for (const quic::ParsedQuicVersion& vers : quic::AllSupportedVersions()) {
+      // Use the most recent IETF QUIC version.
+      if (vers.HasIetfQuicFrames() && vers.UsesHttp3() && vers.UsesTls()) {
+        version = vers;
+        break;
+      }
+    }
+  }
+  QUICHE_CHECK(version.IsKnown());
+  QuicEnableVersion(version);
+
+  auto supported_features =
+      quic::ServerSupport(dns_host, url_host, port, version);
+  std::cout << "Results for " << url_host << ":" << port << std::endl;
+  int current_row = 1;
+  for (auto feature : supported_features) {
+    if (current_row < 2 && feature >= quic::Feature::kRebinding) {
+      std::cout << std::endl;
+      current_row = 2;
+    }
+    if (current_row < 3 && feature >= quic::Feature::kHttp3) {
+      std::cout << std::endl;
+      current_row = 3;
+    }
+    std::cout << MatrixLetter(feature);
+  }
+  std::cout << std::endl;
+}
diff --git a/quiche/quic/tools/quic_client_test.cc b/quiche/quic/tools/quic_client_test.cc
new file mode 100644
index 0000000..5c3aec6
--- /dev/null
+++ b/quiche/quic/tools/quic_client_test.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2014 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 "quiche/quic/tools/quic_client.h"
+
+#include <dirent.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <utility>
+
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/platform/api/quic_test_loopback.h"
+#include "quiche/quic/test_tools/crypto_test_utils.h"
+#include "quiche/quic/test_tools/quic_client_peer.h"
+#include "quiche/common/quiche_text_utils.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+const char* kPathToFds = "/proc/self/fd";
+
+// Return the value of a symbolic link in |path|, if |path| is not found, return
+// an empty string.
+std::string ReadLink(const std::string& path) {
+  std::string result(PATH_MAX, '\0');
+  ssize_t result_size = readlink(path.c_str(), &result[0], result.size());
+  if (result_size < 0 && errno == ENOENT) {
+    return "";
+  }
+  QUICHE_CHECK(result_size > 0 &&
+               static_cast<size_t>(result_size) < result.size())
+      << "result_size:" << result_size << ", errno:" << errno
+      << ", path:" << path;
+  result.resize(result_size);
+  return result;
+}
+
+// Counts the number of open sockets for the current process.
+size_t NumOpenSocketFDs() {
+  size_t socket_count = 0;
+  dirent* file;
+  std::unique_ptr<DIR, int (*)(DIR*)> fd_directory(opendir(kPathToFds),
+                                                   closedir);
+  while ((file = readdir(fd_directory.get())) != nullptr) {
+    absl::string_view name(file->d_name);
+    if (name == "." || name == "..") {
+      continue;
+    }
+
+    std::string fd_path = ReadLink(absl::StrCat(kPathToFds, "/", name));
+    if (absl::StartsWith(fd_path, "socket:")) {
+      socket_count++;
+    }
+  }
+  return socket_count;
+}
+
+class QuicClientTest : public QuicTest {
+ public:
+  QuicClientTest() {
+    // Creates and destroys a single client first which may open persistent
+    // sockets when initializing platform dependencies like certificate
+    // verifier. Future creation of addtional clients will deterministically
+    // open one socket per client.
+    CreateAndInitializeQuicClient();
+  }
+
+  // Creates a new QuicClient and Initializes it on an unused port.
+  // Caller is responsible for deletion.
+  std::unique_ptr<QuicClient> CreateAndInitializeQuicClient() {
+    QuicSocketAddress server_address(QuicSocketAddress(TestLoopback(), 0));
+    QuicServerId server_id("hostname", server_address.port(), false);
+    ParsedQuicVersionVector versions = AllSupportedVersions();
+    auto client = std::make_unique<QuicClient>(
+        server_address, server_id, versions, &epoll_server_,
+        crypto_test_utils::ProofVerifierForTesting());
+    EXPECT_TRUE(client->Initialize());
+    return client;
+  }
+
+ private:
+  QuicEpollServer epoll_server_;
+};
+
+TEST_F(QuicClientTest, DoNotLeakSocketFDs) {
+  // Make sure that the QuicClient doesn't leak socket FDs. Doing so could cause
+  // port exhaustion in long running processes which repeatedly create clients.
+
+  // Record the initial number of FDs.
+  size_t number_of_open_fds = NumOpenSocketFDs();
+
+  // Create a number of clients, initialize them, and verify this has resulted
+  // in additional FDs being opened.
+  const int kNumClients = 50;
+  for (int i = 0; i < kNumClients; ++i) {
+    EXPECT_EQ(number_of_open_fds, NumOpenSocketFDs());
+    std::unique_ptr<QuicClient> client(CreateAndInitializeQuicClient());
+    // Initializing the client will create a new FD.
+    EXPECT_EQ(number_of_open_fds + 1, NumOpenSocketFDs());
+  }
+
+  // The FDs created by the QuicClients should now be closed.
+  EXPECT_EQ(number_of_open_fds, NumOpenSocketFDs());
+}
+
+TEST_F(QuicClientTest, CreateAndCleanUpUDPSockets) {
+  size_t number_of_open_fds = NumOpenSocketFDs();
+
+  std::unique_ptr<QuicClient> client(CreateAndInitializeQuicClient());
+  // Creating and initializing a client will result in one socket being opened.
+  EXPECT_EQ(number_of_open_fds + 1, NumOpenSocketFDs());
+
+  // Create more UDP sockets.
+  EXPECT_TRUE(QuicClientPeer::CreateUDPSocketAndBind(client.get()));
+  EXPECT_EQ(number_of_open_fds + 2, NumOpenSocketFDs());
+  EXPECT_TRUE(QuicClientPeer::CreateUDPSocketAndBind(client.get()));
+  EXPECT_EQ(number_of_open_fds + 3, NumOpenSocketFDs());
+
+  // Clean up UDP sockets.
+  QuicClientPeer::CleanUpUDPSocket(client.get(), client->GetLatestFD());
+  EXPECT_EQ(number_of_open_fds + 2, NumOpenSocketFDs());
+  QuicClientPeer::CleanUpUDPSocket(client.get(), client->GetLatestFD());
+  EXPECT_EQ(number_of_open_fds + 1, NumOpenSocketFDs());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_epoll_client_factory.cc b/quiche/quic/tools/quic_epoll_client_factory.cc
new file mode 100644
index 0000000..2719057
--- /dev/null
+++ b/quiche/quic/tools/quic_epoll_client_factory.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2019 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 "quiche/quic/tools/quic_epoll_client_factory.h"
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <utility>
+
+#include "absl/strings/str_cat.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_client.h"
+
+namespace quic {
+
+std::unique_ptr<QuicSpdyClientBase> QuicEpollClientFactory::CreateClient(
+    std::string host_for_handshake,
+    std::string host_for_lookup,
+    int address_family_for_lookup,
+    uint16_t port,
+    ParsedQuicVersionVector versions,
+    const QuicConfig& config,
+    std::unique_ptr<ProofVerifier> verifier,
+    std::unique_ptr<SessionCache> session_cache) {
+  QuicSocketAddress addr = tools::LookupAddress(
+      address_family_for_lookup, host_for_lookup, absl::StrCat(port));
+  if (!addr.IsInitialized()) {
+    QUIC_LOG(ERROR) << "Unable to resolve address: " << host_for_lookup;
+    return nullptr;
+  }
+  QuicServerId server_id(host_for_handshake, port, false);
+  return std::make_unique<QuicClient>(addr, server_id, versions, config,
+                                      &epoll_server_, std::move(verifier),
+                                      std::move(session_cache));
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_epoll_client_factory.h b/quiche/quic/tools/quic_epoll_client_factory.h
new file mode 100644
index 0000000..1e5708f
--- /dev/null
+++ b/quiche/quic/tools/quic_epoll_client_factory.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2019 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.
+
+#ifndef QUICHE_QUIC_TOOLS_EPOLL_CLIENT_FACTORY_H_
+#define QUICHE_QUIC_TOOLS_EPOLL_CLIENT_FACTORY_H_
+
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/tools/quic_toy_client.h"
+
+namespace quic {
+
+// Factory creating QuicClient instances.
+class QuicEpollClientFactory : public QuicToyClient::ClientFactory {
+ public:
+  std::unique_ptr<QuicSpdyClientBase> CreateClient(
+      std::string host_for_handshake,
+      std::string host_for_lookup,
+      int address_family_for_lookup,
+      uint16_t port,
+      ParsedQuicVersionVector versions,
+      const QuicConfig& config,
+      std::unique_ptr<ProofVerifier> verifier,
+      std::unique_ptr<SessionCache> session_cache) override;
+
+ private:
+  QuicEpollServer epoll_server_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_EPOLL_CLIENT_FACTORY_H_
diff --git a/quiche/quic/tools/quic_epoll_server_factory.cc b/quiche/quic/tools/quic_epoll_server_factory.cc
new file mode 100644
index 0000000..a81b973
--- /dev/null
+++ b/quiche/quic/tools/quic_epoll_server_factory.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2019 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 "quiche/quic/tools/quic_epoll_server_factory.h"
+
+#include <utility>
+
+#include "quiche/quic/tools/quic_server.h"
+
+namespace quic {
+
+std::unique_ptr<quic::QuicSpdyServerBase> QuicEpollServerFactory::CreateServer(
+    quic::QuicSimpleServerBackend* backend,
+    std::unique_ptr<quic::ProofSource> proof_source,
+    const quic::ParsedQuicVersionVector& supported_versions) {
+  return std::make_unique<quic::QuicServer>(std::move(proof_source), backend,
+                                            supported_versions);
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_epoll_server_factory.h b/quiche/quic/tools/quic_epoll_server_factory.h
new file mode 100644
index 0000000..6d6876a
--- /dev/null
+++ b/quiche/quic/tools/quic_epoll_server_factory.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2019 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.
+
+#ifndef QUICHE_QUIC_TOOLS_EPOLL_SERVER_FACTORY_H_
+#define QUICHE_QUIC_TOOLS_EPOLL_SERVER_FACTORY_H_
+
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/tools/quic_toy_server.h"
+
+namespace quic {
+
+// Factory creating QuicServer instances.
+class QuicEpollServerFactory : public QuicToyServer::ServerFactory {
+ public:
+  std::unique_ptr<QuicSpdyServerBase> CreateServer(
+      QuicSimpleServerBackend* backend,
+      std::unique_ptr<ProofSource> proof_source,
+      const quic::ParsedQuicVersionVector& supported_versions) override;
+
+ private:
+  QuicEpollServer epoll_server_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_EPOLL_SERVER_FACTORY_H_
diff --git a/quiche/quic/tools/quic_memory_cache_backend.cc b/quiche/quic/tools/quic_memory_cache_backend.cc
new file mode 100644
index 0000000..0e50139
--- /dev/null
+++ b/quiche/quic/tools/quic_memory_cache_backend.cc
@@ -0,0 +1,496 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_memory_cache_backend.h"
+
+#include <utility>
+
+#include "absl/strings/match.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/tools/web_transport_test_visitors.h"
+#include "quiche/common/platform/api/quiche_file_utils.h"
+#include "quiche/common/quiche_text_utils.h"
+
+using spdy::Http2HeaderBlock;
+using spdy::kV3LowestPriority;
+
+namespace quic {
+
+QuicMemoryCacheBackend::ResourceFile::ResourceFile(const std::string& file_name)
+    : file_name_(file_name) {}
+
+QuicMemoryCacheBackend::ResourceFile::~ResourceFile() = default;
+
+void QuicMemoryCacheBackend::ResourceFile::Read() {
+  absl::optional<std::string> maybe_file_contents =
+      quiche::ReadFileContents(file_name_);
+  if (!maybe_file_contents) {
+    QUIC_LOG(DFATAL) << "Failed to read file for the memory cache backend: "
+                     << file_name_;
+    return;
+  }
+  file_contents_ = *maybe_file_contents;
+
+  // First read the headers.
+  size_t start = 0;
+  while (start < file_contents_.length()) {
+    size_t pos = file_contents_.find('\n', start);
+    if (pos == std::string::npos) {
+      QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_;
+      return;
+    }
+    size_t len = pos - start;
+    // Support both dos and unix line endings for convenience.
+    if (file_contents_[pos - 1] == '\r') {
+      len -= 1;
+    }
+    absl::string_view line(file_contents_.data() + start, len);
+    start = pos + 1;
+    // Headers end with an empty line.
+    if (line.empty()) {
+      break;
+    }
+    // Extract the status from the HTTP first line.
+    if (line.substr(0, 4) == "HTTP") {
+      pos = line.find(' ');
+      if (pos == std::string::npos) {
+        QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: "
+                         << file_name_;
+        return;
+      }
+      spdy_headers_[":status"] = line.substr(pos + 1, 3);
+      continue;
+    }
+    // Headers are "key: value".
+    pos = line.find(": ");
+    if (pos == std::string::npos) {
+      QUIC_LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file_name_;
+      return;
+    }
+    spdy_headers_.AppendValueOrAddHeader(
+        quiche::QuicheTextUtils::ToLower(line.substr(0, pos)),
+        line.substr(pos + 2));
+  }
+
+  // The connection header is prohibited in HTTP/2.
+  spdy_headers_.erase("connection");
+
+  // Override the URL with the X-Original-Url header, if present.
+  auto it = spdy_headers_.find("x-original-url");
+  if (it != spdy_headers_.end()) {
+    x_original_url_ = it->second;
+    HandleXOriginalUrl();
+  }
+
+  // X-Push-URL header is a relatively quick way to support sever push
+  // in the toy server.  A production server should use link=preload
+  // stuff as described in https://w3c.github.io/preload/.
+  it = spdy_headers_.find("x-push-url");
+  if (it != spdy_headers_.end()) {
+    absl::string_view push_urls = it->second;
+    size_t start = 0;
+    while (start < push_urls.length()) {
+      size_t pos = push_urls.find('\0', start);
+      if (pos == std::string::npos) {
+        push_urls_.push_back(absl::string_view(push_urls.data() + start,
+                                               push_urls.length() - start));
+        break;
+      }
+      push_urls_.push_back(absl::string_view(push_urls.data() + start, pos));
+      start += pos + 1;
+    }
+  }
+
+  body_ = absl::string_view(file_contents_.data() + start,
+                            file_contents_.size() - start);
+}
+
+void QuicMemoryCacheBackend::ResourceFile::SetHostPathFromBase(
+    absl::string_view base) {
+  QUICHE_DCHECK(base[0] != '/') << base;
+  size_t path_start = base.find_first_of('/');
+  if (path_start == absl::string_view::npos) {
+    host_ = std::string(base);
+    path_ = "";
+    return;
+  }
+
+  host_ = std::string(base.substr(0, path_start));
+  size_t query_start = base.find_first_of(',');
+  if (query_start > 0) {
+    path_ = std::string(base.substr(path_start, query_start - 1));
+  } else {
+    path_ = std::string(base.substr(path_start));
+  }
+}
+
+absl::string_view QuicMemoryCacheBackend::ResourceFile::RemoveScheme(
+    absl::string_view url) {
+  if (absl::StartsWith(url, "https://")) {
+    url.remove_prefix(8);
+  } else if (absl::StartsWith(url, "http://")) {
+    url.remove_prefix(7);
+  }
+  return url;
+}
+
+void QuicMemoryCacheBackend::ResourceFile::HandleXOriginalUrl() {
+  absl::string_view url(x_original_url_);
+  SetHostPathFromBase(RemoveScheme(url));
+}
+
+const QuicBackendResponse* QuicMemoryCacheBackend::GetResponse(
+    absl::string_view host, absl::string_view path) const {
+  QuicWriterMutexLock lock(&response_mutex_);
+
+  auto it = responses_.find(GetKey(host, path));
+  if (it == responses_.end()) {
+    uint64_t ignored = 0;
+    if (generate_bytes_response_) {
+      if (absl::SimpleAtoi(absl::string_view(path.data() + 1, path.size() - 1),
+                           &ignored)) {
+        // The actual parsed length is ignored here and will be recomputed
+        // by the caller.
+        return generate_bytes_response_.get();
+      }
+    }
+    QUIC_DVLOG(1) << "Get response for resource failed: host " << host
+                  << " path " << path;
+    if (default_response_) {
+      return default_response_.get();
+    }
+    return nullptr;
+  }
+  return it->second.get();
+}
+
+using ServerPushInfo = QuicBackendResponse::ServerPushInfo;
+using SpecialResponseType = QuicBackendResponse::SpecialResponseType;
+
+void QuicMemoryCacheBackend::AddSimpleResponse(absl::string_view host,
+                                               absl::string_view path,
+                                               int response_code,
+                                               absl::string_view body) {
+  Http2HeaderBlock response_headers;
+  response_headers[":status"] = absl::StrCat(response_code);
+  response_headers["content-length"] = absl::StrCat(body.length());
+  AddResponse(host, path, std::move(response_headers), body);
+}
+
+void QuicMemoryCacheBackend::AddSimpleResponseWithServerPushResources(
+    absl::string_view host, absl::string_view path, int response_code,
+    absl::string_view body, std::list<ServerPushInfo> push_resources) {
+  AddSimpleResponse(host, path, response_code, body);
+  MaybeAddServerPushResources(host, path, push_resources);
+}
+
+void QuicMemoryCacheBackend::AddDefaultResponse(QuicBackendResponse* response) {
+  QuicWriterMutexLock lock(&response_mutex_);
+  default_response_.reset(response);
+}
+
+void QuicMemoryCacheBackend::AddResponse(absl::string_view host,
+                                         absl::string_view path,
+                                         Http2HeaderBlock response_headers,
+                                         absl::string_view response_body) {
+  AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
+                  std::move(response_headers), response_body,
+                  Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
+}
+
+void QuicMemoryCacheBackend::AddResponse(absl::string_view host,
+                                         absl::string_view path,
+                                         Http2HeaderBlock response_headers,
+                                         absl::string_view response_body,
+                                         Http2HeaderBlock response_trailers) {
+  AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
+                  std::move(response_headers), response_body,
+                  std::move(response_trailers),
+                  std::vector<spdy::Http2HeaderBlock>());
+}
+
+void QuicMemoryCacheBackend::AddResponseWithEarlyHints(
+    absl::string_view host, absl::string_view path,
+    spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
+    const std::vector<spdy::Http2HeaderBlock>& early_hints) {
+  AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
+                  std::move(response_headers), response_body,
+                  Http2HeaderBlock(), early_hints);
+}
+
+void QuicMemoryCacheBackend::AddSpecialResponse(
+    absl::string_view host, absl::string_view path,
+    SpecialResponseType response_type) {
+  AddResponseImpl(host, path, response_type, Http2HeaderBlock(), "",
+                  Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
+}
+
+void QuicMemoryCacheBackend::AddSpecialResponse(
+    absl::string_view host, absl::string_view path,
+    spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
+    SpecialResponseType response_type) {
+  AddResponseImpl(host, path, response_type, std::move(response_headers),
+                  response_body, Http2HeaderBlock(),
+                  std::vector<spdy::Http2HeaderBlock>());
+}
+
+QuicMemoryCacheBackend::QuicMemoryCacheBackend() : cache_initialized_(false) {}
+
+bool QuicMemoryCacheBackend::InitializeBackend(
+    const std::string& cache_directory) {
+  if (cache_directory.empty()) {
+    QUIC_BUG(quic_bug_10932_1) << "cache_directory must not be empty.";
+    return false;
+  }
+  QUIC_LOG(INFO)
+      << "Attempting to initialize QuicMemoryCacheBackend from directory: "
+      << cache_directory;
+  std::vector<std::string> files;
+  if (!quiche::EnumerateDirectoryRecursively(cache_directory, files)) {
+    QUIC_BUG(QuicMemoryCacheBackend unreadable directory)
+        << "Can't read QuicMemoryCacheBackend directory: " << cache_directory;
+    return false;
+  }
+  std::list<std::unique_ptr<ResourceFile>> resource_files;
+  for (const auto& filename : files) {
+    std::unique_ptr<ResourceFile> resource_file(new ResourceFile(filename));
+
+    // Tease apart filename into host and path.
+    std::string base(resource_file->file_name());
+    // Transform windows path separators to URL path separators.
+    for (size_t i = 0; i < base.length(); ++i) {
+      if (base[i] == '\\') {
+        base[i] = '/';
+      }
+    }
+    base.erase(0, cache_directory.length());
+    if (base[0] == '/') {
+      base.erase(0, 1);
+    }
+
+    resource_file->SetHostPathFromBase(base);
+    resource_file->Read();
+
+    AddResponse(resource_file->host(), resource_file->path(),
+                resource_file->spdy_headers().Clone(), resource_file->body());
+
+    resource_files.push_back(std::move(resource_file));
+  }
+
+  for (const auto& resource_file : resource_files) {
+    std::list<ServerPushInfo> push_resources;
+    for (const auto& push_url : resource_file->push_urls()) {
+      QuicUrl url(push_url);
+      const QuicBackendResponse* response = GetResponse(url.host(), url.path());
+      if (!response) {
+        QUIC_BUG(quic_bug_10932_2)
+            << "Push URL '" << push_url << "' not found.";
+        return false;
+      }
+      push_resources.push_back(ServerPushInfo(url, response->headers().Clone(),
+                                              kV3LowestPriority,
+                                              (std::string(response->body()))));
+    }
+    MaybeAddServerPushResources(resource_file->host(), resource_file->path(),
+                                push_resources);
+  }
+
+  cache_initialized_ = true;
+  return true;
+}
+
+void QuicMemoryCacheBackend::GenerateDynamicResponses() {
+  QuicWriterMutexLock lock(&response_mutex_);
+  // Add a generate bytes response.
+  spdy::Http2HeaderBlock response_headers;
+  response_headers[":status"] = "200";
+  generate_bytes_response_ = std::make_unique<QuicBackendResponse>();
+  generate_bytes_response_->set_headers(std::move(response_headers));
+  generate_bytes_response_->set_response_type(
+      QuicBackendResponse::GENERATE_BYTES);
+}
+
+void QuicMemoryCacheBackend::EnableWebTransport() {
+  enable_webtransport_ = true;
+}
+
+bool QuicMemoryCacheBackend::IsBackendInitialized() const {
+  return cache_initialized_;
+}
+
+void QuicMemoryCacheBackend::FetchResponseFromBackend(
+    const Http2HeaderBlock& request_headers,
+    const std::string& /*request_body*/,
+    QuicSimpleServerBackend::RequestHandler* quic_stream) {
+  const QuicBackendResponse* quic_response = nullptr;
+  // Find response in cache. If not found, send error response.
+  auto authority = request_headers.find(":authority");
+  auto path = request_headers.find(":path");
+  if (authority != request_headers.end() && path != request_headers.end()) {
+    quic_response = GetResponse(authority->second, path->second);
+  }
+
+  std::string request_url;
+  if (authority != request_headers.end()) {
+    request_url = std::string(authority->second);
+  }
+  if (path != request_headers.end()) {
+    request_url += std::string(path->second);
+  }
+  QUIC_DVLOG(1)
+      << "Fetching QUIC response from backend in-memory cache for url "
+      << request_url;
+  quic_stream->OnResponseBackendComplete(quic_response);
+}
+
+// The memory cache does not have a per-stream handler
+void QuicMemoryCacheBackend::CloseBackendResponseStream(
+    QuicSimpleServerBackend::RequestHandler* /*quic_stream*/) {}
+
+std::list<ServerPushInfo> QuicMemoryCacheBackend::GetServerPushResources(
+    std::string request_url) {
+  QuicWriterMutexLock lock(&response_mutex_);
+
+  std::list<ServerPushInfo> resources;
+  auto resource_range = server_push_resources_.equal_range(request_url);
+  for (auto it = resource_range.first; it != resource_range.second; ++it) {
+    resources.push_back(it->second);
+  }
+  QUIC_DVLOG(1) << "Found " << resources.size() << " push resources for "
+                << request_url;
+  return resources;
+}
+
+QuicMemoryCacheBackend::WebTransportResponse
+QuicMemoryCacheBackend::ProcessWebTransportRequest(
+    const spdy::Http2HeaderBlock& request_headers,
+    WebTransportSession* session) {
+  if (!SupportsWebTransport()) {
+    return QuicSimpleServerBackend::ProcessWebTransportRequest(request_headers,
+                                                               session);
+  }
+
+  auto path_it = request_headers.find(":path");
+  if (path_it == request_headers.end()) {
+    WebTransportResponse response;
+    response.response_headers[":status"] = "400";
+    return response;
+  }
+  absl::string_view path = path_it->second;
+  if (path == "/echo") {
+    WebTransportResponse response;
+    response.response_headers[":status"] = "200";
+    response.visitor =
+        std::make_unique<EchoWebTransportSessionVisitor>(session);
+    return response;
+  }
+
+  WebTransportResponse response;
+  response.response_headers[":status"] = "404";
+  return response;
+}
+
+QuicMemoryCacheBackend::~QuicMemoryCacheBackend() {
+  {
+    QuicWriterMutexLock lock(&response_mutex_);
+    responses_.clear();
+  }
+}
+
+void QuicMemoryCacheBackend::AddResponseImpl(
+    absl::string_view host, absl::string_view path,
+    SpecialResponseType response_type, Http2HeaderBlock response_headers,
+    absl::string_view response_body, Http2HeaderBlock response_trailers,
+    const std::vector<spdy::Http2HeaderBlock>& early_hints) {
+  QuicWriterMutexLock lock(&response_mutex_);
+
+  QUICHE_DCHECK(!host.empty())
+      << "Host must be populated, e.g. \"www.google.com\"";
+  std::string key = GetKey(host, path);
+  if (responses_.contains(key)) {
+    QUIC_BUG(quic_bug_10932_3)
+        << "Response for '" << key << "' already exists!";
+    return;
+  }
+  auto new_response = std::make_unique<QuicBackendResponse>();
+  new_response->set_response_type(response_type);
+  new_response->set_headers(std::move(response_headers));
+  new_response->set_body(response_body);
+  new_response->set_trailers(std::move(response_trailers));
+  for (auto& headers : early_hints) {
+    new_response->AddEarlyHints(headers);
+  }
+  QUIC_DVLOG(1) << "Add response with key " << key;
+  responses_[key] = std::move(new_response);
+}
+
+std::string QuicMemoryCacheBackend::GetKey(absl::string_view host,
+                                           absl::string_view path) const {
+  std::string host_string = std::string(host);
+  size_t port = host_string.find(':');
+  if (port != std::string::npos)
+    host_string = std::string(host_string.c_str(), port);
+  return host_string + std::string(path);
+}
+
+void QuicMemoryCacheBackend::MaybeAddServerPushResources(
+    absl::string_view request_host, absl::string_view request_path,
+    std::list<ServerPushInfo> push_resources) {
+  std::string request_url = GetKey(request_host, request_path);
+
+  for (const auto& push_resource : push_resources) {
+    if (PushResourceExistsInCache(request_url, push_resource)) {
+      continue;
+    }
+
+    QUIC_DVLOG(1) << "Add request-resource association: request url "
+                  << request_url << " push url "
+                  << push_resource.request_url.ToString()
+                  << " response headers "
+                  << push_resource.headers.DebugString();
+    {
+      QuicWriterMutexLock lock(&response_mutex_);
+      server_push_resources_.insert(std::make_pair(request_url, push_resource));
+    }
+    std::string host = push_resource.request_url.host();
+    if (host.empty()) {
+      host = std::string(request_host);
+    }
+    std::string path = push_resource.request_url.path();
+    bool found_existing_response = false;
+    {
+      QuicWriterMutexLock lock(&response_mutex_);
+      found_existing_response = responses_.contains(GetKey(host, path));
+    }
+    if (!found_existing_response) {
+      // Add a server push response to responses map, if it is not in the map.
+      absl::string_view body = push_resource.body;
+      QUIC_DVLOG(1) << "Add response for push resource: host " << host
+                    << " path " << path;
+      AddResponse(host, path, push_resource.headers.Clone(), body);
+    }
+  }
+}
+
+bool QuicMemoryCacheBackend::PushResourceExistsInCache(
+    std::string original_request_url, ServerPushInfo resource) {
+  QuicWriterMutexLock lock(&response_mutex_);
+  auto resource_range =
+      server_push_resources_.equal_range(original_request_url);
+  for (auto it = resource_range.first; it != resource_range.second; ++it) {
+    ServerPushInfo push_resource = it->second;
+    if (push_resource.request_url.ToString() ==
+        resource.request_url.ToString()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_memory_cache_backend.h b/quiche/quic/tools/quic_memory_cache_backend.h
new file mode 100644
index 0000000..986badb
--- /dev/null
+++ b/quiche/quic/tools/quic_memory_cache_backend.h
@@ -0,0 +1,217 @@
+// Copyright (c) 2012 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_
+#define QUICHE_QUIC_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_
+
+#include <list>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/platform/api/quic_mutex.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+// In-memory cache for HTTP responses.
+// Reads from disk cache generated by:
+// `wget -p --save_headers <url>`
+class QuicMemoryCacheBackend : public QuicSimpleServerBackend {
+ public:
+  // Class to manage loading a resource file into memory.  There are
+  // two uses: called by InitializeBackend to load resources
+  // from files, and recursively called when said resources specify
+  // server push associations.
+  class ResourceFile {
+   public:
+    explicit ResourceFile(const std::string& file_name);
+    ResourceFile(const ResourceFile&) = delete;
+    ResourceFile& operator=(const ResourceFile&) = delete;
+    virtual ~ResourceFile();
+
+    void Read();
+
+    // |base| is |file_name_| with |cache_directory| prefix stripped.
+    void SetHostPathFromBase(absl::string_view base);
+
+    const std::string& file_name() { return file_name_; }
+
+    absl::string_view host() { return host_; }
+
+    absl::string_view path() { return path_; }
+
+    const spdy::Http2HeaderBlock& spdy_headers() { return spdy_headers_; }
+
+    absl::string_view body() { return body_; }
+
+    const std::vector<absl::string_view>& push_urls() { return push_urls_; }
+
+   private:
+    void HandleXOriginalUrl();
+    absl::string_view RemoveScheme(absl::string_view url);
+
+    std::string file_name_;
+    std::string file_contents_;
+    absl::string_view body_;
+    spdy::Http2HeaderBlock spdy_headers_;
+    absl::string_view x_original_url_;
+    std::vector<absl::string_view> push_urls_;
+    std::string host_;
+    std::string path_;
+  };
+
+  QuicMemoryCacheBackend();
+  QuicMemoryCacheBackend(const QuicMemoryCacheBackend&) = delete;
+  QuicMemoryCacheBackend& operator=(const QuicMemoryCacheBackend&) = delete;
+  ~QuicMemoryCacheBackend() override;
+
+  // Retrieve a response from this cache for a given host and path..
+  // If no appropriate response exists, nullptr is returned.
+  const QuicBackendResponse* GetResponse(absl::string_view host,
+                                         absl::string_view path) const;
+
+  // Adds a simple response to the cache.  The response headers will
+  // only contain the "content-length" header with the length of |body|.
+  void AddSimpleResponse(absl::string_view host,
+                         absl::string_view path,
+                         int response_code,
+                         absl::string_view body);
+
+  // Add a simple response to the cache as AddSimpleResponse() does, and add
+  // some server push resources(resource path, corresponding response status and
+  // path) associated with it.
+  // Push resource implicitly come from the same host.
+  // TODO(b/171463363): Remove.
+  void AddSimpleResponseWithServerPushResources(
+      absl::string_view host,
+      absl::string_view path,
+      int response_code,
+      absl::string_view body,
+      std::list<QuicBackendResponse::ServerPushInfo> push_resources);
+
+  // Add a response to the cache.
+  void AddResponse(absl::string_view host,
+                   absl::string_view path,
+                   spdy::Http2HeaderBlock response_headers,
+                   absl::string_view response_body);
+
+  // Add a response, with trailers, to the cache.
+  void AddResponse(absl::string_view host,
+                   absl::string_view path,
+                   spdy::Http2HeaderBlock response_headers,
+                   absl::string_view response_body,
+                   spdy::Http2HeaderBlock response_trailers);
+
+  // Add a response, with 103 Early Hints, to the cache.
+  void AddResponseWithEarlyHints(
+      absl::string_view host,
+      absl::string_view path,
+      spdy::Http2HeaderBlock response_headers,
+      absl::string_view response_body,
+      const std::vector<spdy::Http2HeaderBlock>& early_hints);
+
+  // Simulate a special behavior at a particular path.
+  void AddSpecialResponse(
+      absl::string_view host,
+      absl::string_view path,
+      QuicBackendResponse::SpecialResponseType response_type);
+
+  void AddSpecialResponse(
+      absl::string_view host,
+      absl::string_view path,
+      spdy::Http2HeaderBlock response_headers,
+      absl::string_view response_body,
+      QuicBackendResponse::SpecialResponseType response_type);
+
+  // Sets a default response in case of cache misses.  Takes ownership of
+  // 'response'.
+  void AddDefaultResponse(QuicBackendResponse* response);
+
+  // Once called, URLs which have a numeric path will send a dynamically
+  // generated response of that many bytes.
+  void GenerateDynamicResponses();
+
+  void EnableWebTransport();
+
+  // Find all the server push resources associated with |request_url|.
+  // TODO(b/171463363): Remove.
+  std::list<QuicBackendResponse::ServerPushInfo> GetServerPushResources(
+      std::string request_url);
+
+  // Implements the functions for interface QuicSimpleServerBackend
+  // |cache_cirectory| can be generated using `wget -p --save-headers <url>`.
+  bool InitializeBackend(const std::string& cache_directory) override;
+  bool IsBackendInitialized() const override;
+  void FetchResponseFromBackend(
+      const spdy::Http2HeaderBlock& request_headers,
+      const std::string& request_body,
+      QuicSimpleServerBackend::RequestHandler* quic_stream) override;
+  void CloseBackendResponseStream(
+      QuicSimpleServerBackend::RequestHandler* quic_stream) override;
+  WebTransportResponse ProcessWebTransportRequest(
+      const spdy::Http2HeaderBlock& request_headers,
+      WebTransportSession* session) override;
+  bool SupportsWebTransport() override { return enable_webtransport_; }
+
+ private:
+  void AddResponseImpl(absl::string_view host,
+                       absl::string_view path,
+                       QuicBackendResponse::SpecialResponseType response_type,
+                       spdy::Http2HeaderBlock response_headers,
+                       absl::string_view response_body,
+                       spdy::Http2HeaderBlock response_trailers,
+                       const std::vector<spdy::Http2HeaderBlock>& early_hints);
+
+  std::string GetKey(absl::string_view host, absl::string_view path) const;
+
+  // Add some server push urls with given responses for specified
+  // request if these push resources are not associated with this request yet.
+  // TODO(b/171463363): Remove.
+  void MaybeAddServerPushResources(
+      absl::string_view request_host,
+      absl::string_view request_path,
+      std::list<QuicBackendResponse::ServerPushInfo> push_resources);
+
+  // Check if push resource(push_host/push_path) associated with given request
+  // url already exists in server push map.
+  // TODO(b/171463363): Remove.
+  bool PushResourceExistsInCache(std::string original_request_url,
+                                 QuicBackendResponse::ServerPushInfo resource);
+
+  // Cached responses.
+  absl::flat_hash_map<std::string, std::unique_ptr<QuicBackendResponse>>
+      responses_ QUIC_GUARDED_BY(response_mutex_);
+
+  // The default response for cache misses, if set.
+  std::unique_ptr<QuicBackendResponse> default_response_
+      QUIC_GUARDED_BY(response_mutex_);
+
+  // The generate bytes response, if set.
+  std::unique_ptr<QuicBackendResponse> generate_bytes_response_
+      QUIC_GUARDED_BY(response_mutex_);
+
+  // A map from request URL to associated server push responses (if any).
+  // TODO(b/171463363): Remove.
+  std::multimap<std::string, QuicBackendResponse::ServerPushInfo>
+      server_push_resources_ QUIC_GUARDED_BY(response_mutex_);
+
+  // Protects against concurrent access from test threads setting responses, and
+  // server threads accessing those responses.
+  mutable QuicMutex response_mutex_;
+  bool cache_initialized_;
+
+  bool enable_webtransport_ = false;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_
diff --git a/quiche/quic/tools/quic_memory_cache_backend_test.cc b/quiche/quic/tools/quic_memory_cache_backend_test.cc
new file mode 100644
index 0000000..bb5e675
--- /dev/null
+++ b/quiche/quic/tools/quic_memory_cache_backend_test.cc
@@ -0,0 +1,291 @@
+// Copyright 2013 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 "quiche/quic/tools/quic_memory_cache_backend.h"
+
+#include <vector>
+
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/common/platform/api/quiche_file_utils.h"
+
+namespace quic {
+namespace test {
+
+namespace {
+using Response = QuicBackendResponse;
+using ServerPushInfo = QuicBackendResponse::ServerPushInfo;
+}  // namespace
+
+class QuicMemoryCacheBackendTest : public QuicTest {
+ protected:
+  void CreateRequest(std::string host, std::string path,
+                     spdy::Http2HeaderBlock* headers) {
+    (*headers)[":method"] = "GET";
+    (*headers)[":path"] = path;
+    (*headers)[":authority"] = host;
+    (*headers)[":scheme"] = "https";
+  }
+
+  std::string CacheDirectory() { return QuicGetTestMemoryCachePath(); }
+
+  QuicMemoryCacheBackend cache_;
+};
+
+TEST_F(QuicMemoryCacheBackendTest, GetResponseNoMatch) {
+  const Response* response =
+      cache_.GetResponse("mail.google.com", "/index.html");
+  ASSERT_FALSE(response);
+}
+
+TEST_F(QuicMemoryCacheBackendTest, AddSimpleResponseGetResponse) {
+  std::string response_body("hello response");
+  cache_.AddSimpleResponse("www.google.com", "/", 200, response_body);
+
+  spdy::Http2HeaderBlock request_headers;
+  CreateRequest("www.google.com", "/", &request_headers);
+  const Response* response = cache_.GetResponse("www.google.com", "/");
+  ASSERT_TRUE(response);
+  ASSERT_TRUE(response->headers().contains(":status"));
+  EXPECT_EQ("200", response->headers().find(":status")->second);
+  EXPECT_EQ(response_body.size(), response->body().length());
+}
+
+TEST_F(QuicMemoryCacheBackendTest, AddResponse) {
+  const std::string kRequestHost = "www.foo.com";
+  const std::string kRequestPath = "/";
+  const std::string kResponseBody("hello response");
+
+  spdy::Http2HeaderBlock response_headers;
+  response_headers[":status"] = "200";
+  response_headers["content-length"] = absl::StrCat(kResponseBody.size());
+
+  spdy::Http2HeaderBlock response_trailers;
+  response_trailers["key-1"] = "value-1";
+  response_trailers["key-2"] = "value-2";
+  response_trailers["key-3"] = "value-3";
+
+  cache_.AddResponse(kRequestHost, "/", response_headers.Clone(), kResponseBody,
+                     response_trailers.Clone());
+
+  const Response* response = cache_.GetResponse(kRequestHost, kRequestPath);
+  EXPECT_EQ(response->headers(), response_headers);
+  EXPECT_EQ(response->body(), kResponseBody);
+  EXPECT_EQ(response->trailers(), response_trailers);
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_ReadsCacheDir DISABLED_ReadsCacheDir
+#else
+#define MAYBE_ReadsCacheDir ReadsCacheDir
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_ReadsCacheDir) {
+  cache_.InitializeBackend(CacheDirectory());
+  const Response* response =
+      cache_.GetResponse("test.example.com", "/index.html");
+  ASSERT_TRUE(response);
+  ASSERT_TRUE(response->headers().contains(":status"));
+  EXPECT_EQ("200", response->headers().find(":status")->second);
+  // Connection headers are not valid in HTTP/2.
+  EXPECT_FALSE(response->headers().contains("connection"));
+  EXPECT_LT(0U, response->body().length());
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_ReadsCacheDirWithServerPushResource \
+  DISABLED_ReadsCacheDirWithServerPushResource
+#else
+#define MAYBE_ReadsCacheDirWithServerPushResource \
+  ReadsCacheDirWithServerPushResource
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_ReadsCacheDirWithServerPushResource) {
+  cache_.InitializeBackend(CacheDirectory() + "_with_push");
+  std::list<ServerPushInfo> resources =
+      cache_.GetServerPushResources("test.example.com/");
+  ASSERT_EQ(1UL, resources.size());
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_ReadsCacheDirWithServerPushResources \
+  DISABLED_ReadsCacheDirWithServerPushResources
+#else
+#define MAYBE_ReadsCacheDirWithServerPushResources \
+  ReadsCacheDirWithServerPushResources
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_ReadsCacheDirWithServerPushResources) {
+  cache_.InitializeBackend(CacheDirectory() + "_with_push");
+  std::list<ServerPushInfo> resources =
+      cache_.GetServerPushResources("test.example.com/index2.html");
+  ASSERT_EQ(2UL, resources.size());
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_UsesOriginalUrl DISABLED_UsesOriginalUrl
+#else
+#define MAYBE_UsesOriginalUrl UsesOriginalUrl
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_UsesOriginalUrl) {
+  cache_.InitializeBackend(CacheDirectory());
+  const Response* response =
+      cache_.GetResponse("test.example.com", "/site_map.html");
+  ASSERT_TRUE(response);
+  ASSERT_TRUE(response->headers().contains(":status"));
+  EXPECT_EQ("200", response->headers().find(":status")->second);
+  // Connection headers are not valid in HTTP/2.
+  EXPECT_FALSE(response->headers().contains("connection"));
+  EXPECT_LT(0U, response->body().length());
+}
+
+// TODO(crbug.com/1249712) This test is failing on iOS.
+#if defined(OS_IOS)
+#define MAYBE_UsesOriginalUrlOnly DISABLED_UsesOriginalUrlOnly
+#else
+#define MAYBE_UsesOriginalUrlOnly UsesOriginalUrlOnly
+#endif
+TEST_F(QuicMemoryCacheBackendTest, MAYBE_UsesOriginalUrlOnly) {
+  // Tests that if the URL cannot be inferred correctly from the path
+  // because the directory does not include the hostname, that the
+  // X-Original-Url header's value will be used.
+  std::string dir;
+  std::string path = "map.html";
+  std::vector<std::string> files;
+  ASSERT_TRUE(quiche::EnumerateDirectoryRecursively(CacheDirectory(), files));
+  for (const std::string& file : files) {
+    if (absl::EndsWithIgnoreCase(file, "map.html")) {
+      dir = file;
+      dir.erase(dir.length() - path.length() - 1);
+      break;
+    }
+  }
+  ASSERT_NE("", dir);
+
+  cache_.InitializeBackend(dir);
+  const Response* response =
+      cache_.GetResponse("test.example.com", "/site_map.html");
+  ASSERT_TRUE(response);
+  ASSERT_TRUE(response->headers().contains(":status"));
+  EXPECT_EQ("200", response->headers().find(":status")->second);
+  // Connection headers are not valid in HTTP/2.
+  EXPECT_FALSE(response->headers().contains("connection"));
+  EXPECT_LT(0U, response->body().length());
+}
+
+TEST_F(QuicMemoryCacheBackendTest, DefaultResponse) {
+  // Verify GetResponse returns nullptr when no default is set.
+  const Response* response = cache_.GetResponse("www.google.com", "/");
+  ASSERT_FALSE(response);
+
+  // Add a default response.
+  spdy::Http2HeaderBlock response_headers;
+  response_headers[":status"] = "200";
+  response_headers["content-length"] = "0";
+  Response* default_response = new Response;
+  default_response->set_headers(std::move(response_headers));
+  cache_.AddDefaultResponse(default_response);
+
+  // Now we should get the default response for the original request.
+  response = cache_.GetResponse("www.google.com", "/");
+  ASSERT_TRUE(response);
+  ASSERT_TRUE(response->headers().contains(":status"));
+  EXPECT_EQ("200", response->headers().find(":status")->second);
+
+  // Now add a set response for / and make sure it is returned
+  cache_.AddSimpleResponse("www.google.com", "/", 302, "");
+  response = cache_.GetResponse("www.google.com", "/");
+  ASSERT_TRUE(response);
+  ASSERT_TRUE(response->headers().contains(":status"));
+  EXPECT_EQ("302", response->headers().find(":status")->second);
+
+  // We should get the default response for other requests.
+  response = cache_.GetResponse("www.google.com", "/asd");
+  ASSERT_TRUE(response);
+  ASSERT_TRUE(response->headers().contains(":status"));
+  EXPECT_EQ("200", response->headers().find(":status")->second);
+}
+
+TEST_F(QuicMemoryCacheBackendTest, AddSimpleResponseWithServerPushResources) {
+  std::string request_host = "www.foo.com";
+  std::string response_body("hello response");
+  const size_t kNumResources = 5;
+  int NumResources = 5;
+  std::list<ServerPushInfo> push_resources;
+  std::string scheme = "http";
+  for (int i = 0; i < NumResources; ++i) {
+    std::string path = absl::StrCat("/server_push_src", i);
+    std::string url = scheme + "://" + request_host + path;
+    QuicUrl resource_url(url);
+    std::string body =
+        absl::StrCat("This is server push response body for ", path);
+    spdy::Http2HeaderBlock response_headers;
+    response_headers[":status"] = "200";
+    response_headers["content-length"] = absl::StrCat(body.size());
+    push_resources.push_back(
+        ServerPushInfo(resource_url, response_headers.Clone(), i, body));
+  }
+
+  cache_.AddSimpleResponseWithServerPushResources(
+      request_host, "/", 200, response_body, push_resources);
+
+  std::string request_url = request_host + "/";
+  std::list<ServerPushInfo> resources =
+      cache_.GetServerPushResources(request_url);
+  ASSERT_EQ(kNumResources, resources.size());
+  for (const auto& push_resource : push_resources) {
+    ServerPushInfo resource = resources.front();
+    EXPECT_EQ(resource.request_url.ToString(),
+              push_resource.request_url.ToString());
+    EXPECT_EQ(resource.priority, push_resource.priority);
+    resources.pop_front();
+  }
+}
+
+TEST_F(QuicMemoryCacheBackendTest, GetServerPushResourcesAndPushResponses) {
+  std::string request_host = "www.foo.com";
+  std::string response_body("hello response");
+  const size_t kNumResources = 4;
+  int NumResources = 4;
+  std::string scheme = "http";
+  std::string push_response_status[kNumResources] = {"200", "200", "301",
+                                                     "404"};
+  std::list<ServerPushInfo> push_resources;
+  for (int i = 0; i < NumResources; ++i) {
+    std::string path = absl::StrCat("/server_push_src", i);
+    std::string url = scheme + "://" + request_host + path;
+    QuicUrl resource_url(url);
+    std::string body = "This is server push response body for " + path;
+    spdy::Http2HeaderBlock response_headers;
+    response_headers[":status"] = push_response_status[i];
+    response_headers["content-length"] = absl::StrCat(body.size());
+    push_resources.push_back(
+        ServerPushInfo(resource_url, response_headers.Clone(), i, body));
+  }
+  cache_.AddSimpleResponseWithServerPushResources(
+      request_host, "/", 200, response_body, push_resources);
+  std::string request_url = request_host + "/";
+  std::list<ServerPushInfo> resources =
+      cache_.GetServerPushResources(request_url);
+  ASSERT_EQ(kNumResources, resources.size());
+  int i = 0;
+  for (const auto& push_resource : push_resources) {
+    QuicUrl url = resources.front().request_url;
+    std::string host = url.host();
+    std::string path = url.path();
+    const Response* response = cache_.GetResponse(host, path);
+    ASSERT_TRUE(response);
+    ASSERT_TRUE(response->headers().contains(":status"));
+    EXPECT_EQ(push_response_status[i++],
+              response->headers().find(":status")->second);
+    EXPECT_EQ(push_resource.body, response->body());
+    resources.pop_front();
+  }
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_packet_printer_bin.cc b/quiche/quic/tools/quic_packet_printer_bin.cc
new file mode 100644
index 0000000..8867e44
--- /dev/null
+++ b/quiche/quic/tools/quic_packet_printer_bin.cc
@@ -0,0 +1,283 @@
+// Copyright (c) 2012 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.
+
+// clang-format off
+
+// Dumps out the decryptable contents of a QUIC packet in a human-readable way.
+// If the packet is null encrypted, this will dump full packet contents.
+// Otherwise it will dump the header, and fail with an error that the
+// packet is undecryptable.
+//
+// Usage: quic_packet_printer server|client <hex dump of packet>
+//
+// Example input:
+// quic_packet_printer server 0c6b810308320f24c004a939a38a2e3fd6ca589917f200400201b80b0100501c0700060003023d0000001c00556e656e637279707465642073747265616d2064617461207365656e
+//
+// Example output:
+// OnPacket
+// OnUnauthenticatedPublicHeader
+// OnUnauthenticatedHeader: { connection_id: 13845207862000976235, connection_id_length:8, packet_number_length:1, multipath_flag: 0, reset_flag: 0, version_flag: 0, path_id: , packet_number: 4 }
+// OnDecryptedPacket
+// OnPacketHeader
+// OnAckFrame:  largest_observed: 1 ack_delay_time: 3000 missing_packets: [  ] is_truncated: 0 received_packets: [ 1 at 466016  ]
+// OnStopWaitingFrame
+// OnConnectionCloseFrame: error_code { 61 } error_details { Unencrypted stream data seen }
+
+// clang-format on
+
+#include <iostream>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/quic_framer.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/quiche_text_utils.h"
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, quic_version, "",
+                                "If set, specify the QUIC version to use.");
+
+namespace quic {
+
+class QuicPacketPrinter : public QuicFramerVisitorInterface {
+ public:
+  explicit QuicPacketPrinter(QuicFramer* framer) : framer_(framer) {}
+
+  void OnError(QuicFramer* framer) override {
+    std::cerr << "OnError: " << QuicErrorCodeToString(framer->error())
+              << " detail: " << framer->detailed_error() << "\n";
+  }
+  bool OnProtocolVersionMismatch(ParsedQuicVersion received_version) override {
+    framer_->set_version(received_version);
+    std::cerr << "OnProtocolVersionMismatch: "
+              << ParsedQuicVersionToString(received_version) << "\n";
+    return true;
+  }
+  void OnPacket() override { std::cerr << "OnPacket\n"; }
+  void OnPublicResetPacket(const QuicPublicResetPacket& /*packet*/) override {
+    std::cerr << "OnPublicResetPacket\n";
+  }
+  void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& /*packet*/) override {
+    std::cerr << "OnVersionNegotiationPacket\n";
+  }
+  void OnRetryPacket(QuicConnectionId /*original_connection_id*/,
+                     QuicConnectionId /*new_connection_id*/,
+                     absl::string_view /*retry_token*/,
+                     absl::string_view /*retry_integrity_tag*/,
+                     absl::string_view /*retry_without_tag*/) override {
+    std::cerr << "OnRetryPacket\n";
+  }
+  bool OnUnauthenticatedPublicHeader(
+      const QuicPacketHeader& /*header*/) override {
+    std::cerr << "OnUnauthenticatedPublicHeader\n";
+    return true;
+  }
+  bool OnUnauthenticatedHeader(const QuicPacketHeader& header) override {
+    std::cerr << "OnUnauthenticatedHeader: " << header;
+    return true;
+  }
+  void OnDecryptedPacket(size_t /*length*/, EncryptionLevel level) override {
+    // This only currently supports "decrypting" null encrypted packets.
+    QUICHE_DCHECK_EQ(ENCRYPTION_INITIAL, level);
+    std::cerr << "OnDecryptedPacket\n";
+  }
+  bool OnPacketHeader(const QuicPacketHeader& /*header*/) override {
+    std::cerr << "OnPacketHeader\n";
+    return true;
+  }
+  void OnCoalescedPacket(const QuicEncryptedPacket& /*packet*/) override {
+    std::cerr << "OnCoalescedPacket\n";
+  }
+  void OnUndecryptablePacket(const QuicEncryptedPacket& /*packet*/,
+                             EncryptionLevel /*decryption_level*/,
+                             bool /*has_decryption_key*/) override {
+    std::cerr << "OnUndecryptablePacket\n";
+  }
+  bool OnStreamFrame(const QuicStreamFrame& frame) override {
+    std::cerr << "OnStreamFrame: " << frame;
+    std::cerr << "         data: { "
+              << absl::BytesToHexString(
+                     absl::string_view(frame.data_buffer, frame.data_length))
+              << " }\n";
+    return true;
+  }
+  bool OnCryptoFrame(const QuicCryptoFrame& frame) override {
+    std::cerr << "OnCryptoFrame: " << frame;
+    std::cerr << "         data: { "
+              << absl::BytesToHexString(
+                     absl::string_view(frame.data_buffer, frame.data_length))
+              << " }\n";
+    return true;
+  }
+  bool OnAckFrameStart(QuicPacketNumber largest_acked,
+                       QuicTime::Delta /*ack_delay_time*/) override {
+    std::cerr << "OnAckFrameStart, largest_acked: " << largest_acked;
+    return true;
+  }
+  bool OnAckRange(QuicPacketNumber start, QuicPacketNumber end) override {
+    std::cerr << "OnAckRange: [" << start << ", " << end << ")";
+    return true;
+  }
+  bool OnAckTimestamp(QuicPacketNumber packet_number,
+                      QuicTime timestamp) override {
+    std::cerr << "OnAckTimestamp: [" << packet_number << ", "
+              << timestamp.ToDebuggingValue() << ")";
+    return true;
+  }
+  bool OnAckFrameEnd(QuicPacketNumber start) override {
+    std::cerr << "OnAckFrameEnd, start: " << start;
+    return true;
+  }
+  bool OnStopWaitingFrame(const QuicStopWaitingFrame& frame) override {
+    std::cerr << "OnStopWaitingFrame: " << frame;
+    return true;
+  }
+  bool OnPaddingFrame(const QuicPaddingFrame& frame) override {
+    std::cerr << "OnPaddingFrame: " << frame;
+    return true;
+  }
+  bool OnPingFrame(const QuicPingFrame& frame) override {
+    std::cerr << "OnPingFrame: " << frame;
+    return true;
+  }
+  bool OnRstStreamFrame(const QuicRstStreamFrame& frame) override {
+    std::cerr << "OnRstStreamFrame: " << frame;
+    return true;
+  }
+  bool OnConnectionCloseFrame(const QuicConnectionCloseFrame& frame) override {
+    // The frame printout will indicate whether it's a Google QUIC
+    // CONNECTION_CLOSE, IETF QUIC CONNECTION_CLOSE/Transport, or IETF QUIC
+    // CONNECTION_CLOSE/Application frame.
+    std::cerr << "OnConnectionCloseFrame: " << frame;
+    return true;
+  }
+  bool OnNewConnectionIdFrame(const QuicNewConnectionIdFrame& frame) override {
+    std::cerr << "OnNewConnectionIdFrame: " << frame;
+    return true;
+  }
+  bool OnRetireConnectionIdFrame(
+      const QuicRetireConnectionIdFrame& frame) override {
+    std::cerr << "OnRetireConnectionIdFrame: " << frame;
+    return true;
+  }
+  bool OnNewTokenFrame(const QuicNewTokenFrame& frame) override {
+    std::cerr << "OnNewTokenFrame: " << frame;
+    return true;
+  }
+  bool OnStopSendingFrame(const QuicStopSendingFrame& frame) override {
+    std::cerr << "OnStopSendingFrame: " << frame;
+    return true;
+  }
+  bool OnPathChallengeFrame(const QuicPathChallengeFrame& frame) override {
+    std::cerr << "OnPathChallengeFrame: " << frame;
+    return true;
+  }
+  bool OnPathResponseFrame(const QuicPathResponseFrame& frame) override {
+    std::cerr << "OnPathResponseFrame: " << frame;
+    return true;
+  }
+  bool OnGoAwayFrame(const QuicGoAwayFrame& frame) override {
+    std::cerr << "OnGoAwayFrame: " << frame;
+    return true;
+  }
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override {
+    std::cerr << "OnMaxStreamsFrame: " << frame;
+    return true;
+  }
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override {
+    std::cerr << "OnStreamsBlockedFrame: " << frame;
+    return true;
+  }
+  bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override {
+    std::cerr << "OnWindowUpdateFrame: " << frame;
+    return true;
+  }
+  bool OnBlockedFrame(const QuicBlockedFrame& frame) override {
+    std::cerr << "OnBlockedFrame: " << frame;
+    return true;
+  }
+  bool OnMessageFrame(const QuicMessageFrame& frame) override {
+    std::cerr << "OnMessageFrame: " << frame;
+    return true;
+  }
+  bool OnHandshakeDoneFrame(const QuicHandshakeDoneFrame& frame) override {
+    std::cerr << "OnHandshakeDoneFrame: " << frame;
+    return true;
+  }
+  bool OnAckFrequencyFrame(const QuicAckFrequencyFrame& frame) override {
+    std::cerr << "OnAckFrequencyFrame: " << frame;
+    return true;
+  }
+  void OnPacketComplete() override { std::cerr << "OnPacketComplete\n"; }
+  bool IsValidStatelessResetToken(
+      const StatelessResetToken& /*token*/) const override {
+    std::cerr << "IsValidStatelessResetToken\n";
+    return false;
+  }
+  void OnAuthenticatedIetfStatelessResetPacket(
+      const QuicIetfStatelessResetPacket& /*packet*/) override {
+    std::cerr << "OnAuthenticatedIetfStatelessResetPacket\n";
+  }
+  void OnKeyUpdate(KeyUpdateReason reason) override {
+    std::cerr << "OnKeyUpdate: " << reason << "\n";
+  }
+  void OnDecryptedFirstPacketInKeyPhase() override {
+    std::cerr << "OnDecryptedFirstPacketInKeyPhase\n";
+  }
+  std::unique_ptr<QuicDecrypter> AdvanceKeysAndCreateCurrentOneRttDecrypter()
+      override {
+    std::cerr << "AdvanceKeysAndCreateCurrentOneRttDecrypter\n";
+    return nullptr;
+  }
+  std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override {
+    std::cerr << "CreateCurrentOneRttEncrypter\n";
+    return nullptr;
+  }
+
+ private:
+  QuicFramer* framer_;  // Unowned.
+};
+
+}  // namespace quic
+
+int main(int argc, char* argv[]) {
+  const char* usage = "Usage: quic_packet_printer client|server <hex>";
+  std::vector<std::string> args =
+      quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+
+  if (args.size() < 2) {
+    quiche::QuichePrintCommandLineFlagHelp(usage);
+    return 1;
+  }
+
+  std::string perspective_string = args[0];
+  quic::Perspective perspective;
+  if (perspective_string == "client") {
+    perspective = quic::Perspective::IS_CLIENT;
+  } else if (perspective_string == "server") {
+    perspective = quic::Perspective::IS_SERVER;
+  } else {
+    std::cerr << "Invalid perspective" << std::endl;
+    quiche::QuichePrintCommandLineFlagHelp(usage);
+    return 1;
+  }
+  std::string hex = absl::HexStringToBytes(args[1]);
+  quic::ParsedQuicVersionVector versions = quic::AllSupportedVersions();
+  // Fake a time since we're not actually generating acks.
+  quic::QuicTime start(quic::QuicTime::Zero());
+  quic::QuicFramer framer(versions, start, perspective,
+                          quic::kQuicDefaultConnectionIdLength);
+  const quic::ParsedQuicVersion& version =
+      quic::ParseQuicVersionString(GetQuicFlag(FLAGS_quic_version));
+  if (version != quic::ParsedQuicVersion::Unsupported()) {
+    framer.set_version(version);
+  }
+  quic::QuicPacketPrinter visitor(&framer);
+  framer.set_visitor(&visitor);
+  quic::QuicEncryptedPacket encrypted(hex.c_str(), hex.length());
+  return framer.ProcessPacket(encrypted);
+}
diff --git a/quiche/quic/tools/quic_reject_reason_decoder_bin.cc b/quiche/quic/tools/quic_reject_reason_decoder_bin.cc
new file mode 100644
index 0000000..661e3d9
--- /dev/null
+++ b/quiche/quic/tools/quic_reject_reason_decoder_bin.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2016 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.
+
+// Decodes the packet HandshakeFailureReason from the chromium histogram
+// Net.QuicClientHelloRejectReasons
+
+#include <iostream>
+
+#include "absl/strings/numbers.h"
+#include "quiche/quic/core/crypto/crypto_handshake.h"
+#include "quiche/quic/core/crypto/crypto_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/quiche_text_utils.h"
+
+using quic::CryptoUtils;
+using quic::HandshakeFailureReason;
+using quic::MAX_FAILURE_REASON;
+
+int main(int argc, char* argv[]) {
+  const char* usage = "Usage: quic_reject_reason_decoder <packed_reason>";
+  std::vector<std::string> args =
+      quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+
+  if (args.size() != 1) {
+    std::cerr << usage << std::endl;
+    return 1;
+  }
+
+  uint32_t packed_error = 0;
+  if (!absl::SimpleAtoi(args[0], &packed_error)) {
+    std::cerr << "Unable to parse: " << args[0] << "\n";
+    return 2;
+  }
+
+  for (int i = 1; i < MAX_FAILURE_REASON; ++i) {
+    if ((packed_error & (1 << (i - 1))) == 0) {
+      continue;
+    }
+    HandshakeFailureReason reason = static_cast<HandshakeFailureReason>(i);
+    std::cout << CryptoUtils::HandshakeFailureReasonToString(reason) << "\n";
+  }
+  return 0;
+}
diff --git a/quiche/quic/tools/quic_server.cc b/quiche/quic/tools/quic_server.cc
new file mode 100644
index 0000000..aa521ef
--- /dev/null
+++ b/quiche/quic/tools/quic_server.cc
@@ -0,0 +1,221 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_server.h"
+
+#include <errno.h>
+#include <features.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "quiche/quic/core/crypto/crypto_handshake.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/quic_clock.h"
+#include "quiche/quic/core/quic_crypto_stream.h"
+#include "quiche/quic/core/quic_data_reader.h"
+#include "quiche/quic/core/quic_default_packet_writer.h"
+#include "quiche/quic/core/quic_dispatcher.h"
+#include "quiche/quic/core/quic_epoll_alarm_factory.h"
+#include "quiche/quic/core/quic_epoll_clock.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_packet_reader.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/tools/quic_simple_crypto_server_stream_helper.h"
+#include "quiche/quic/tools/quic_simple_dispatcher.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+
+namespace quic {
+
+namespace {
+
+const int kEpollFlags = EPOLLIN | EPOLLOUT | EPOLLET;
+const char kSourceAddressTokenSecret[] = "secret";
+
+}  // namespace
+
+const size_t kNumSessionsToCreatePerSocketEvent = 16;
+
+QuicServer::QuicServer(std::unique_ptr<ProofSource> proof_source,
+                       QuicSimpleServerBackend* quic_simple_server_backend)
+    : QuicServer(std::move(proof_source),
+                 quic_simple_server_backend,
+                 AllSupportedVersions()) {}
+
+QuicServer::QuicServer(std::unique_ptr<ProofSource> proof_source,
+                       QuicSimpleServerBackend* quic_simple_server_backend,
+                       const ParsedQuicVersionVector& supported_versions)
+    : QuicServer(std::move(proof_source),
+                 QuicConfig(),
+                 QuicCryptoServerConfig::ConfigOptions(),
+                 supported_versions,
+                 quic_simple_server_backend,
+                 kQuicDefaultConnectionIdLength) {}
+
+QuicServer::QuicServer(
+    std::unique_ptr<ProofSource> proof_source,
+    const QuicConfig& config,
+    const QuicCryptoServerConfig::ConfigOptions& crypto_config_options,
+    const ParsedQuicVersionVector& supported_versions,
+    QuicSimpleServerBackend* quic_simple_server_backend,
+    uint8_t expected_server_connection_id_length)
+    : port_(0),
+      fd_(-1),
+      packets_dropped_(0),
+      overflow_supported_(false),
+      silent_close_(false),
+      config_(config),
+      crypto_config_(kSourceAddressTokenSecret,
+                     QuicRandom::GetInstance(),
+                     std::move(proof_source),
+                     KeyExchangeSource::Default()),
+      crypto_config_options_(crypto_config_options),
+      version_manager_(supported_versions),
+      packet_reader_(new QuicPacketReader()),
+      quic_simple_server_backend_(quic_simple_server_backend),
+      expected_server_connection_id_length_(
+          expected_server_connection_id_length) {
+  QUICHE_DCHECK(quic_simple_server_backend_);
+  Initialize();
+}
+
+void QuicServer::Initialize() {
+  // If an initial flow control window has not explicitly been set, then use a
+  // sensible value for a server: 1 MB for session, 64 KB for each stream.
+  const uint32_t kInitialSessionFlowControlWindow = 1 * 1024 * 1024;  // 1 MB
+  const uint32_t kInitialStreamFlowControlWindow = 64 * 1024;         // 64 KB
+  if (config_.GetInitialStreamFlowControlWindowToSend() ==
+      kDefaultFlowControlSendWindow) {
+    config_.SetInitialStreamFlowControlWindowToSend(
+        kInitialStreamFlowControlWindow);
+  }
+  if (config_.GetInitialSessionFlowControlWindowToSend() ==
+      kDefaultFlowControlSendWindow) {
+    config_.SetInitialSessionFlowControlWindowToSend(
+        kInitialSessionFlowControlWindow);
+  }
+
+  epoll_server_.set_timeout_in_us(50 * 1000);
+
+  QuicEpollClock clock(&epoll_server_);
+
+  std::unique_ptr<CryptoHandshakeMessage> scfg(crypto_config_.AddDefaultConfig(
+      QuicRandom::GetInstance(), &clock, crypto_config_options_));
+}
+
+QuicServer::~QuicServer() = default;
+
+bool QuicServer::CreateUDPSocketAndListen(const QuicSocketAddress& address) {
+  QuicUdpSocketApi socket_api;
+  fd_ = socket_api.Create(address.host().AddressFamilyToInt(),
+                          /*receive_buffer_size =*/kDefaultSocketReceiveBuffer,
+                          /*send_buffer_size =*/kDefaultSocketReceiveBuffer);
+  if (fd_ == kQuicInvalidSocketFd) {
+    QUIC_LOG(ERROR) << "CreateSocket() failed: " << strerror(errno);
+    return false;
+  }
+
+  overflow_supported_ = socket_api.EnableDroppedPacketCount(fd_);
+  socket_api.EnableReceiveTimestamp(fd_);
+
+  sockaddr_storage addr = address.generic_address();
+  int rc = bind(fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
+  if (rc < 0) {
+    QUIC_LOG(ERROR) << "Bind failed: " << strerror(errno);
+    return false;
+  }
+  QUIC_LOG(INFO) << "Listening on " << address.ToString();
+  port_ = address.port();
+  if (port_ == 0) {
+    QuicSocketAddress address;
+    if (address.FromSocket(fd_) != 0) {
+      QUIC_LOG(ERROR) << "Unable to get self address.  Error: "
+                      << strerror(errno);
+    }
+    port_ = address.port();
+  }
+
+  epoll_server_.RegisterFD(fd_, this, kEpollFlags);
+  dispatcher_.reset(CreateQuicDispatcher());
+  dispatcher_->InitializeWithWriter(CreateWriter(fd_));
+
+  return true;
+}
+
+QuicPacketWriter* QuicServer::CreateWriter(int fd) {
+  return new QuicDefaultPacketWriter(fd);
+}
+
+QuicDispatcher* QuicServer::CreateQuicDispatcher() {
+  QuicEpollAlarmFactory alarm_factory(&epoll_server_);
+  return new QuicSimpleDispatcher(
+      &config_, &crypto_config_, &version_manager_,
+      std::unique_ptr<QuicEpollConnectionHelper>(new QuicEpollConnectionHelper(
+          &epoll_server_, QuicAllocator::BUFFER_POOL)),
+      std::unique_ptr<QuicCryptoServerStreamBase::Helper>(
+          new QuicSimpleCryptoServerStreamHelper()),
+      std::unique_ptr<QuicEpollAlarmFactory>(
+          new QuicEpollAlarmFactory(&epoll_server_)),
+      quic_simple_server_backend_, expected_server_connection_id_length_);
+}
+
+void QuicServer::HandleEventsForever() {
+  while (true) {
+    WaitForEvents();
+  }
+}
+
+void QuicServer::WaitForEvents() {
+  epoll_server_.WaitForEventsAndExecuteCallbacks();
+}
+
+void QuicServer::Shutdown() {
+  if (!silent_close_) {
+    // Before we shut down the epoll server, give all active sessions a chance
+    // to notify clients that they're closing.
+    dispatcher_->Shutdown();
+  }
+
+  epoll_server_.Shutdown();
+
+  close(fd_);
+  fd_ = -1;
+}
+
+void QuicServer::OnEvent(int fd, QuicEpollEvent* event) {
+  QUICHE_DCHECK_EQ(fd, fd_);
+  event->out_ready_mask = 0;
+
+  if (event->in_events & EPOLLIN) {
+    QUIC_DVLOG(1) << "EPOLLIN";
+
+    dispatcher_->ProcessBufferedChlos(kNumSessionsToCreatePerSocketEvent);
+
+    bool more_to_read = true;
+    while (more_to_read) {
+      more_to_read = packet_reader_->ReadAndDispatchPackets(
+          fd_, port_, QuicEpollClock(&epoll_server_), dispatcher_.get(),
+          overflow_supported_ ? &packets_dropped_ : nullptr);
+    }
+
+    if (dispatcher_->HasChlosBuffered()) {
+      // Register EPOLLIN event to consume buffered CHLO(s).
+      event->out_ready_mask |= EPOLLIN;
+    }
+  }
+  if (event->in_events & EPOLLOUT) {
+    dispatcher_->OnCanWrite();
+    if (dispatcher_->HasPendingWrites()) {
+      event->out_ready_mask |= EPOLLOUT;
+    }
+  }
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_server.h b/quiche/quic/tools/quic_server.h
new file mode 100644
index 0000000..9024289
--- /dev/null
+++ b/quiche/quic/tools/quic_server.h
@@ -0,0 +1,171 @@
+// Copyright (c) 2012 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.
+
+// A toy server, which listens on a specified address for QUIC traffic and
+// handles incoming responses.
+//
+// Note that this server is intended to verify correctness of the client and is
+// in no way expected to be performant.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SERVER_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SERVER_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/quic_crypto_server_config.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_framer.h"
+#include "quiche/quic/core/quic_packet_writer.h"
+#include "quiche/quic/core/quic_udp_socket.h"
+#include "quiche/quic/core/quic_version_manager.h"
+#include "quiche/quic/platform/api/quic_epoll.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/quic/tools/quic_spdy_server_base.h"
+
+namespace quic {
+
+namespace test {
+class QuicServerPeer;
+}  // namespace test
+
+class QuicDispatcher;
+class QuicPacketReader;
+
+class QuicServer : public QuicSpdyServerBase,
+                   public QuicEpollCallbackInterface {
+ public:
+  QuicServer(std::unique_ptr<ProofSource> proof_source,
+             QuicSimpleServerBackend* quic_simple_server_backend);
+  QuicServer(std::unique_ptr<ProofSource> proof_source,
+             QuicSimpleServerBackend* quic_simple_server_backend,
+             const ParsedQuicVersionVector& supported_versions);
+  QuicServer(std::unique_ptr<ProofSource> proof_source,
+             const QuicConfig& config,
+             const QuicCryptoServerConfig::ConfigOptions& crypto_config_options,
+             const ParsedQuicVersionVector& supported_versions,
+             QuicSimpleServerBackend* quic_simple_server_backend,
+             uint8_t expected_server_connection_id_length);
+  QuicServer(const QuicServer&) = delete;
+  QuicServer& operator=(const QuicServer&) = delete;
+
+  ~QuicServer() override;
+
+  std::string Name() const override { return "QuicServer"; }
+
+  // Start listening on the specified address.
+  bool CreateUDPSocketAndListen(const QuicSocketAddress& address) override;
+  // Handles all events. Does not return.
+  void HandleEventsForever() override;
+
+  // Wait up to 50ms, and handle any events which occur.
+  void WaitForEvents();
+
+  // Server deletion is imminent.  Start cleaning up the epoll server.
+  virtual void Shutdown();
+
+  // From EpollCallbackInterface
+  void OnRegistration(QuicEpollServer* /*eps*/,
+                      int /*fd*/,
+                      int /*event_mask*/) override {}
+  void OnModification(int /*fd*/, int /*event_mask*/) override {}
+  void OnEvent(int /*fd*/, QuicEpollEvent* /*event*/) override;
+  void OnUnregistration(int /*fd*/, bool /*replaced*/) override {}
+
+  void OnShutdown(QuicEpollServer* /*eps*/, int /*fd*/) override {}
+
+  void SetChloMultiplier(size_t multiplier) {
+    crypto_config_.set_chlo_multiplier(multiplier);
+  }
+
+  void SetPreSharedKey(absl::string_view key) {
+    crypto_config_.set_pre_shared_key(key);
+  }
+
+  bool overflow_supported() { return overflow_supported_; }
+
+  QuicPacketCount packets_dropped() { return packets_dropped_; }
+
+  int port() { return port_; }
+
+  QuicEpollServer* epoll_server() { return &epoll_server_; }
+
+ protected:
+  virtual QuicPacketWriter* CreateWriter(int fd);
+
+  virtual QuicDispatcher* CreateQuicDispatcher();
+
+  const QuicConfig& config() const { return config_; }
+  const QuicCryptoServerConfig& crypto_config() const { return crypto_config_; }
+
+  QuicDispatcher* dispatcher() { return dispatcher_.get(); }
+
+  QuicVersionManager* version_manager() { return &version_manager_; }
+
+  QuicSimpleServerBackend* server_backend() {
+    return quic_simple_server_backend_;
+  }
+
+  void set_silent_close(bool value) { silent_close_ = value; }
+
+  uint8_t expected_server_connection_id_length() {
+    return expected_server_connection_id_length_;
+  }
+
+ private:
+  friend class quic::test::QuicServerPeer;
+
+  // Initialize the internal state of the server.
+  void Initialize();
+
+  // Accepts data from the framer and demuxes clients to sessions.
+  std::unique_ptr<QuicDispatcher> dispatcher_;
+  // Frames incoming packets and hands them to the dispatcher.
+  QuicEpollServer epoll_server_;
+
+  // The port the server is listening on.
+  int port_;
+
+  // Listening connection.  Also used for outbound client communication.
+  QuicUdpSocketFd fd_;
+
+  // If overflow_supported_ is true this will be the number of packets dropped
+  // during the lifetime of the server.  This may overflow if enough packets
+  // are dropped.
+  QuicPacketCount packets_dropped_;
+
+  // True if the kernel supports SO_RXQ_OVFL, the number of packets dropped
+  // because the socket would otherwise overflow.
+  bool overflow_supported_;
+
+  // If true, do not call Shutdown on the dispatcher.  Connections will close
+  // without sending a final connection close.
+  bool silent_close_;
+
+  // config_ contains non-crypto parameters that are negotiated in the crypto
+  // handshake.
+  QuicConfig config_;
+  // crypto_config_ contains crypto parameters for the handshake.
+  QuicCryptoServerConfig crypto_config_;
+  // crypto_config_options_ contains crypto parameters for the handshake.
+  QuicCryptoServerConfig::ConfigOptions crypto_config_options_;
+
+  // Used to generate current supported versions.
+  QuicVersionManager version_manager_;
+
+  // Point to a QuicPacketReader object on the heap. The reader allocates more
+  // space than allowed on the stack.
+  std::unique_ptr<QuicPacketReader> packet_reader_;
+
+  QuicSimpleServerBackend* quic_simple_server_backend_;  // unowned.
+
+  // Connection ID length expected to be read on incoming IETF short headers.
+  uint8_t expected_server_connection_id_length_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SERVER_H_
diff --git a/quiche/quic/tools/quic_server_bin.cc b/quiche/quic/tools/quic_server_bin.cc
new file mode 100644
index 0000000..b7e587b
--- /dev/null
+++ b/quiche/quic/tools/quic_server_bin.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 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.
+
+// A binary wrapper for QuicServer.  It listens forever on --port
+// (default 6121) until it's killed or ctrl-cd to death.
+
+#include <vector>
+
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/tools/quic_epoll_server_factory.h"
+#include "quiche/quic/tools/quic_toy_server.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/platform/api/quiche_system_event_loop.h"
+
+int main(int argc, char* argv[]) {
+  quiche::QuicheSystemEventLoop event_loop("quic_server");
+  const char* usage = "Usage: quic_server [options]";
+  std::vector<std::string> non_option_args =
+      quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+  if (!non_option_args.empty()) {
+    quiche::QuichePrintCommandLineFlagHelp(usage);
+    exit(0);
+  }
+
+  quic::QuicToyServer::MemoryCacheBackendFactory backend_factory;
+  quic::QuicEpollServerFactory server_factory;
+  quic::QuicToyServer server(&backend_factory, &server_factory);
+  return server.Start();
+}
diff --git a/quiche/quic/tools/quic_server_test.cc b/quiche/quic/tools/quic_server_test.cc
new file mode 100644
index 0000000..5994689
--- /dev/null
+++ b/quiche/quic/tools/quic_server_test.cc
@@ -0,0 +1,210 @@
+// Copyright 2013 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 "quiche/quic/tools/quic_server.h"
+
+#include "absl/base/macros.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/quic_epoll_alarm_factory.h"
+#include "quiche/quic/core/quic_epoll_connection_helper.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/platform/api/quic_test_loopback.h"
+#include "quiche/quic/test_tools/crypto_test_utils.h"
+#include "quiche/quic/test_tools/mock_quic_dispatcher.h"
+#include "quiche/quic/test_tools/quic_server_peer.h"
+#include "quiche/quic/tools/quic_memory_cache_backend.h"
+#include "quiche/quic/tools/quic_simple_crypto_server_stream_helper.h"
+
+namespace quic {
+namespace test {
+
+using ::testing::_;
+
+namespace {
+
+class MockQuicSimpleDispatcher : public QuicSimpleDispatcher {
+ public:
+  MockQuicSimpleDispatcher(
+      const QuicConfig* config,
+      const QuicCryptoServerConfig* crypto_config,
+      QuicVersionManager* version_manager,
+      std::unique_ptr<QuicConnectionHelperInterface> helper,
+      std::unique_ptr<QuicCryptoServerStreamBase::Helper> session_helper,
+      std::unique_ptr<QuicAlarmFactory> alarm_factory,
+      QuicSimpleServerBackend* quic_simple_server_backend)
+      : QuicSimpleDispatcher(config,
+                             crypto_config,
+                             version_manager,
+                             std::move(helper),
+                             std::move(session_helper),
+                             std::move(alarm_factory),
+                             quic_simple_server_backend,
+                             kQuicDefaultConnectionIdLength) {}
+  ~MockQuicSimpleDispatcher() override = default;
+
+  MOCK_METHOD(void, OnCanWrite, (), (override));
+  MOCK_METHOD(bool, HasPendingWrites, (), (const, override));
+  MOCK_METHOD(bool, HasChlosBuffered, (), (const, override));
+  MOCK_METHOD(void, ProcessBufferedChlos, (size_t), (override));
+};
+
+class TestQuicServer : public QuicServer {
+ public:
+  TestQuicServer()
+      : QuicServer(crypto_test_utils::ProofSourceForTesting(),
+                   &quic_simple_server_backend_) {}
+
+  ~TestQuicServer() override = default;
+
+  MockQuicSimpleDispatcher* mock_dispatcher() { return mock_dispatcher_; }
+
+ protected:
+  QuicDispatcher* CreateQuicDispatcher() override {
+    mock_dispatcher_ = new MockQuicSimpleDispatcher(
+        &config(), &crypto_config(), version_manager(),
+        std::unique_ptr<QuicEpollConnectionHelper>(
+            new QuicEpollConnectionHelper(epoll_server(),
+                                          QuicAllocator::BUFFER_POOL)),
+        std::unique_ptr<QuicCryptoServerStreamBase::Helper>(
+            new QuicSimpleCryptoServerStreamHelper()),
+        std::unique_ptr<QuicEpollAlarmFactory>(
+            new QuicEpollAlarmFactory(epoll_server())),
+        &quic_simple_server_backend_);
+    return mock_dispatcher_;
+  }
+
+  MockQuicSimpleDispatcher* mock_dispatcher_ = nullptr;
+  QuicMemoryCacheBackend quic_simple_server_backend_;
+};
+
+class QuicServerEpollInTest : public QuicTest {
+ public:
+  QuicServerEpollInTest() : server_address_(TestLoopback(), 0) {}
+
+  void StartListening() {
+    server_.CreateUDPSocketAndListen(server_address_);
+    server_address_ = QuicSocketAddress(server_address_.host(), server_.port());
+
+    ASSERT_TRUE(QuicServerPeer::SetSmallSocket(&server_));
+
+    if (!server_.overflow_supported()) {
+      QUIC_LOG(WARNING) << "Overflow not supported.  Not testing.";
+      return;
+    }
+  }
+
+ protected:
+  QuicSocketAddress server_address_;
+  TestQuicServer server_;
+};
+
+// Tests that if dispatcher has CHLOs waiting for connection creation, EPOLLIN
+// event should try to create connections for them. And set epoll mask with
+// EPOLLIN if there are still CHLOs remaining at the end of epoll event.
+TEST_F(QuicServerEpollInTest, ProcessBufferedCHLOsOnEpollin) {
+  // Given an EPOLLIN event, try to create session for buffered CHLOs. In first
+  // event, dispatcher can't create session for all of CHLOs. So listener should
+  // register another EPOLLIN event by itself. Even without new packet arrival,
+  // the rest CHLOs should be process in next epoll event.
+  StartListening();
+  bool more_chlos = true;
+  MockQuicSimpleDispatcher* dispatcher_ = server_.mock_dispatcher();
+  QUICHE_DCHECK(dispatcher_ != nullptr);
+  EXPECT_CALL(*dispatcher_, OnCanWrite()).Times(testing::AnyNumber());
+  EXPECT_CALL(*dispatcher_, ProcessBufferedChlos(_)).Times(2);
+  EXPECT_CALL(*dispatcher_, HasPendingWrites()).Times(testing::AnyNumber());
+  // Expect there are still CHLOs buffered after 1st event. But not any more
+  // after 2nd event.
+  EXPECT_CALL(*dispatcher_, HasChlosBuffered())
+      .WillOnce(testing::Return(true))
+      .WillOnce(
+          DoAll(testing::Assign(&more_chlos, false), testing::Return(false)));
+
+  // Send a packet to trigger epoll event.
+  int fd = socket(
+      AddressFamilyUnderTest() == IpAddressFamily::IP_V4 ? AF_INET : AF_INET6,
+      SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
+  ASSERT_LT(0, fd);
+
+  char buf[1024];
+  memset(buf, 0, ABSL_ARRAYSIZE(buf));
+  sockaddr_storage storage = server_address_.generic_address();
+  int rc = sendto(fd, buf, ABSL_ARRAYSIZE(buf), 0,
+                  reinterpret_cast<sockaddr*>(&storage), sizeof(storage));
+  if (rc < 0) {
+    QUIC_DLOG(INFO) << errno << " " << strerror(errno);
+  }
+
+  while (more_chlos) {
+    server_.WaitForEvents();
+  }
+}
+
+class QuicServerDispatchPacketTest : public QuicTest {
+ public:
+  QuicServerDispatchPacketTest()
+      : crypto_config_("blah",
+                       QuicRandom::GetInstance(),
+                       crypto_test_utils::ProofSourceForTesting(),
+                       KeyExchangeSource::Default()),
+        version_manager_(AllSupportedVersions()),
+        dispatcher_(
+            &config_,
+            &crypto_config_,
+            &version_manager_,
+            std::unique_ptr<QuicEpollConnectionHelper>(
+                new QuicEpollConnectionHelper(&eps_,
+                                              QuicAllocator::BUFFER_POOL)),
+            std::unique_ptr<QuicCryptoServerStreamBase::Helper>(
+                new QuicSimpleCryptoServerStreamHelper()),
+            std::unique_ptr<QuicEpollAlarmFactory>(
+                new QuicEpollAlarmFactory(&eps_)),
+            &quic_simple_server_backend_) {
+    dispatcher_.InitializeWithWriter(new QuicDefaultPacketWriter(1234));
+  }
+
+  void DispatchPacket(const QuicReceivedPacket& packet) {
+    QuicSocketAddress client_addr, server_addr;
+    dispatcher_.ProcessPacket(server_addr, client_addr, packet);
+  }
+
+ protected:
+  QuicConfig config_;
+  QuicCryptoServerConfig crypto_config_;
+  QuicVersionManager version_manager_;
+  QuicEpollServer eps_;
+  QuicMemoryCacheBackend quic_simple_server_backend_;
+  MockQuicDispatcher dispatcher_;
+};
+
+TEST_F(QuicServerDispatchPacketTest, DispatchPacket) {
+  // clang-format off
+  unsigned char valid_packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00
+  };
+  // clang-format on
+  QuicReceivedPacket encrypted_valid_packet(
+      reinterpret_cast<char*>(valid_packet), ABSL_ARRAYSIZE(valid_packet),
+      QuicTime::Zero(), false);
+
+  EXPECT_CALL(dispatcher_, ProcessPacket(_, _, _)).Times(1);
+  DispatchPacket(encrypted_valid_packet);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_simple_client_session.cc b/quiche/quic/tools/quic_simple_client_session.cc
new file mode 100644
index 0000000..fa211ac
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_client_session.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2018 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 "quiche/quic/tools/quic_simple_client_session.h"
+
+#include <utility>
+
+namespace quic {
+
+QuicSimpleClientSession::QuicSimpleClientSession(
+    const QuicConfig& config, const ParsedQuicVersionVector& supported_versions,
+    QuicConnection* connection, const QuicServerId& server_id,
+    QuicCryptoClientConfig* crypto_config,
+    QuicClientPushPromiseIndex* push_promise_index, bool drop_response_body)
+    : QuicSimpleClientSession(config, supported_versions, connection, server_id,
+                              crypto_config, push_promise_index,
+                              drop_response_body,
+                              /*enable_web_transport=*/false,
+                              /*use_datagram_contexts=*/false) {}
+
+QuicSimpleClientSession::QuicSimpleClientSession(
+    const QuicConfig& config, const ParsedQuicVersionVector& supported_versions,
+    QuicConnection* connection, const QuicServerId& server_id,
+    QuicCryptoClientConfig* crypto_config,
+    QuicClientPushPromiseIndex* push_promise_index, bool drop_response_body,
+    bool enable_web_transport, bool use_datagram_contexts)
+    : QuicSpdyClientSession(config, supported_versions, connection, server_id,
+                            crypto_config, push_promise_index),
+      drop_response_body_(drop_response_body),
+      enable_web_transport_(enable_web_transport),
+      use_datagram_contexts_(use_datagram_contexts) {}
+
+std::unique_ptr<QuicSpdyClientStream>
+QuicSimpleClientSession::CreateClientStream() {
+  return std::make_unique<QuicSimpleClientStream>(
+      GetNextOutgoingBidirectionalStreamId(), this, BIDIRECTIONAL,
+      drop_response_body_);
+}
+
+bool QuicSimpleClientSession::ShouldNegotiateWebTransport() {
+  return enable_web_transport_;
+}
+
+bool QuicSimpleClientSession::ShouldNegotiateDatagramContexts() {
+  return use_datagram_contexts_;
+}
+
+HttpDatagramSupport QuicSimpleClientSession::LocalHttpDatagramSupport() {
+  return enable_web_transport_ ? HttpDatagramSupport::kDraft04
+                               : HttpDatagramSupport::kNone;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_simple_client_session.h b/quiche/quic/tools/quic_simple_client_session.h
new file mode 100644
index 0000000..75ed195
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_client_session.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2018 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_SESSION_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_SESSION_H_
+
+#include "quiche/quic/core/http/quic_spdy_client_session.h"
+#include "quiche/quic/tools/quic_simple_client_stream.h"
+
+namespace quic {
+
+class QuicSimpleClientSession : public QuicSpdyClientSession {
+ public:
+  QuicSimpleClientSession(const QuicConfig& config,
+                          const ParsedQuicVersionVector& supported_versions,
+                          QuicConnection* connection,
+                          const QuicServerId& server_id,
+                          QuicCryptoClientConfig* crypto_config,
+                          QuicClientPushPromiseIndex* push_promise_index,
+                          bool drop_response_body);
+  QuicSimpleClientSession(const QuicConfig& config,
+                          const ParsedQuicVersionVector& supported_versions,
+                          QuicConnection* connection,
+                          const QuicServerId& server_id,
+                          QuicCryptoClientConfig* crypto_config,
+                          QuicClientPushPromiseIndex* push_promise_index,
+                          bool drop_response_body, bool enable_web_transport,
+                          bool use_datagram_contexts);
+
+  std::unique_ptr<QuicSpdyClientStream> CreateClientStream() override;
+  bool ShouldNegotiateWebTransport() override;
+  bool ShouldNegotiateDatagramContexts() override;
+  HttpDatagramSupport LocalHttpDatagramSupport() override;
+
+ private:
+  const bool drop_response_body_;
+  const bool enable_web_transport_;
+  const bool use_datagram_contexts_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_SESSION_H_
diff --git a/quiche/quic/tools/quic_simple_client_stream.cc b/quiche/quic/tools/quic_simple_client_stream.cc
new file mode 100644
index 0000000..14de9f0
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_client_stream.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2018 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 "quiche/quic/tools/quic_simple_client_stream.h"
+
+namespace quic {
+
+void QuicSimpleClientStream::OnBodyAvailable() {
+  if (!drop_response_body_) {
+    QuicSpdyClientStream::OnBodyAvailable();
+    return;
+  }
+
+  while (HasBytesToRead()) {
+    struct iovec iov;
+    if (GetReadableRegions(&iov, 1) == 0) {
+      break;
+    }
+    MarkConsumed(iov.iov_len);
+  }
+  if (sequencer()->IsClosed()) {
+    OnFinRead();
+  } else {
+    sequencer()->SetUnblocked();
+  }
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_simple_client_stream.h b/quiche/quic/tools/quic_simple_client_stream.h
new file mode 100644
index 0000000..a68b7cb
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_client_stream.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2018 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_STREAM_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_STREAM_H_
+
+#include "quiche/quic/core/http/quic_spdy_client_stream.h"
+
+namespace quic {
+
+class QuicSimpleClientStream : public QuicSpdyClientStream {
+ public:
+  QuicSimpleClientStream(QuicStreamId id,
+                         QuicSpdyClientSession* session,
+                         StreamType type,
+                         bool drop_response_body)
+      : QuicSpdyClientStream(id, session, type),
+        drop_response_body_(drop_response_body) {}
+  void OnBodyAvailable() override;
+
+ private:
+  const bool drop_response_body_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CLIENT_STREAM_H_
diff --git a/quiche/quic/tools/quic_simple_crypto_server_stream_helper.cc b/quiche/quic/tools/quic_simple_crypto_server_stream_helper.cc
new file mode 100644
index 0000000..08d63b7
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_crypto_server_stream_helper.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_simple_crypto_server_stream_helper.h"
+
+#include "quiche/quic/core/quic_utils.h"
+
+namespace quic {
+
+QuicSimpleCryptoServerStreamHelper::QuicSimpleCryptoServerStreamHelper() =
+    default;
+
+QuicSimpleCryptoServerStreamHelper::~QuicSimpleCryptoServerStreamHelper() =
+    default;
+
+bool QuicSimpleCryptoServerStreamHelper::CanAcceptClientHello(
+    const CryptoHandshakeMessage& /*message*/,
+    const QuicSocketAddress& /*client_address*/,
+    const QuicSocketAddress& /*peer_address*/,
+    const QuicSocketAddress& /*self_address*/,
+    std::string* /*error_details*/) const {
+  return true;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_simple_crypto_server_stream_helper.h b/quiche/quic/tools/quic_simple_crypto_server_stream_helper.h
new file mode 100644
index 0000000..ba64226
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_crypto_server_stream_helper.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CRYPTO_SERVER_STREAM_HELPER_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CRYPTO_SERVER_STREAM_HELPER_H_
+
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/quic_crypto_server_stream_base.h"
+
+namespace quic {
+
+// Simple helper for server crypto streams which generates a new random
+// connection ID for rejects.
+class QuicSimpleCryptoServerStreamHelper
+    : public QuicCryptoServerStreamBase::Helper {
+ public:
+  QuicSimpleCryptoServerStreamHelper();
+
+  ~QuicSimpleCryptoServerStreamHelper() override;
+
+  bool CanAcceptClientHello(const CryptoHandshakeMessage& message,
+                            const QuicSocketAddress& client_address,
+                            const QuicSocketAddress& peer_address,
+                            const QuicSocketAddress& self_address,
+                            std::string* error_details) const override;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_CRYPTO_SERVER_STREAM_HELPER_H_
diff --git a/quiche/quic/tools/quic_simple_dispatcher.cc b/quiche/quic/tools/quic_simple_dispatcher.cc
new file mode 100644
index 0000000..b820864
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_dispatcher.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_simple_dispatcher.h"
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/tools/quic_simple_server_session.h"
+
+namespace quic {
+
+QuicSimpleDispatcher::QuicSimpleDispatcher(
+    const QuicConfig* config,
+    const QuicCryptoServerConfig* crypto_config,
+    QuicVersionManager* version_manager,
+    std::unique_ptr<QuicConnectionHelperInterface> helper,
+    std::unique_ptr<QuicCryptoServerStreamBase::Helper> session_helper,
+    std::unique_ptr<QuicAlarmFactory> alarm_factory,
+    QuicSimpleServerBackend* quic_simple_server_backend,
+    uint8_t expected_server_connection_id_length)
+    : QuicDispatcher(config,
+                     crypto_config,
+                     version_manager,
+                     std::move(helper),
+                     std::move(session_helper),
+                     std::move(alarm_factory),
+                     expected_server_connection_id_length),
+      quic_simple_server_backend_(quic_simple_server_backend) {}
+
+QuicSimpleDispatcher::~QuicSimpleDispatcher() = default;
+
+int QuicSimpleDispatcher::GetRstErrorCount(
+    QuicRstStreamErrorCode error_code) const {
+  auto it = rst_error_map_.find(error_code);
+  if (it == rst_error_map_.end()) {
+    return 0;
+  }
+  return it->second;
+}
+
+void QuicSimpleDispatcher::OnRstStreamReceived(
+    const QuicRstStreamFrame& frame) {
+  auto it = rst_error_map_.find(frame.error_code);
+  if (it == rst_error_map_.end()) {
+    rst_error_map_.insert(std::make_pair(frame.error_code, 1));
+  } else {
+    it->second++;
+  }
+}
+
+std::unique_ptr<QuicSession> QuicSimpleDispatcher::CreateQuicSession(
+    QuicConnectionId connection_id, const QuicSocketAddress& self_address,
+    const QuicSocketAddress& peer_address, absl::string_view /*alpn*/,
+    const ParsedQuicVersion& version,
+    const ParsedClientHello& /*parsed_chlo*/) {
+  // The QuicServerSessionBase takes ownership of |connection| below.
+  QuicConnection* connection =
+      new QuicConnection(connection_id, self_address, peer_address, helper(),
+                         alarm_factory(), writer(),
+                         /* owns_writer= */ false, Perspective::IS_SERVER,
+                         ParsedQuicVersionVector{version});
+
+  auto session = std::make_unique<QuicSimpleServerSession>(
+      config(), GetSupportedVersions(), connection, this, session_helper(),
+      crypto_config(), compressed_certs_cache(), quic_simple_server_backend_);
+  session->Initialize();
+  return session;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_simple_dispatcher.h b/quiche/quic/tools/quic_simple_dispatcher.h
new file mode 100644
index 0000000..ee80955
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_dispatcher.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SIMPLE_DISPATCHER_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_DISPATCHER_H_
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/http/quic_server_session_base.h"
+#include "quiche/quic/core/quic_dispatcher.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+
+namespace quic {
+
+class QuicSimpleDispatcher : public QuicDispatcher {
+ public:
+  QuicSimpleDispatcher(
+      const QuicConfig* config,
+      const QuicCryptoServerConfig* crypto_config,
+      QuicVersionManager* version_manager,
+      std::unique_ptr<QuicConnectionHelperInterface> helper,
+      std::unique_ptr<QuicCryptoServerStreamBase::Helper> session_helper,
+      std::unique_ptr<QuicAlarmFactory> alarm_factory,
+      QuicSimpleServerBackend* quic_simple_server_backend,
+      uint8_t expected_server_connection_id_length);
+
+  ~QuicSimpleDispatcher() override;
+
+  int GetRstErrorCount(QuicRstStreamErrorCode rst_error_code) const;
+
+  void OnRstStreamReceived(const QuicRstStreamFrame& frame) override;
+
+ protected:
+  std::unique_ptr<QuicSession> CreateQuicSession(
+      QuicConnectionId connection_id, const QuicSocketAddress& self_address,
+      const QuicSocketAddress& peer_address, absl::string_view alpn,
+      const ParsedQuicVersion& version,
+      const ParsedClientHello& parsed_chlo) override;
+
+  QuicSimpleServerBackend* server_backend() {
+    return quic_simple_server_backend_;
+  }
+
+ private:
+  QuicSimpleServerBackend* quic_simple_server_backend_;  // Unowned.
+
+  // The map of the reset error code with its counter.
+  std::map<QuicRstStreamErrorCode, int> rst_error_map_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_DISPATCHER_H_
diff --git a/quiche/quic/tools/quic_simple_server_backend.h b/quiche/quic/tools/quic_simple_server_backend.h
new file mode 100644
index 0000000..b0e1d8f
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_backend.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_BACKEND_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_BACKEND_H_
+
+#include <memory>
+
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/web_transport_interface.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/spdy/core/spdy_header_block.h"
+
+namespace quic {
+
+// This interface implements the functionality to fetch a response
+// from the backend (such as cache, http-proxy etc) to serve
+// requests received by a Quic Server
+class QuicSimpleServerBackend {
+ public:
+  // This interface implements the methods
+  // called by the QuicSimpleServerBackend implementation
+  // to process the request in the backend
+  class RequestHandler {
+   public:
+    virtual ~RequestHandler() {}
+
+    virtual QuicConnectionId connection_id() const = 0;
+    virtual QuicStreamId stream_id() const = 0;
+    virtual std::string peer_host() const = 0;
+    // Called when the response is ready at the backend and can be send back to
+    // the QUIC client.
+    virtual void OnResponseBackendComplete(
+        const QuicBackendResponse* response) = 0;
+  };
+
+  struct WebTransportResponse {
+    spdy::Http2HeaderBlock response_headers;
+    std::unique_ptr<WebTransportVisitor> visitor;
+  };
+
+  virtual ~QuicSimpleServerBackend() = default;
+  // This method initializes the backend instance to fetch responses
+  // from a backend server, in-memory cache etc.
+  virtual bool InitializeBackend(const std::string& backend_url) = 0;
+  // Returns true if the backend has been successfully initialized
+  // and could be used to fetch HTTP requests
+  virtual bool IsBackendInitialized() const = 0;
+  // Triggers a HTTP request to be sent to the backend server or cache
+  // If response is immediately available, the function synchronously calls
+  // the |request_handler| with the HTTP response.
+  // If the response has to be fetched over the network, the function
+  // asynchronously calls |request_handler| with the HTTP response.
+  virtual void FetchResponseFromBackend(
+      const spdy::Http2HeaderBlock& request_headers,
+      const std::string& request_body,
+      RequestHandler* request_handler) = 0;
+  // Clears the state of the backend  instance
+  virtual void CloseBackendResponseStream(RequestHandler* request_handler) = 0;
+
+  virtual WebTransportResponse ProcessWebTransportRequest(
+      const spdy::Http2HeaderBlock& /*request_headers*/,
+      WebTransportSession* /*session*/) {
+    WebTransportResponse response;
+    response.response_headers[":status"] = "400";
+    return response;
+  }
+  virtual bool SupportsWebTransport() { return false; }
+  virtual bool UsesDatagramContexts() { return false; }
+  virtual bool SupportsExtendedConnect() { return true; }
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_BACKEND_H_
diff --git a/quiche/quic/tools/quic_simple_server_session.cc b/quiche/quic/tools/quic_simple_server_session.cc
new file mode 100644
index 0000000..15e43b9
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_session.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_simple_server_session.h"
+
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "quiche/quic/core/http/quic_server_initiated_spdy_stream.h"
+#include "quiche/quic/core/http/quic_spdy_session.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/tools/quic_simple_server_stream.h"
+
+namespace quic {
+
+QuicSimpleServerSession::QuicSimpleServerSession(
+    const QuicConfig& config, const ParsedQuicVersionVector& supported_versions,
+    QuicConnection* connection, QuicSession::Visitor* visitor,
+    QuicCryptoServerStreamBase::Helper* helper,
+    const QuicCryptoServerConfig* crypto_config,
+    QuicCompressedCertsCache* compressed_certs_cache,
+    QuicSimpleServerBackend* quic_simple_server_backend)
+    : QuicServerSessionBase(config, supported_versions, connection, visitor,
+                            helper, crypto_config, compressed_certs_cache),
+      highest_promised_stream_id_(
+          QuicUtils::GetInvalidStreamId(connection->transport_version())),
+      quic_simple_server_backend_(quic_simple_server_backend) {
+  QUICHE_DCHECK(quic_simple_server_backend_);
+}
+
+QuicSimpleServerSession::~QuicSimpleServerSession() { DeleteConnection(); }
+
+std::unique_ptr<QuicCryptoServerStreamBase>
+QuicSimpleServerSession::CreateQuicCryptoServerStream(
+    const QuicCryptoServerConfig* crypto_config,
+    QuicCompressedCertsCache* compressed_certs_cache) {
+  return CreateCryptoServerStream(crypto_config, compressed_certs_cache, this,
+                                  stream_helper());
+}
+
+void QuicSimpleServerSession::OnStreamFrame(const QuicStreamFrame& frame) {
+  if (!IsIncomingStream(frame.stream_id) && !WillNegotiateWebTransport()) {
+    QUIC_LOG(WARNING) << "Client shouldn't send data on server push stream";
+    connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID, "Client sent data on server push stream",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  QuicSpdySession::OnStreamFrame(frame);
+}
+
+QuicSpdyStream* QuicSimpleServerSession::CreateIncomingStream(QuicStreamId id) {
+  if (!ShouldCreateIncomingStream(id)) {
+    return nullptr;
+  }
+
+  QuicSpdyStream* stream = new QuicSimpleServerStream(
+      id, this, BIDIRECTIONAL, quic_simple_server_backend_);
+  ActivateStream(absl::WrapUnique(stream));
+  return stream;
+}
+
+QuicSpdyStream* QuicSimpleServerSession::CreateIncomingStream(
+    PendingStream* pending) {
+  QuicSpdyStream* stream =
+      new QuicSimpleServerStream(pending, this, quic_simple_server_backend_);
+  ActivateStream(absl::WrapUnique(stream));
+  return stream;
+}
+
+QuicSpdyStream* QuicSimpleServerSession::CreateOutgoingBidirectionalStream() {
+  if (!WillNegotiateWebTransport()) {
+    QUIC_BUG(QuicSimpleServerSession CreateOutgoingBidirectionalStream without
+                 WebTransport support)
+        << "QuicSimpleServerSession::CreateOutgoingBidirectionalStream called "
+           "in a session without WebTransport support.";
+    return nullptr;
+  }
+  if (!ShouldCreateOutgoingBidirectionalStream()) {
+    return nullptr;
+  }
+
+  QuicServerInitiatedSpdyStream* stream = new QuicServerInitiatedSpdyStream(
+      GetNextOutgoingBidirectionalStreamId(), this, BIDIRECTIONAL);
+  ActivateStream(absl::WrapUnique(stream));
+  return stream;
+}
+
+QuicSimpleServerStream*
+QuicSimpleServerSession::CreateOutgoingUnidirectionalStream() {
+  if (!ShouldCreateOutgoingUnidirectionalStream()) {
+    return nullptr;
+  }
+
+  QuicSimpleServerStream* stream = new QuicSimpleServerStream(
+      GetNextOutgoingUnidirectionalStreamId(), this, WRITE_UNIDIRECTIONAL,
+      quic_simple_server_backend_);
+  ActivateStream(absl::WrapUnique(stream));
+  return stream;
+}
+
+void QuicSimpleServerSession::HandleFrameOnNonexistentOutgoingStream(
+    QuicStreamId stream_id) {
+  // If this stream is a promised but not created stream (stream_id within the
+  // range of next_outgoing_stream_id_ and highes_promised_stream_id_),
+  // connection shouldn't be closed.
+  // Otherwise behave in the same way as base class.
+  if (highest_promised_stream_id_ ==
+          QuicUtils::GetInvalidStreamId(transport_version()) ||
+      stream_id > highest_promised_stream_id_) {
+    QuicSession::HandleFrameOnNonexistentOutgoingStream(stream_id);
+  }
+}
+
+void QuicSimpleServerSession::HandleRstOnValidNonexistentStream(
+    const QuicRstStreamFrame& frame) {
+  QuicSession::HandleRstOnValidNonexistentStream(frame);
+  if (!IsClosedStream(frame.stream_id)) {
+    // If a nonexistent stream is not a closed stream and still valid, it must
+    // be a locally preserved stream. Resetting this kind of stream means
+    // cancelling the promised server push.
+    // Since PromisedStreamInfo are queued in sequence, the corresponding
+    // index for it in promised_streams_ can be calculated.
+    QuicStreamId next_stream_id = next_outgoing_unidirectional_stream_id();
+    if ((!version().HasIetfQuicFrames() ||
+         !QuicUtils::IsBidirectionalStreamId(frame.stream_id, version())) &&
+        frame.stream_id >= next_stream_id) {
+      size_t index = (frame.stream_id - next_stream_id) /
+                     QuicUtils::StreamIdDelta(transport_version());
+      if (index < promised_streams_.size()) {
+        promised_streams_[index].is_cancelled = true;
+      }
+    }
+    control_frame_manager().WriteOrBufferRstStream(
+        frame.stream_id,
+        QuicResetStreamError::FromInternal(QUIC_RST_ACKNOWLEDGEMENT), 0);
+    connection()->OnStreamReset(frame.stream_id, QUIC_RST_ACKNOWLEDGEMENT);
+  }
+}
+
+spdy::Http2HeaderBlock QuicSimpleServerSession::SynthesizePushRequestHeaders(
+    std::string request_url, QuicBackendResponse::ServerPushInfo resource,
+    const spdy::Http2HeaderBlock& original_request_headers) {
+  QuicUrl push_request_url = resource.request_url;
+
+  spdy::Http2HeaderBlock spdy_headers = original_request_headers.Clone();
+  // :authority could be different from original request.
+  spdy_headers[":authority"] = push_request_url.host();
+  spdy_headers[":path"] = push_request_url.path();
+  // Push request always use GET.
+  spdy_headers[":method"] = "GET";
+  spdy_headers["referer"] = request_url;
+  spdy_headers[":scheme"] = push_request_url.scheme();
+  // It is not possible to push a response to a request that includes a request
+  // body.
+  spdy_headers["content-length"] = "0";
+  // Remove "host" field as push request is a directly generated HTTP2 request
+  // which should use ":authority" instead of "host".
+  spdy_headers.erase("host");
+  return spdy_headers;
+}
+
+void QuicSimpleServerSession::SendPushPromise(QuicStreamId original_stream_id,
+                                              QuicStreamId promised_stream_id,
+                                              spdy::Http2HeaderBlock headers) {
+  QUIC_DLOG(INFO) << "stream " << original_stream_id
+                  << " send PUSH_PROMISE for promised stream "
+                  << promised_stream_id;
+  WritePushPromise(original_stream_id, promised_stream_id, std::move(headers));
+}
+
+void QuicSimpleServerSession::HandlePromisedPushRequests() {
+  while (!promised_streams_.empty() &&
+         ShouldCreateOutgoingUnidirectionalStream()) {
+    PromisedStreamInfo& promised_info = promised_streams_.front();
+    QUICHE_DCHECK_EQ(next_outgoing_unidirectional_stream_id(),
+                     promised_info.stream_id);
+
+    if (promised_info.is_cancelled) {
+      // This stream has been reset by client. Skip this stream id.
+      promised_streams_.pop_front();
+      GetNextOutgoingUnidirectionalStreamId();
+      return;
+    }
+
+    QuicSimpleServerStream* promised_stream =
+        static_cast<QuicSimpleServerStream*>(
+            CreateOutgoingUnidirectionalStream());
+    QUICHE_DCHECK_NE(promised_stream, nullptr);
+    QUICHE_DCHECK_EQ(promised_info.stream_id, promised_stream->id());
+    QUIC_DLOG(INFO) << "created server push stream " << promised_stream->id();
+
+    promised_stream->SetPriority(promised_info.precedence);
+
+    spdy::Http2HeaderBlock request_headers(
+        std::move(promised_info.request_headers));
+
+    promised_streams_.pop_front();
+    promised_stream->PushResponse(std::move(request_headers));
+  }
+}
+
+void QuicSimpleServerSession::OnCanCreateNewOutgoingStream(
+    bool unidirectional) {
+  QuicSpdySession::OnCanCreateNewOutgoingStream(unidirectional);
+  if (unidirectional) {
+    HandlePromisedPushRequests();
+  }
+}
+
+void QuicSimpleServerSession::MaybeInitializeHttp3UnidirectionalStreams() {
+  size_t previous_static_stream_count = num_static_streams();
+  QuicSpdySession::MaybeInitializeHttp3UnidirectionalStreams();
+  size_t current_static_stream_count = num_static_streams();
+  QUICHE_DCHECK_GE(current_static_stream_count, previous_static_stream_count);
+  highest_promised_stream_id_ +=
+      QuicUtils::StreamIdDelta(transport_version()) *
+      (current_static_stream_count - previous_static_stream_count);
+}
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_simple_server_session.h b/quiche/quic/tools/quic_simple_server_session.h
new file mode 100644
index 0000000..950da18
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_session.h
@@ -0,0 +1,160 @@
+// Copyright (c) 2012 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.
+
+// A toy server specific QuicSession subclass.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_SESSION_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_SESSION_H_
+
+#include <stdint.h>
+
+#include <list>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "quiche/quic/core/http/quic_server_session_base.h"
+#include "quiche/quic/core/http/quic_spdy_session.h"
+#include "quiche/quic/core/quic_crypto_server_stream_base.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/quic/tools/quic_simple_server_stream.h"
+
+namespace quic {
+
+namespace test {
+class QuicSimpleServerSessionPeer;
+}  // namespace test
+
+class QuicSimpleServerSession : public QuicServerSessionBase {
+ public:
+  // A PromisedStreamInfo is an element of the queue to store promised
+  // stream which hasn't been created yet. It keeps a mapping between promised
+  // stream id with its priority and the headers sent out in PUSH_PROMISE.
+  struct PromisedStreamInfo {
+   public:
+    PromisedStreamInfo(spdy::Http2HeaderBlock request_headers,
+                       QuicStreamId stream_id,
+                       const spdy::SpdyStreamPrecedence& precedence)
+        : request_headers(std::move(request_headers)),
+          stream_id(stream_id),
+          precedence(precedence),
+          is_cancelled(false) {}
+    spdy::Http2HeaderBlock request_headers;
+    QuicStreamId stream_id;
+    spdy::SpdyStreamPrecedence precedence;
+    bool is_cancelled;
+  };
+
+  // Takes ownership of |connection|.
+  QuicSimpleServerSession(const QuicConfig& config,
+                          const ParsedQuicVersionVector& supported_versions,
+                          QuicConnection* connection,
+                          QuicSession::Visitor* visitor,
+                          QuicCryptoServerStreamBase::Helper* helper,
+                          const QuicCryptoServerConfig* crypto_config,
+                          QuicCompressedCertsCache* compressed_certs_cache,
+                          QuicSimpleServerBackend* quic_simple_server_backend);
+  QuicSimpleServerSession(const QuicSimpleServerSession&) = delete;
+  QuicSimpleServerSession& operator=(const QuicSimpleServerSession&) = delete;
+
+  ~QuicSimpleServerSession() override;
+
+  // Override base class to detact client sending data on server push stream.
+  void OnStreamFrame(const QuicStreamFrame& frame) override;
+
+  void OnCanCreateNewOutgoingStream(bool unidirectional) override;
+
+  bool ShouldNegotiateDatagramContexts() override {
+    return quic_simple_server_backend_->UsesDatagramContexts();
+  }
+
+ protected:
+  // QuicSession methods:
+  QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override;
+  QuicSpdyStream* CreateIncomingStream(PendingStream* pending) override;
+  QuicSpdyStream* CreateOutgoingBidirectionalStream() override;
+  QuicSimpleServerStream* CreateOutgoingUnidirectionalStream() override;
+  // Override to return true for locally preserved server push stream.
+  void HandleFrameOnNonexistentOutgoingStream(QuicStreamId stream_id) override;
+  // Override to handle reseting locally preserved streams.
+  void HandleRstOnValidNonexistentStream(
+      const QuicRstStreamFrame& frame) override;
+
+  // QuicServerSessionBaseMethod:
+  std::unique_ptr<QuicCryptoServerStreamBase> CreateQuicCryptoServerStream(
+      const QuicCryptoServerConfig* crypto_config,
+      QuicCompressedCertsCache* compressed_certs_cache) override;
+
+  QuicSimpleServerBackend* server_backend() {
+    return quic_simple_server_backend_;
+  }
+
+  void MaybeInitializeHttp3UnidirectionalStreams() override;
+
+  bool ShouldNegotiateWebTransport() override {
+    return quic_simple_server_backend_->SupportsWebTransport();
+  }
+  HttpDatagramSupport LocalHttpDatagramSupport() override {
+    if (ShouldNegotiateWebTransport()) {
+      return HttpDatagramSupport::kDraft00And04;
+    }
+    return QuicServerSessionBase::LocalHttpDatagramSupport();
+  }
+
+ private:
+  friend class test::QuicSimpleServerSessionPeer;
+
+  // Create a server push headers block by copying request's headers block.
+  // But replace or add these pseudo-headers as they are specific to each
+  // request:
+  // :authority, :path, :method, :scheme, referer.
+  // Copying the rest headers ensures they are the same as the original
+  // request, especially cookies.
+  spdy::Http2HeaderBlock SynthesizePushRequestHeaders(
+      std::string request_url,
+      QuicBackendResponse::ServerPushInfo resource,
+      const spdy::Http2HeaderBlock& original_request_headers);
+
+  // Send PUSH_PROMISE frame on headers stream.
+  void SendPushPromise(QuicStreamId original_stream_id,
+                       QuicStreamId promised_stream_id,
+                       spdy::Http2HeaderBlock headers);
+
+  // Fetch response from cache for request headers enqueued into
+  // promised_headers_and_streams_ and send them on dedicated stream until
+  // reaches max_open_stream_ limit.
+  // Called when return value of GetNumOpenOutgoingStreams() changes:
+  //    CloseStreamInner();
+  //    StreamDraining();
+  // Note that updateFlowControlOnFinalReceivedByteOffset() won't change the
+  // return value becasue all push streams are impossible to become locally
+  // closed. Since a locally preserved stream becomes remotely closed after
+  // HandlePromisedPushRequests() starts to process it, and if it is reset
+  // locally afterwards, it will be immediately become closed and never get into
+  // locally_closed_stream_highest_offset_. So all the streams in this map
+  // are not outgoing streams.
+  void HandlePromisedPushRequests();
+
+  // Keep track of the highest stream id which has been sent in PUSH_PROMISE.
+  QuicStreamId highest_promised_stream_id_;
+
+  // Promised streams which hasn't been created yet because of max_open_stream_
+  // limit. New element is added to the end of the queue.
+  // Since outgoing stream is created in sequence, stream_id of each element in
+  // the queue also increases by 2 from previous one's. The front element's
+  // stream_id is always next_outgoing_stream_id_, and the last one is always
+  // highest_promised_stream_id_.
+  quiche::QuicheCircularDeque<PromisedStreamInfo> promised_streams_;
+
+  QuicSimpleServerBackend* quic_simple_server_backend_;  // Not owned.
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_SESSION_H_
diff --git a/quiche/quic/tools/quic_simple_server_session_test.cc b/quiche/quic/tools/quic_simple_server_session_test.cc
new file mode 100644
index 0000000..2cff502
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_session_test.cc
@@ -0,0 +1,595 @@
+// Copyright 2013 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 "quiche/quic/tools/quic_simple_server_session.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "absl/strings/str_cat.h"
+#include "quiche/quic/core/crypto/null_encrypter.h"
+#include "quiche/quic/core/crypto/quic_crypto_server_config.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/http_encoder.h"
+#include "quiche/quic/core/proto/cached_network_parameters_proto.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_crypto_server_stream.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/core/tls_server_handshaker.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/platform/api/quic_expect_bug.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/test_tools/crypto_test_utils.h"
+#include "quiche/quic/test_tools/mock_quic_session_visitor.h"
+#include "quiche/quic/test_tools/quic_config_peer.h"
+#include "quiche/quic/test_tools/quic_connection_peer.h"
+#include "quiche/quic/test_tools/quic_sent_packet_manager_peer.h"
+#include "quiche/quic/test_tools/quic_session_peer.h"
+#include "quiche/quic/test_tools/quic_spdy_session_peer.h"
+#include "quiche/quic/test_tools/quic_stream_peer.h"
+#include "quiche/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h"
+#include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_memory_cache_backend.h"
+#include "quiche/quic/tools/quic_simple_server_stream.h"
+
+using testing::_;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+// Data to be sent on a request stream.  In Google QUIC, this is interpreted as
+// DATA payload (there is no framing on request streams).  In IETF QUIC, this is
+// interpreted as HEADERS frame (type 0x1) with payload length 122 ('z').  Since
+// no payload is included, QPACK decoder will not be invoked.
+const char* const kStreamData = "\1z";
+
+}  // namespace
+
+class QuicSimpleServerSessionPeer {
+ public:
+  static void SetCryptoStream(QuicSimpleServerSession* s,
+                              QuicCryptoServerStreamBase* crypto_stream) {
+    s->crypto_stream_.reset(crypto_stream);
+  }
+
+  static QuicSpdyStream* CreateIncomingStream(QuicSimpleServerSession* s,
+                                              QuicStreamId id) {
+    return s->CreateIncomingStream(id);
+  }
+
+  static QuicSimpleServerStream* CreateOutgoingUnidirectionalStream(
+      QuicSimpleServerSession* s) {
+    return s->CreateOutgoingUnidirectionalStream();
+  }
+};
+
+namespace {
+
+const size_t kMaxStreamsForTest = 10;
+
+class MockQuicCryptoServerStream : public QuicCryptoServerStream {
+ public:
+  explicit MockQuicCryptoServerStream(
+      const QuicCryptoServerConfig* crypto_config,
+      QuicCompressedCertsCache* compressed_certs_cache,
+      QuicSession* session,
+      QuicCryptoServerStreamBase::Helper* helper)
+      : QuicCryptoServerStream(crypto_config,
+                               compressed_certs_cache,
+                               session,
+                               helper) {}
+  MockQuicCryptoServerStream(const MockQuicCryptoServerStream&) = delete;
+  MockQuicCryptoServerStream& operator=(const MockQuicCryptoServerStream&) =
+      delete;
+  ~MockQuicCryptoServerStream() override {}
+
+  MOCK_METHOD(void,
+              SendServerConfigUpdate,
+              (const CachedNetworkParameters*),
+              (override));
+
+  bool encryption_established() const override { return true; }
+};
+
+class MockTlsServerHandshaker : public TlsServerHandshaker {
+ public:
+  explicit MockTlsServerHandshaker(QuicSession* session,
+                                   const QuicCryptoServerConfig* crypto_config)
+      : TlsServerHandshaker(session, crypto_config) {}
+  MockTlsServerHandshaker(const MockTlsServerHandshaker&) = delete;
+  MockTlsServerHandshaker& operator=(const MockTlsServerHandshaker&) = delete;
+  ~MockTlsServerHandshaker() override {}
+
+  MOCK_METHOD(void,
+              SendServerConfigUpdate,
+              (const CachedNetworkParameters*),
+              (override));
+
+  bool encryption_established() const override { return true; }
+};
+
+QuicCryptoServerStreamBase* CreateMockCryptoServerStream(
+    const QuicCryptoServerConfig* crypto_config,
+    QuicCompressedCertsCache* compressed_certs_cache,
+    QuicSession* session,
+    QuicCryptoServerStreamBase::Helper* helper) {
+  switch (session->connection()->version().handshake_protocol) {
+    case PROTOCOL_QUIC_CRYPTO:
+      return new MockQuicCryptoServerStream(
+          crypto_config, compressed_certs_cache, session, helper);
+    case PROTOCOL_TLS1_3:
+      return new MockTlsServerHandshaker(session, crypto_config);
+    case PROTOCOL_UNSUPPORTED:
+      break;
+  }
+  QUIC_BUG(quic_bug_10933_1)
+      << "Unknown handshake protocol: "
+      << static_cast<int>(session->connection()->version().handshake_protocol);
+  return nullptr;
+}
+
+class MockQuicConnectionWithSendStreamData : public MockQuicConnection {
+ public:
+  MockQuicConnectionWithSendStreamData(
+      MockQuicConnectionHelper* helper,
+      MockAlarmFactory* alarm_factory,
+      Perspective perspective,
+      const ParsedQuicVersionVector& supported_versions)
+      : MockQuicConnection(helper,
+                           alarm_factory,
+                           perspective,
+                           supported_versions) {
+    auto consume_all_data = [](QuicStreamId /*id*/, size_t write_length,
+                               QuicStreamOffset /*offset*/,
+                               StreamSendingState state) {
+      return QuicConsumedData(write_length, state != NO_FIN);
+    };
+    ON_CALL(*this, SendStreamData(_, _, _, _))
+        .WillByDefault(Invoke(consume_all_data));
+  }
+
+  MOCK_METHOD(QuicConsumedData,
+              SendStreamData,
+              (QuicStreamId id,
+               size_t write_length,
+               QuicStreamOffset offset,
+               StreamSendingState state),
+              (override));
+};
+
+class MockQuicSimpleServerSession : public QuicSimpleServerSession {
+ public:
+  MockQuicSimpleServerSession(
+      const QuicConfig& config,
+      QuicConnection* connection,
+      QuicSession::Visitor* visitor,
+      QuicCryptoServerStreamBase::Helper* helper,
+      const QuicCryptoServerConfig* crypto_config,
+      QuicCompressedCertsCache* compressed_certs_cache,
+      QuicSimpleServerBackend* quic_simple_server_backend)
+      : QuicSimpleServerSession(config,
+                                CurrentSupportedVersions(),
+                                connection,
+                                visitor,
+                                helper,
+                                crypto_config,
+                                compressed_certs_cache,
+                                quic_simple_server_backend) {}
+  // Methods taking non-copyable types like Http2HeaderBlock by value cannot be
+  // mocked directly.
+  void WritePushPromise(QuicStreamId original_stream_id,
+                        QuicStreamId promised_stream_id,
+                        spdy::Http2HeaderBlock headers) override {
+    return WritePushPromiseMock(original_stream_id, promised_stream_id,
+                                headers);
+  }
+  MOCK_METHOD(void,
+              WritePushPromiseMock,
+              (QuicStreamId original_stream_id,
+               QuicStreamId promised_stream_id,
+               const spdy::Http2HeaderBlock& headers),
+              ());
+
+  MOCK_METHOD(void, SendBlocked, (QuicStreamId), (override));
+  MOCK_METHOD(bool,
+              WriteControlFrame,
+              (const QuicFrame& frame, TransmissionType type),
+              (override));
+};
+
+class QuicSimpleServerSessionTest
+    : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+  // The function ensures that A) the MAX_STREAMS frames get properly deleted
+  // (since the test uses a 'did we leak memory' check ... if we just lose the
+  // frame, the test fails) and B) returns true (instead of the default, false)
+  // which ensures that the rest of the system thinks that the frame actually
+  // was transmitted.
+  bool ClearMaxStreamsControlFrame(const QuicFrame& frame) {
+    if (frame.type == MAX_STREAMS_FRAME) {
+      DeleteFrame(&const_cast<QuicFrame&>(frame));
+      return true;
+    }
+    return false;
+  }
+
+ protected:
+  QuicSimpleServerSessionTest()
+      : crypto_config_(QuicCryptoServerConfig::TESTING,
+                       QuicRandom::GetInstance(),
+                       crypto_test_utils::ProofSourceForTesting(),
+                       KeyExchangeSource::Default()),
+        compressed_certs_cache_(
+            QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) {
+    config_.SetMaxBidirectionalStreamsToSend(kMaxStreamsForTest);
+    QuicConfigPeer::SetReceivedMaxBidirectionalStreams(&config_,
+                                                       kMaxStreamsForTest);
+    config_.SetMaxUnidirectionalStreamsToSend(kMaxStreamsForTest);
+
+    config_.SetInitialStreamFlowControlWindowToSend(
+        kInitialStreamFlowControlWindowForTest);
+    config_.SetInitialMaxStreamDataBytesIncomingBidirectionalToSend(
+        kInitialStreamFlowControlWindowForTest);
+    config_.SetInitialMaxStreamDataBytesOutgoingBidirectionalToSend(
+        kInitialStreamFlowControlWindowForTest);
+    config_.SetInitialMaxStreamDataBytesUnidirectionalToSend(
+        kInitialStreamFlowControlWindowForTest);
+    config_.SetInitialSessionFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    if (VersionUsesHttp3(transport_version())) {
+      QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(
+          &config_, kMaxStreamsForTest + 3);
+    } else {
+      QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(&config_,
+                                                          kMaxStreamsForTest);
+    }
+
+    ParsedQuicVersionVector supported_versions = SupportedVersions(version());
+    connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>(
+        &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions);
+    connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    connection_->SetEncrypter(
+        ENCRYPTION_FORWARD_SECURE,
+        std::make_unique<NullEncrypter>(connection_->perspective()));
+    session_ = std::make_unique<MockQuicSimpleServerSession>(
+        config_, connection_, &owner_, &stream_helper_, &crypto_config_,
+        &compressed_certs_cache_, &memory_cache_backend_);
+    MockClock clock;
+    handshake_message_ = crypto_config_.AddDefaultConfig(
+        QuicRandom::GetInstance(), &clock,
+        QuicCryptoServerConfig::ConfigOptions());
+    session_->Initialize();
+
+    if (VersionHasIetfQuicFrames(transport_version())) {
+      EXPECT_CALL(*session_, WriteControlFrame(_, _))
+          .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
+    }
+    session_->OnConfigNegotiated();
+  }
+
+  QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+    return GetNthClientInitiatedBidirectionalStreamId(transport_version(), n);
+  }
+
+  QuicStreamId GetNthServerInitiatedUnidirectionalId(int n) {
+    return quic::test::GetNthServerInitiatedUnidirectionalStreamId(
+        transport_version(), n);
+  }
+
+  ParsedQuicVersion version() const { return GetParam(); }
+
+  QuicTransportVersion transport_version() const {
+    return version().transport_version;
+  }
+
+  void InjectStopSending(QuicStreamId stream_id,
+                         QuicRstStreamErrorCode rst_stream_code) {
+    // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+    // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+    // a one-way close.
+    if (!VersionHasIetfQuicFrames(transport_version())) {
+      // Only needed for version 99/IETF QUIC.
+      return;
+    }
+    EXPECT_CALL(owner_, OnStopSendingReceived(_)).Times(1);
+    QuicStopSendingFrame stop_sending(kInvalidControlFrameId, stream_id,
+                                      rst_stream_code);
+    // Expect the RESET_STREAM that is generated in response to receiving a
+    // STOP_SENDING.
+    EXPECT_CALL(*connection_, OnStreamReset(stream_id, rst_stream_code));
+    session_->OnStopSendingFrame(stop_sending);
+  }
+
+  StrictMock<MockQuicSessionVisitor> owner_;
+  StrictMock<MockQuicCryptoServerStreamHelper> stream_helper_;
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnectionWithSendStreamData>* connection_;
+  QuicConfig config_;
+  QuicCryptoServerConfig crypto_config_;
+  QuicCompressedCertsCache compressed_certs_cache_;
+  QuicMemoryCacheBackend memory_cache_backend_;
+  std::unique_ptr<MockQuicSimpleServerSession> session_;
+  std::unique_ptr<CryptoHandshakeMessage> handshake_message_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+                         QuicSimpleServerSessionTest,
+                         ::testing::ValuesIn(AllSupportedVersions()),
+                         ::testing::PrintToStringParamName());
+
+TEST_P(QuicSimpleServerSessionTest, CloseStreamDueToReset) {
+  // Send some data open a stream, then reset it.
+  QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        kStreamData);
+  session_->OnStreamFrame(data1);
+  EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+
+  // Receive a reset (and send a RST in response).
+  QuicRstStreamFrame rst1(kInvalidControlFrameId,
+                          GetNthClientInitiatedBidirectionalId(0),
+                          QUIC_ERROR_PROCESSING_STREAM, 0);
+  EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+  EXPECT_CALL(*session_, WriteControlFrame(_, _));
+
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    // For version 99, this is covered in InjectStopSending()
+    EXPECT_CALL(*connection_,
+                OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+                              QUIC_RST_ACKNOWLEDGEMENT));
+  }
+  session_->OnRstStream(rst1);
+  // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+  // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+  // a one-way close.
+  InjectStopSending(GetNthClientInitiatedBidirectionalId(0),
+                    QUIC_ERROR_PROCESSING_STREAM);
+  EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+
+  // Send the same two bytes of payload in a new packet.
+  session_->OnStreamFrame(data1);
+
+  // The stream should not be re-opened.
+  EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+  EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSimpleServerSessionTest, NeverOpenStreamDueToReset) {
+  // Send a reset (and expect the peer to send a RST in response).
+  QuicRstStreamFrame rst1(kInvalidControlFrameId,
+                          GetNthClientInitiatedBidirectionalId(0),
+                          QUIC_ERROR_PROCESSING_STREAM, 0);
+  EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    EXPECT_CALL(*session_, WriteControlFrame(_, _));
+    // For version 99, this is covered in InjectStopSending()
+    EXPECT_CALL(*connection_,
+                OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+                              QUIC_RST_ACKNOWLEDGEMENT));
+  }
+  session_->OnRstStream(rst1);
+  // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+  // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+  // a one-way close.
+  InjectStopSending(GetNthClientInitiatedBidirectionalId(0),
+                    QUIC_ERROR_PROCESSING_STREAM);
+
+  EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+
+  QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        kStreamData);
+  session_->OnStreamFrame(data1);
+
+  // The stream should never be opened, now that the reset is received.
+  EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+  EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSimpleServerSessionTest, AcceptClosedStream) {
+  // Send some data to open two streams.
+  QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                         kStreamData);
+  QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+                         kStreamData);
+  session_->OnStreamFrame(frame1);
+  session_->OnStreamFrame(frame2);
+  EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+
+  // Send a reset (and expect the peer to send a RST in response).
+  QuicRstStreamFrame rst(kInvalidControlFrameId,
+                         GetNthClientInitiatedBidirectionalId(0),
+                         QUIC_ERROR_PROCESSING_STREAM, 0);
+  EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    EXPECT_CALL(*session_, WriteControlFrame(_, _));
+    // For version 99, this is covered in InjectStopSending()
+    EXPECT_CALL(*connection_,
+                OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+                              QUIC_RST_ACKNOWLEDGEMENT));
+  }
+  session_->OnRstStream(rst);
+  // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+  // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+  // a one-way close.
+  InjectStopSending(GetNthClientInitiatedBidirectionalId(0),
+                    QUIC_ERROR_PROCESSING_STREAM);
+
+  // If we were tracking, we'd probably want to reject this because it's data
+  // past the reset point of stream 3.  As it's a closed stream we just drop the
+  // data on the floor, but accept the packet because it has data for stream 5.
+  QuicStreamFrame frame3(GetNthClientInitiatedBidirectionalId(0), false, 2,
+                         kStreamData);
+  QuicStreamFrame frame4(GetNthClientInitiatedBidirectionalId(1), false, 2,
+                         kStreamData);
+  session_->OnStreamFrame(frame3);
+  session_->OnStreamFrame(frame4);
+  // The stream should never be opened, now that the reset is received.
+  EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+  EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateIncomingStreamDisconnected) {
+  // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+  if (version() != AllSupportedVersions()[0]) {
+    return;
+  }
+
+  // Tests that incoming stream creation fails when connection is not connected.
+  size_t initial_num_open_stream =
+      QuicSessionPeer::GetNumOpenDynamicStreams(session_.get());
+  QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+  EXPECT_QUIC_BUG(QuicSimpleServerSessionPeer::CreateIncomingStream(
+                      session_.get(), GetNthClientInitiatedBidirectionalId(0)),
+                  "ShouldCreateIncomingStream called when disconnected");
+  EXPECT_EQ(initial_num_open_stream,
+            QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateIncomingStream) {
+  QuicSpdyStream* stream = QuicSimpleServerSessionPeer::CreateIncomingStream(
+      session_.get(), GetNthClientInitiatedBidirectionalId(0));
+  EXPECT_NE(nullptr, stream);
+  EXPECT_EQ(GetNthClientInitiatedBidirectionalId(0), stream->id());
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamDisconnected) {
+  // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+  if (version() != AllSupportedVersions()[0]) {
+    return;
+  }
+
+  // Tests that outgoing stream creation fails when connection is not connected.
+  size_t initial_num_open_stream =
+      QuicSessionPeer::GetNumOpenDynamicStreams(session_.get());
+  QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+  EXPECT_QUIC_BUG(
+      QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+          session_.get()),
+      "ShouldCreateOutgoingUnidirectionalStream called when disconnected");
+
+  EXPECT_EQ(initial_num_open_stream,
+            QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamUnencrypted) {
+  // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+  if (version() != AllSupportedVersions()[0]) {
+    return;
+  }
+
+  // Tests that outgoing stream creation fails when encryption has not yet been
+  // established.
+  size_t initial_num_open_stream =
+      QuicSessionPeer::GetNumOpenDynamicStreams(session_.get());
+  EXPECT_QUIC_BUG(
+      QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+          session_.get()),
+      "Encryption not established so no outgoing stream created.");
+  EXPECT_EQ(initial_num_open_stream,
+            QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamUptoLimit) {
+  // Tests that outgoing stream creation should not be affected by existing
+  // incoming stream and vice-versa. But when reaching the limit of max outgoing
+  // stream allowed, creation should fail.
+
+  // Receive some data to initiate a incoming stream which should not effect
+  // creating outgoing streams.
+  QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        kStreamData);
+  session_->OnStreamFrame(data1);
+  EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+  EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) -
+                    /*incoming=*/1);
+
+  if (!VersionUsesHttp3(transport_version())) {
+    session_->UnregisterStreamPriority(
+        QuicUtils::GetHeadersStreamId(transport_version()),
+        /*is_static=*/true);
+  }
+  // Assume encryption already established.
+  QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), nullptr);
+  QuicCryptoServerStreamBase* crypto_stream =
+      CreateMockCryptoServerStream(&crypto_config_, &compressed_certs_cache_,
+                                   session_.get(), &stream_helper_);
+  QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), crypto_stream);
+  if (!VersionUsesHttp3(transport_version())) {
+    session_->RegisterStreamPriority(
+        QuicUtils::GetHeadersStreamId(transport_version()),
+        /*is_static=*/true,
+        spdy::SpdyStreamPrecedence(QuicStream::kDefaultPriority));
+  }
+
+  // Create push streams till reaching the upper limit of allowed open streams.
+  for (size_t i = 0; i < kMaxStreamsForTest; ++i) {
+    QuicSpdyStream* created_stream =
+        QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+            session_.get());
+    if (VersionUsesHttp3(transport_version())) {
+      EXPECT_EQ(GetNthServerInitiatedUnidirectionalId(i + 3),
+                created_stream->id());
+    } else {
+      EXPECT_EQ(GetNthServerInitiatedUnidirectionalId(i), created_stream->id());
+    }
+    EXPECT_EQ(i + 1, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) -
+                         /*incoming=*/1);
+  }
+
+  // Continuing creating push stream would fail.
+  EXPECT_EQ(nullptr,
+            QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+                session_.get()));
+  EXPECT_EQ(kMaxStreamsForTest,
+            QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) -
+                /*incoming=*/1);
+
+  // Create peer initiated stream should have no problem.
+  QuicStreamFrame data2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+                        kStreamData);
+  session_->OnStreamFrame(data2);
+  EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) -
+                    /*outcoming=*/kMaxStreamsForTest);
+}
+
+TEST_P(QuicSimpleServerSessionTest, OnStreamFrameWithEvenStreamId) {
+  QuicStreamFrame frame(GetNthServerInitiatedUnidirectionalId(0), false, 0,
+                        kStreamData);
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_STREAM_ID,
+                              "Client sent data on server push stream", _));
+  session_->OnStreamFrame(frame);
+}
+
+// Tests that calling GetOrCreateStream() on an outgoing stream not promised yet
+// should result close connection.
+TEST_P(QuicSimpleServerSessionTest, GetEvenIncomingError) {
+  const size_t initial_num_open_stream =
+      QuicSessionPeer::GetNumOpenDynamicStreams(session_.get());
+  const QuicErrorCode expected_error = VersionUsesHttp3(transport_version())
+                                           ? QUIC_HTTP_STREAM_WRONG_DIRECTION
+                                           : QUIC_INVALID_STREAM_ID;
+  EXPECT_CALL(*connection_, CloseConnection(expected_error,
+                                            "Data for nonexistent stream", _));
+  EXPECT_EQ(nullptr,
+            QuicSessionPeer::GetOrCreateStream(
+                session_.get(), GetNthServerInitiatedUnidirectionalId(3)));
+  EXPECT_EQ(initial_num_open_stream,
+            QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_simple_server_stream.cc b/quiche/quic/tools/quic_simple_server_stream.cc
new file mode 100644
index 0000000..ab9622e
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_stream.cc
@@ -0,0 +1,429 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_simple_server_stream.h"
+
+#include <list>
+#include <utility>
+
+#include "absl/strings/match.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/http/quic_spdy_stream.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/http/web_transport_http3.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/tools/quic_simple_server_session.h"
+#include "quiche/spdy/core/spdy_protocol.h"
+
+using spdy::Http2HeaderBlock;
+
+namespace quic {
+
+QuicSimpleServerStream::QuicSimpleServerStream(
+    QuicStreamId id,
+    QuicSpdySession* session,
+    StreamType type,
+    QuicSimpleServerBackend* quic_simple_server_backend)
+    : QuicSpdyServerStreamBase(id, session, type),
+      content_length_(-1),
+      generate_bytes_length_(0),
+      quic_simple_server_backend_(quic_simple_server_backend) {
+  QUICHE_DCHECK(quic_simple_server_backend_);
+}
+
+QuicSimpleServerStream::QuicSimpleServerStream(
+    PendingStream* pending, QuicSpdySession* session,
+    QuicSimpleServerBackend* quic_simple_server_backend)
+    : QuicSpdyServerStreamBase(pending, session),
+      content_length_(-1),
+      generate_bytes_length_(0),
+      quic_simple_server_backend_(quic_simple_server_backend) {
+  QUICHE_DCHECK(quic_simple_server_backend_);
+}
+
+QuicSimpleServerStream::~QuicSimpleServerStream() {
+  quic_simple_server_backend_->CloseBackendResponseStream(this);
+}
+
+void QuicSimpleServerStream::OnInitialHeadersComplete(
+    bool fin,
+    size_t frame_len,
+    const QuicHeaderList& header_list) {
+  QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list);
+  // QuicSpdyStream::OnInitialHeadersComplete() may have already sent error
+  // response.
+  if (!response_sent_ &&
+      !SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_,
+                                         &request_headers_)) {
+    QUIC_DVLOG(1) << "Invalid headers";
+      SendErrorResponse();
+  }
+  ConsumeHeaderList();
+  if (!fin && !response_sent_) {
+    // CONNECT and other CONNECT-like methods (such as CONNECT-UDP) require
+    // sending the response right after parsing the headers even though the FIN
+    // bit has not been received on the request stream.
+    auto it = request_headers_.find(":method");
+    if (it != request_headers_.end() &&
+        absl::StartsWith(it->second, "CONNECT")) {
+      SendResponse();
+    }
+  }
+}
+
+void QuicSimpleServerStream::OnTrailingHeadersComplete(
+    bool /*fin*/,
+    size_t /*frame_len*/,
+    const QuicHeaderList& /*header_list*/) {
+  QUIC_BUG(quic_bug_10962_1) << "Server does not support receiving Trailers.";
+  SendErrorResponse();
+}
+
+void QuicSimpleServerStream::OnBodyAvailable() {
+  while (HasBytesToRead()) {
+    struct iovec iov;
+    if (GetReadableRegions(&iov, 1) == 0) {
+      // No more data to read.
+      break;
+    }
+    QUIC_DVLOG(1) << "Stream " << id() << " processed " << iov.iov_len
+                  << " bytes.";
+    body_.append(static_cast<char*>(iov.iov_base), iov.iov_len);
+
+    if (content_length_ >= 0 &&
+        body_.size() > static_cast<uint64_t>(content_length_)) {
+      QUIC_DVLOG(1) << "Body size (" << body_.size() << ") > content length ("
+                    << content_length_ << ").";
+      SendErrorResponse();
+      return;
+    }
+    MarkConsumed(iov.iov_len);
+  }
+  if (!sequencer()->IsClosed()) {
+    sequencer()->SetUnblocked();
+    return;
+  }
+
+  // If the sequencer is closed, then all the body, including the fin, has been
+  // consumed.
+  OnFinRead();
+
+  if (write_side_closed() || fin_buffered()) {
+    return;
+  }
+
+  SendResponse();
+}
+
+void QuicSimpleServerStream::PushResponse(
+    Http2HeaderBlock push_request_headers) {
+  if (QuicUtils::IsClientInitiatedStreamId(session()->transport_version(),
+                                           id())) {
+    QUIC_BUG(quic_bug_10962_2)
+        << "Client initiated stream shouldn't be used as promised stream.";
+    return;
+  }
+  // Change the stream state to emulate a client request.
+  request_headers_ = std::move(push_request_headers);
+  content_length_ = 0;
+  QUIC_DVLOG(1) << "Stream " << id()
+                << " ready to receive server push response.";
+  QUICHE_DCHECK(reading_stopped());
+
+  // Directly send response based on the emulated request_headers_.
+  SendResponse();
+}
+
+void QuicSimpleServerStream::SendResponse() {
+  if (request_headers_.empty()) {
+    QUIC_DVLOG(1) << "Request headers empty.";
+    SendErrorResponse();
+    return;
+  }
+
+  if (content_length_ > 0 &&
+      static_cast<uint64_t>(content_length_) != body_.size()) {
+    QUIC_DVLOG(1) << "Content length (" << content_length_ << ") != body size ("
+                  << body_.size() << ").";
+    SendErrorResponse();
+    return;
+  }
+
+  if (!request_headers_.contains(":authority")) {
+    QUIC_DVLOG(1) << "Request headers do not contain :authority.";
+    SendErrorResponse();
+    return;
+  }
+
+  if (!request_headers_.contains(":path")) {
+    // CONNECT and other CONNECT-like methods (such as CONNECT-UDP) do not all
+    // require :path to be present.
+    auto it = request_headers_.find(":method");
+    if (it == request_headers_.end() ||
+        !absl::StartsWith(it->second, "CONNECT")) {
+      QUIC_DVLOG(1) << "Request headers do not contain :path.";
+      SendErrorResponse();
+      return;
+    }
+  }
+
+  if (quic_simple_server_backend_ == nullptr) {
+    QUIC_DVLOG(1) << "Backend is missing.";
+    SendErrorResponse();
+    return;
+  }
+
+  if (web_transport() != nullptr) {
+    QuicSimpleServerBackend::WebTransportResponse response =
+        quic_simple_server_backend_->ProcessWebTransportRequest(
+            request_headers_, web_transport());
+    if (response.response_headers[":status"] == "200") {
+      WriteHeaders(std::move(response.response_headers), false, nullptr);
+      if (response.visitor != nullptr) {
+        web_transport()->SetVisitor(std::move(response.visitor));
+      }
+      web_transport()->HeadersReceived(request_headers_);
+    } else {
+      WriteHeaders(std::move(response.response_headers), true, nullptr);
+    }
+    return;
+  }
+
+  // Fetch the response from the backend interface and wait for callback once
+  // response is ready
+  quic_simple_server_backend_->FetchResponseFromBackend(request_headers_, body_,
+                                                        this);
+}
+
+QuicConnectionId QuicSimpleServerStream::connection_id() const {
+  return spdy_session()->connection_id();
+}
+
+QuicStreamId QuicSimpleServerStream::stream_id() const {
+  return id();
+}
+
+std::string QuicSimpleServerStream::peer_host() const {
+  return spdy_session()->peer_address().host().ToString();
+}
+
+void QuicSimpleServerStream::OnResponseBackendComplete(
+    const QuicBackendResponse* response) {
+  if (response == nullptr) {
+    QUIC_DVLOG(1) << "Response not found in cache.";
+    SendNotFoundResponse();
+    return;
+  }
+
+  // Send Early Hints first.
+  for (const auto& headers : response->early_hints()) {
+    QUIC_DVLOG(1) << "Stream " << id() << " sending an Early Hints response: "
+                  << headers.DebugString();
+    WriteHeaders(headers.Clone(), false, nullptr);
+  }
+
+  if (response->response_type() == QuicBackendResponse::CLOSE_CONNECTION) {
+    QUIC_DVLOG(1) << "Special response: closing connection.";
+    OnUnrecoverableError(QUIC_NO_ERROR, "Toy server forcing close");
+    return;
+  }
+
+  if (response->response_type() == QuicBackendResponse::IGNORE_REQUEST) {
+    QUIC_DVLOG(1) << "Special response: ignoring request.";
+    return;
+  }
+
+  if (response->response_type() == QuicBackendResponse::BACKEND_ERR_RESPONSE) {
+    QUIC_DVLOG(1) << "Quic Proxy: Backend connection error.";
+    /*502 Bad Gateway
+      The server was acting as a gateway or proxy and received an
+      invalid response from the upstream server.*/
+    SendErrorResponse(502);
+    return;
+  }
+
+  // Examing response status, if it was not pure integer as typical h2
+  // response status, send error response. Notice that
+  // QuicHttpResponseCache push urls are strictly authority + path only,
+  // scheme is not included (see |QuicHttpResponseCache::GetKey()|).
+  std::string request_url = request_headers_[":authority"].as_string() +
+                            request_headers_[":path"].as_string();
+  int response_code;
+  const Http2HeaderBlock& response_headers = response->headers();
+  if (!ParseHeaderStatusCode(response_headers, &response_code)) {
+    auto status = response_headers.find(":status");
+    if (status == response_headers.end()) {
+      QUIC_LOG(WARNING)
+          << ":status not present in response from cache for request "
+          << request_url;
+    } else {
+      QUIC_LOG(WARNING) << "Illegal (non-integer) response :status from cache: "
+                        << status->second << " for request " << request_url;
+    }
+    SendErrorResponse();
+    return;
+  }
+
+  if (QuicUtils::IsServerInitiatedStreamId(session()->transport_version(),
+                                           id())) {
+    // A server initiated stream is only used for a server push response,
+    // and only 200 and 30X response codes are supported for server push.
+    // This behavior mirrors the HTTP/2 implementation.
+    bool is_redirection = response_code / 100 == 3;
+    if (response_code != 200 && !is_redirection) {
+      QUIC_LOG(WARNING) << "Response to server push request " << request_url
+                        << " result in response code " << response_code;
+      Reset(QUIC_STREAM_CANCELLED);
+      return;
+    }
+  }
+
+  if (response->response_type() == QuicBackendResponse::INCOMPLETE_RESPONSE) {
+    QUIC_DVLOG(1)
+        << "Stream " << id()
+        << " sending an incomplete response, i.e. no trailer, no fin.";
+    SendIncompleteResponse(response->headers().Clone(), response->body());
+    return;
+  }
+
+  if (response->response_type() == QuicBackendResponse::GENERATE_BYTES) {
+    QUIC_DVLOG(1) << "Stream " << id() << " sending a generate bytes response.";
+    std::string path = request_headers_[":path"].as_string().substr(1);
+    if (!absl::SimpleAtoi(path, &generate_bytes_length_)) {
+      QUIC_LOG(ERROR) << "Path is not a number.";
+      SendNotFoundResponse();
+      return;
+    }
+    Http2HeaderBlock headers = response->headers().Clone();
+    headers["content-length"] = absl::StrCat(generate_bytes_length_);
+
+    WriteHeaders(std::move(headers), false, nullptr);
+    QUICHE_DCHECK(!response_sent_);
+    response_sent_ = true;
+
+    WriteGeneratedBytes();
+
+    return;
+  }
+
+  QUIC_DVLOG(1) << "Stream " << id() << " sending response.";
+  SendHeadersAndBodyAndTrailers(response->headers().Clone(), response->body(),
+                                response->trailers().Clone());
+}
+
+void QuicSimpleServerStream::OnCanWrite() {
+  QuicSpdyStream::OnCanWrite();
+  WriteGeneratedBytes();
+}
+
+void QuicSimpleServerStream::WriteGeneratedBytes() {
+  static size_t kChunkSize = 1024;
+  while (!HasBufferedData() && generate_bytes_length_ > 0) {
+    size_t len = std::min<size_t>(kChunkSize, generate_bytes_length_);
+    std::string data(len, 'a');
+    generate_bytes_length_ -= len;
+    bool fin = generate_bytes_length_ == 0;
+    WriteOrBufferBody(data, fin);
+  }
+}
+
+void QuicSimpleServerStream::SendNotFoundResponse() {
+  QUIC_DVLOG(1) << "Stream " << id() << " sending not found response.";
+  Http2HeaderBlock headers;
+  headers[":status"] = "404";
+  headers["content-length"] = absl::StrCat(strlen(kNotFoundResponseBody));
+  SendHeadersAndBody(std::move(headers), kNotFoundResponseBody);
+}
+
+void QuicSimpleServerStream::SendErrorResponse() {
+  SendErrorResponse(0);
+}
+
+void QuicSimpleServerStream::SendErrorResponse(int resp_code) {
+  QUIC_DVLOG(1) << "Stream " << id() << " sending error response.";
+  if (!reading_stopped()) {
+    StopReading();
+  }
+  Http2HeaderBlock headers;
+  if (resp_code <= 0) {
+    headers[":status"] = "500";
+  } else {
+    headers[":status"] = absl::StrCat(resp_code);
+  }
+  headers["content-length"] = absl::StrCat(strlen(kErrorResponseBody));
+  SendHeadersAndBody(std::move(headers), kErrorResponseBody);
+}
+
+void QuicSimpleServerStream::SendIncompleteResponse(
+    Http2HeaderBlock response_headers,
+    absl::string_view body) {
+  QUIC_DLOG(INFO) << "Stream " << id() << " writing headers (fin = false) : "
+                  << response_headers.DebugString();
+  WriteHeaders(std::move(response_headers), /*fin=*/false, nullptr);
+  QUICHE_DCHECK(!response_sent_);
+  response_sent_ = true;
+
+  QUIC_DLOG(INFO) << "Stream " << id()
+                  << " writing body (fin = false) with size: " << body.size();
+  if (!body.empty()) {
+    WriteOrBufferBody(body, /*fin=*/false);
+  }
+}
+
+void QuicSimpleServerStream::SendHeadersAndBody(
+    Http2HeaderBlock response_headers,
+    absl::string_view body) {
+  SendHeadersAndBodyAndTrailers(std::move(response_headers), body,
+                                Http2HeaderBlock());
+}
+
+void QuicSimpleServerStream::SendHeadersAndBodyAndTrailers(
+    Http2HeaderBlock response_headers,
+    absl::string_view body,
+    Http2HeaderBlock response_trailers) {
+  // Send the headers, with a FIN if there's nothing else to send.
+  bool send_fin = (body.empty() && response_trailers.empty());
+  QUIC_DLOG(INFO) << "Stream " << id() << " writing headers (fin = " << send_fin
+                  << ") : " << response_headers.DebugString();
+  WriteHeaders(std::move(response_headers), send_fin, nullptr);
+  QUICHE_DCHECK(!response_sent_);
+  response_sent_ = true;
+  if (send_fin) {
+    // Nothing else to send.
+    return;
+  }
+
+  // Send the body, with a FIN if there's no trailers to send.
+  send_fin = response_trailers.empty();
+  QUIC_DLOG(INFO) << "Stream " << id() << " writing body (fin = " << send_fin
+                  << ") with size: " << body.size();
+  if (!body.empty() || send_fin) {
+    WriteOrBufferBody(body, send_fin);
+  }
+  if (send_fin) {
+    // Nothing else to send.
+    return;
+  }
+
+  // Send the trailers. A FIN is always sent with trailers.
+  QUIC_DLOG(INFO) << "Stream " << id() << " writing trailers (fin = true): "
+                  << response_trailers.DebugString();
+  WriteTrailers(std::move(response_trailers), nullptr);
+}
+
+void QuicSimpleServerStream::OnInvalidHeaders() {
+  QUIC_DVLOG(1) << "Invalid headers";
+  SendErrorResponse(400);
+}
+
+const char* const QuicSimpleServerStream::kErrorResponseBody = "bad";
+const char* const QuicSimpleServerStream::kNotFoundResponseBody =
+    "file not found";
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_simple_server_stream.h b/quiche/quic/tools/quic_simple_server_stream.h
new file mode 100644
index 0000000..841437d
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_stream.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_STREAM_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_STREAM_H_
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/http/quic_spdy_server_stream_base.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+// All this does right now is aggregate data, and on fin, send an HTTP
+// response.
+class QuicSimpleServerStream : public QuicSpdyServerStreamBase,
+                               public QuicSimpleServerBackend::RequestHandler {
+ public:
+  QuicSimpleServerStream(QuicStreamId id,
+                         QuicSpdySession* session,
+                         StreamType type,
+                         QuicSimpleServerBackend* quic_simple_server_backend);
+  QuicSimpleServerStream(PendingStream* pending,
+                         QuicSpdySession* session,
+                         QuicSimpleServerBackend* quic_simple_server_backend);
+  QuicSimpleServerStream(const QuicSimpleServerStream&) = delete;
+  QuicSimpleServerStream& operator=(const QuicSimpleServerStream&) = delete;
+  ~QuicSimpleServerStream() override;
+
+  // QuicSpdyStream
+  void OnInitialHeadersComplete(bool fin,
+                                size_t frame_len,
+                                const QuicHeaderList& header_list) override;
+  void OnTrailingHeadersComplete(bool fin,
+                                 size_t frame_len,
+                                 const QuicHeaderList& header_list) override;
+  void OnCanWrite() override;
+
+  // QuicStream implementation called by the sequencer when there is
+  // data (or a FIN) to be read.
+  void OnBodyAvailable() override;
+
+  void OnInvalidHeaders() override;
+
+  // Make this stream start from as if it just finished parsing an incoming
+  // request whose headers are equivalent to |push_request_headers|.
+  // Doing so will trigger this toy stream to fetch response and send it back.
+  virtual void PushResponse(spdy::Http2HeaderBlock push_request_headers);
+
+  // The response body of error responses.
+  static const char* const kErrorResponseBody;
+  static const char* const kNotFoundResponseBody;
+
+  // Implements QuicSimpleServerBackend::RequestHandler callbacks
+  QuicConnectionId connection_id() const override;
+  QuicStreamId stream_id() const override;
+  std::string peer_host() const override;
+  void OnResponseBackendComplete(const QuicBackendResponse* response) override;
+
+ protected:
+  // Sends a basic 200 response using SendHeaders for the headers and WriteData
+  // for the body.
+  virtual void SendResponse();
+
+  // Sends a basic 500 response using SendHeaders for the headers and WriteData
+  // for the body.
+  virtual void SendErrorResponse();
+  virtual void SendErrorResponse(int resp_code);
+
+  // Sends a basic 404 response using SendHeaders for the headers and WriteData
+  // for the body.
+  void SendNotFoundResponse();
+
+  // Sends the response header and body, but not the fin.
+  void SendIncompleteResponse(spdy::Http2HeaderBlock response_headers,
+                              absl::string_view body);
+
+  void SendHeadersAndBody(spdy::Http2HeaderBlock response_headers,
+                          absl::string_view body);
+  void SendHeadersAndBodyAndTrailers(spdy::Http2HeaderBlock response_headers,
+                                     absl::string_view body,
+                                     spdy::Http2HeaderBlock response_trailers);
+
+  spdy::Http2HeaderBlock* request_headers() { return &request_headers_; }
+
+  const std::string& body() { return body_; }
+
+  // Writes the body bytes for the GENERATE_BYTES response type.
+  void WriteGeneratedBytes();
+
+  // The parsed headers received from the client.
+  spdy::Http2HeaderBlock request_headers_;
+  int64_t content_length_;
+  std::string body_;
+
+ private:
+  uint64_t generate_bytes_length_;
+  // Whether response headers have already been sent.
+  bool response_sent_ = false;
+
+  QuicSimpleServerBackend* quic_simple_server_backend_;  // Not owned.
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SIMPLE_SERVER_STREAM_H_
diff --git a/quiche/quic/tools/quic_simple_server_stream_test.cc b/quiche/quic/tools/quic_simple_server_stream_test.cc
new file mode 100644
index 0000000..c2aa58e
--- /dev/null
+++ b/quiche/quic/tools/quic_simple_server_stream_test.cc
@@ -0,0 +1,790 @@
+// Copyright (c) 2013 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 "quiche/quic/tools/quic_simple_server_stream.h"
+
+#include <list>
+#include <memory>
+#include <utility>
+
+#include "absl/base/macros.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "quiche/quic/core/crypto/null_encrypter.h"
+#include "quiche/quic/core/http/http_encoder.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_error_codes.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_expect_bug.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/test_tools/crypto_test_utils.h"
+#include "quiche/quic/test_tools/quic_config_peer.h"
+#include "quiche/quic/test_tools/quic_connection_peer.h"
+#include "quiche/quic/test_tools/quic_session_peer.h"
+#include "quiche/quic/test_tools/quic_spdy_session_peer.h"
+#include "quiche/quic/test_tools/quic_stream_peer.h"
+#include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/quic/tools/quic_backend_response.h"
+#include "quiche/quic/tools/quic_memory_cache_backend.h"
+#include "quiche/quic/tools/quic_simple_server_session.h"
+#include "quiche/common/simple_buffer_allocator.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::Invoke;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+
+const size_t kFakeFrameLen = 60;
+const size_t kErrorLength = strlen(QuicSimpleServerStream::kErrorResponseBody);
+const size_t kDataFrameHeaderLength = 2;
+
+class TestStream : public QuicSimpleServerStream {
+ public:
+  TestStream(QuicStreamId stream_id, QuicSpdySession* session, StreamType type,
+             QuicSimpleServerBackend* quic_simple_server_backend)
+      : QuicSimpleServerStream(stream_id, session, type,
+                               quic_simple_server_backend) {}
+
+  ~TestStream() override = default;
+
+  MOCK_METHOD(void, WriteHeadersMock, (bool fin), ());
+  MOCK_METHOD(void, WriteEarlyHintsHeadersMock, (bool fin), ());
+
+  size_t WriteHeaders(
+      spdy::Http2HeaderBlock header_block, bool fin,
+      quiche::QuicheReferenceCountedPointer<QuicAckListenerInterface>
+      /*ack_listener*/) override {
+    if (header_block[":status"] == "103") {
+      WriteEarlyHintsHeadersMock(fin);
+    } else {
+      WriteHeadersMock(fin);
+    }
+    return 0;
+  }
+
+  // Expose protected QuicSimpleServerStream methods.
+  void DoSendResponse() { SendResponse(); }
+  void DoSendErrorResponse() { QuicSimpleServerStream::SendErrorResponse(); }
+
+  spdy::Http2HeaderBlock* mutable_headers() { return &request_headers_; }
+  void set_body(std::string body) { body_ = std::move(body); }
+  const std::string& body() const { return body_; }
+  int content_length() const { return content_length_; }
+  bool send_response_was_called() const { return send_response_was_called_; }
+  bool send_error_response_was_called() const {
+    return send_error_response_was_called_;
+  }
+
+  absl::string_view GetHeader(absl::string_view key) const {
+    auto it = request_headers_.find(key);
+    QUICHE_DCHECK(it != request_headers_.end());
+    return it->second;
+  }
+
+ protected:
+  void SendResponse() override {
+    send_response_was_called_ = true;
+    QuicSimpleServerStream::SendResponse();
+  }
+
+  void SendErrorResponse(int resp_code) override {
+    send_error_response_was_called_ = true;
+    QuicSimpleServerStream::SendErrorResponse(resp_code);
+  }
+
+ private:
+  bool send_response_was_called_ = false;
+  bool send_error_response_was_called_ = false;
+};
+
+namespace {
+
+class MockQuicSimpleServerSession : public QuicSimpleServerSession {
+ public:
+  const size_t kMaxStreamsForTest = 100;
+
+  MockQuicSimpleServerSession(
+      QuicConnection* connection, MockQuicSessionVisitor* owner,
+      MockQuicCryptoServerStreamHelper* helper,
+      QuicCryptoServerConfig* crypto_config,
+      QuicCompressedCertsCache* compressed_certs_cache,
+      QuicSimpleServerBackend* quic_simple_server_backend)
+      : QuicSimpleServerSession(DefaultQuicConfig(), CurrentSupportedVersions(),
+                                connection, owner, helper, crypto_config,
+                                compressed_certs_cache,
+                                quic_simple_server_backend) {
+    if (VersionHasIetfQuicFrames(connection->transport_version())) {
+      QuicSessionPeer::SetMaxOpenIncomingUnidirectionalStreams(
+          this, kMaxStreamsForTest);
+      QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(
+          this, kMaxStreamsForTest);
+    } else {
+      QuicSessionPeer::SetMaxOpenIncomingStreams(this, kMaxStreamsForTest);
+      QuicSessionPeer::SetMaxOpenOutgoingStreams(this, kMaxStreamsForTest);
+    }
+    ON_CALL(*this, WritevData(_, _, _, _, _, _))
+        .WillByDefault(Invoke(this, &MockQuicSimpleServerSession::ConsumeData));
+  }
+
+  MockQuicSimpleServerSession(const MockQuicSimpleServerSession&) = delete;
+  MockQuicSimpleServerSession& operator=(const MockQuicSimpleServerSession&) =
+      delete;
+  ~MockQuicSimpleServerSession() override = default;
+
+  MOCK_METHOD(void, OnConnectionClosed,
+              (const QuicConnectionCloseFrame& frame,
+               ConnectionCloseSource source),
+              (override));
+  MOCK_METHOD(QuicSpdyStream*, CreateIncomingStream, (QuicStreamId id),
+              (override));
+  MOCK_METHOD(QuicConsumedData, WritevData,
+              (QuicStreamId id, size_t write_length, QuicStreamOffset offset,
+               StreamSendingState state, TransmissionType type,
+               EncryptionLevel level),
+              (override));
+  MOCK_METHOD(void, OnStreamHeaderList,
+              (QuicStreamId stream_id, bool fin, size_t frame_len,
+               const QuicHeaderList& header_list),
+              (override));
+  MOCK_METHOD(void, OnStreamHeadersPriority,
+              (QuicStreamId stream_id,
+               const spdy::SpdyStreamPrecedence& precedence),
+              (override));
+  MOCK_METHOD(void, MaybeSendRstStreamFrame,
+              (QuicStreamId stream_id, QuicResetStreamError error,
+               QuicStreamOffset bytes_written),
+              (override));
+  MOCK_METHOD(void, MaybeSendStopSendingFrame,
+              (QuicStreamId stream_id, QuicResetStreamError error), (override));
+
+  using QuicSession::ActivateStream;
+
+  QuicConsumedData ConsumeData(QuicStreamId id, size_t write_length,
+                               QuicStreamOffset offset,
+                               StreamSendingState state,
+                               TransmissionType /*type*/,
+                               absl::optional<EncryptionLevel> /*level*/) {
+    if (write_length > 0) {
+      auto buf = std::make_unique<char[]>(write_length);
+      QuicStream* stream = GetOrCreateStream(id);
+      QUICHE_DCHECK(stream);
+      QuicDataWriter writer(write_length, buf.get(), quiche::HOST_BYTE_ORDER);
+      stream->WriteStreamData(offset, write_length, &writer);
+    } else {
+      QUICHE_DCHECK(state != NO_FIN);
+    }
+    return QuicConsumedData(write_length, state != NO_FIN);
+  }
+
+  spdy::Http2HeaderBlock original_request_headers_;
+};
+
+class QuicSimpleServerStreamTest : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+  QuicSimpleServerStreamTest()
+      : connection_(new StrictMock<MockQuicConnection>(
+            &helper_, &alarm_factory_, Perspective::IS_SERVER,
+            SupportedVersions(GetParam()))),
+        crypto_config_(new QuicCryptoServerConfig(
+            QuicCryptoServerConfig::TESTING, QuicRandom::GetInstance(),
+            crypto_test_utils::ProofSourceForTesting(),
+            KeyExchangeSource::Default())),
+        compressed_certs_cache_(
+            QuicCompressedCertsCache::kQuicCompressedCertsCacheSize),
+        session_(connection_, &session_owner_, &session_helper_,
+                 crypto_config_.get(), &compressed_certs_cache_,
+                 &memory_cache_backend_),
+        quic_response_(new QuicBackendResponse),
+        body_("hello world") {
+    connection_->set_visitor(&session_);
+    header_list_.OnHeaderBlockStart();
+    header_list_.OnHeader(":authority", "www.google.com");
+    header_list_.OnHeader(":path", "/");
+    header_list_.OnHeader(":method", "POST");
+    header_list_.OnHeader(":scheme", "https");
+    header_list_.OnHeader("content-length", "11");
+
+    header_list_.OnHeaderBlockEnd(128, 128);
+
+    // New streams rely on having the peer's flow control receive window
+    // negotiated in the config.
+    session_.config()->SetInitialStreamFlowControlWindowToSend(
+        kInitialStreamFlowControlWindowForTest);
+    session_.config()->SetInitialSessionFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    session_.Initialize();
+    connection_->SetEncrypter(
+        quic::ENCRYPTION_FORWARD_SECURE,
+        std::make_unique<quic::NullEncrypter>(connection_->perspective()));
+    if (connection_->version().SupportsAntiAmplificationLimit()) {
+      QuicConnectionPeer::SetAddressValidated(connection_);
+    }
+    stream_ = new StrictMock<TestStream>(
+        GetNthClientInitiatedBidirectionalStreamId(
+            connection_->transport_version(), 0),
+        &session_, BIDIRECTIONAL, &memory_cache_backend_);
+    // Register stream_ in dynamic_stream_map_ and pass ownership to session_.
+    session_.ActivateStream(absl::WrapUnique(stream_));
+    QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
+        session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesIncomingBidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(session_.config(), 10);
+    session_.OnConfigNegotiated();
+    connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  }
+
+  const std::string& StreamBody() { return stream_->body(); }
+
+  std::string StreamHeadersValue(const std::string& key) {
+    return (*stream_->mutable_headers())[key].as_string();
+  }
+
+  bool UsesHttp3() const {
+    return VersionUsesHttp3(connection_->transport_version());
+  }
+
+  spdy::Http2HeaderBlock response_headers_;
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnection>* connection_;
+  StrictMock<MockQuicSessionVisitor> session_owner_;
+  StrictMock<MockQuicCryptoServerStreamHelper> session_helper_;
+  std::unique_ptr<QuicCryptoServerConfig> crypto_config_;
+  QuicCompressedCertsCache compressed_certs_cache_;
+  QuicMemoryCacheBackend memory_cache_backend_;
+  StrictMock<MockQuicSimpleServerSession> session_;
+  StrictMock<TestStream>* stream_;  // Owned by session_.
+  std::unique_ptr<QuicBackendResponse> quic_response_;
+  std::string body_;
+  QuicHeaderList header_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests, QuicSimpleServerStreamTest,
+                         ::testing::ValuesIn(AllSupportedVersions()),
+                         ::testing::PrintToStringParamName());
+
+TEST_P(QuicSimpleServerStreamTest, TestFraming) {
+  EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+      .WillRepeatedly(
+          Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+  stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_);
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body_.length(), quiche::SimpleBufferAllocator::Get());
+  std::string data =
+      UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_;
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+  EXPECT_EQ("11", StreamHeadersValue("content-length"));
+  EXPECT_EQ("/", StreamHeadersValue(":path"));
+  EXPECT_EQ("POST", StreamHeadersValue(":method"));
+  EXPECT_EQ(body_, StreamBody());
+}
+
+TEST_P(QuicSimpleServerStreamTest, TestFramingOnePacket) {
+  EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+      .WillRepeatedly(
+          Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+
+  stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_);
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body_.length(), quiche::SimpleBufferAllocator::Get());
+  std::string data =
+      UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_;
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+  EXPECT_EQ("11", StreamHeadersValue("content-length"));
+  EXPECT_EQ("/", StreamHeadersValue(":path"));
+  EXPECT_EQ("POST", StreamHeadersValue(":method"));
+  EXPECT_EQ(body_, StreamBody());
+}
+
+TEST_P(QuicSimpleServerStreamTest, SendQuicRstStreamNoErrorInStopReading) {
+  EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+      .WillRepeatedly(
+          Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+
+  EXPECT_FALSE(stream_->fin_received());
+  EXPECT_FALSE(stream_->rst_received());
+
+  QuicStreamPeer::SetFinSent(stream_);
+  stream_->CloseWriteSide();
+
+  if (session_.version().UsesHttp3()) {
+    EXPECT_CALL(session_,
+                MaybeSendStopSendingFrame(_, QuicResetStreamError::FromInternal(
+                                                 QUIC_STREAM_NO_ERROR)))
+        .Times(1);
+  } else {
+    EXPECT_CALL(
+        session_,
+        MaybeSendRstStreamFrame(
+            _, QuicResetStreamError::FromInternal(QUIC_STREAM_NO_ERROR), _))
+        .Times(1);
+  }
+  stream_->StopReading();
+}
+
+TEST_P(QuicSimpleServerStreamTest, TestFramingExtraData) {
+  InSequence seq;
+  std::string large_body = "hello world!!!!!!";
+
+  // We'll automatically write out an error (headers + body)
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  if (UsesHttp3()) {
+    EXPECT_CALL(session_,
+                WritevData(_, kDataFrameHeaderLength, _, NO_FIN, _, _));
+  }
+  EXPECT_CALL(session_, WritevData(_, kErrorLength, _, FIN, _, _));
+
+  stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_);
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body_.length(), quiche::SimpleBufferAllocator::Get());
+  std::string data =
+      UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_;
+
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+  // Content length is still 11.  This will register as an error and we won't
+  // accept the bytes.
+  header = HttpEncoder::SerializeDataFrameHeader(
+      large_body.length(), quiche::SimpleBufferAllocator::Get());
+  std::string data2 = UsesHttp3()
+                          ? absl::StrCat(header.AsStringView(), large_body)
+                          : large_body;
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/true, data.size(), data2));
+  EXPECT_EQ("11", StreamHeadersValue("content-length"));
+  EXPECT_EQ("/", StreamHeadersValue(":path"));
+  EXPECT_EQ("POST", StreamHeadersValue(":method"));
+}
+
+TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus) {
+  // Send an illegal response with response status not supported by HTTP/2.
+  spdy::Http2HeaderBlock* request_headers = stream_->mutable_headers();
+  (*request_headers)[":path"] = "/bar";
+  (*request_headers)[":authority"] = "www.google.com";
+  (*request_headers)[":method"] = "GET";
+
+  // HTTP/2 only supports integer responsecode, so "200 OK" is illegal.
+  response_headers_[":status"] = "200 OK";
+  response_headers_["content-length"] = "5";
+  std::string body = "Yummm";
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body.length(), quiche::SimpleBufferAllocator::Get());
+
+  memory_cache_backend_.AddResponse("www.google.com", "/bar",
+                                    std::move(response_headers_), body);
+
+  QuicStreamPeer::SetFinReceived(stream_);
+
+  InSequence s;
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  if (UsesHttp3()) {
+    EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _));
+  }
+  EXPECT_CALL(session_, WritevData(_, kErrorLength, _, FIN, _, _));
+
+  stream_->DoSendResponse();
+  EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_));
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSimpleServerStreamTest, SendResponseWithIllegalResponseStatus2) {
+  // Send an illegal response with response status not supported by HTTP/2.
+  spdy::Http2HeaderBlock* request_headers = stream_->mutable_headers();
+  (*request_headers)[":path"] = "/bar";
+  (*request_headers)[":authority"] = "www.google.com";
+  (*request_headers)[":method"] = "GET";
+
+  // HTTP/2 only supports 3-digit-integer, so "+200" is illegal.
+  response_headers_[":status"] = "+200";
+  response_headers_["content-length"] = "5";
+  std::string body = "Yummm";
+
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body.length(), quiche::SimpleBufferAllocator::Get());
+
+  memory_cache_backend_.AddResponse("www.google.com", "/bar",
+                                    std::move(response_headers_), body);
+
+  QuicStreamPeer::SetFinReceived(stream_);
+
+  InSequence s;
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  if (UsesHttp3()) {
+    EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _));
+  }
+  EXPECT_CALL(session_, WritevData(_, kErrorLength, _, FIN, _, _));
+
+  stream_->DoSendResponse();
+  EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_));
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSimpleServerStreamTest, SendPushResponseWith404Response) {
+  // Create a new promised stream with even id().
+  auto promised_stream = new StrictMock<TestStream>(
+      GetNthServerInitiatedUnidirectionalStreamId(
+          connection_->transport_version(), 3),
+      &session_, WRITE_UNIDIRECTIONAL, &memory_cache_backend_);
+  session_.ActivateStream(absl::WrapUnique(promised_stream));
+
+  // Send a push response with response status 404, which will be regarded as
+  // invalid server push response.
+  spdy::Http2HeaderBlock* request_headers = promised_stream->mutable_headers();
+  (*request_headers)[":path"] = "/bar";
+  (*request_headers)[":authority"] = "www.google.com";
+  (*request_headers)[":method"] = "GET";
+
+  response_headers_[":status"] = "404";
+  response_headers_["content-length"] = "8";
+  std::string body = "NotFound";
+
+  memory_cache_backend_.AddResponse("www.google.com", "/bar",
+                                    std::move(response_headers_), body);
+
+  InSequence s;
+  if (session_.version().UsesHttp3()) {
+    EXPECT_CALL(session_,
+                MaybeSendStopSendingFrame(
+                    promised_stream->id(),
+                    QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED)));
+  }
+  EXPECT_CALL(
+      session_,
+      MaybeSendRstStreamFrame(
+          promised_stream->id(),
+          QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED), 0));
+
+  promised_stream->DoSendResponse();
+}
+
+TEST_P(QuicSimpleServerStreamTest, SendResponseWithValidHeaders) {
+  // Add a request and response with valid headers.
+  spdy::Http2HeaderBlock* request_headers = stream_->mutable_headers();
+  (*request_headers)[":path"] = "/bar";
+  (*request_headers)[":authority"] = "www.google.com";
+  (*request_headers)[":method"] = "GET";
+
+  response_headers_[":status"] = "200";
+  response_headers_["content-length"] = "5";
+  std::string body = "Yummm";
+
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body.length(), quiche::SimpleBufferAllocator::Get());
+
+  memory_cache_backend_.AddResponse("www.google.com", "/bar",
+                                    std::move(response_headers_), body);
+  QuicStreamPeer::SetFinReceived(stream_);
+
+  InSequence s;
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  if (UsesHttp3()) {
+    EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _));
+  }
+  EXPECT_CALL(session_, WritevData(_, body.length(), _, FIN, _, _));
+
+  stream_->DoSendResponse();
+  EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_));
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSimpleServerStreamTest, SendResponseWithEarlyHints) {
+  std::string host = "www.google.com";
+  std::string request_path = "/foo";
+  std::string body = "Yummm";
+
+  // Add a request and response with early hints.
+  spdy::Http2HeaderBlock* request_headers = stream_->mutable_headers();
+  (*request_headers)[":path"] = request_path;
+  (*request_headers)[":authority"] = host;
+  (*request_headers)[":method"] = "GET";
+
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body.length(), quiche::SimpleBufferAllocator::Get());
+  std::vector<spdy::Http2HeaderBlock> early_hints;
+  // Add two Early Hints.
+  const size_t kNumEarlyHintsResponses = 2;
+  for (size_t i = 0; i < kNumEarlyHintsResponses; ++i) {
+    spdy::Http2HeaderBlock hints;
+    hints["link"] = "</image.png>; rel=preload; as=image";
+    early_hints.push_back(std::move(hints));
+  }
+
+  response_headers_[":status"] = "200";
+  response_headers_["content-length"] = "5";
+  memory_cache_backend_.AddResponseWithEarlyHints(
+      host, request_path, std::move(response_headers_), body, early_hints);
+  QuicStreamPeer::SetFinReceived(stream_);
+
+  InSequence s;
+  for (size_t i = 0; i < kNumEarlyHintsResponses; ++i) {
+    EXPECT_CALL(*stream_, WriteEarlyHintsHeadersMock(false));
+  }
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  if (UsesHttp3()) {
+    EXPECT_CALL(session_, WritevData(_, header.size(), _, NO_FIN, _, _));
+  }
+  EXPECT_CALL(session_, WritevData(_, body.length(), _, FIN, _, _));
+
+  stream_->DoSendResponse();
+  EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_));
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSimpleServerStreamTest, PushResponseOnClientInitiatedStream) {
+  // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+  if (GetParam() != AllSupportedVersions()[0]) {
+    return;
+  }
+
+  // Calling PushResponse() on a client initialted stream is never supposed to
+  // happen.
+  EXPECT_QUIC_BUG(stream_->PushResponse(spdy::Http2HeaderBlock()),
+                  "Client initiated stream"
+                  " shouldn't be used as promised stream.");
+}
+
+TEST_P(QuicSimpleServerStreamTest, PushResponseOnServerInitiatedStream) {
+  // Tests that PushResponse() should take the given headers as request headers
+  // and fetch response from cache, and send it out.
+
+  // Create a stream with even stream id and test against this stream.
+  const QuicStreamId kServerInitiatedStreamId =
+      GetNthServerInitiatedUnidirectionalStreamId(
+          connection_->transport_version(), 3);
+  // Create a server initiated stream and pass it to session_.
+  auto server_initiated_stream =
+      new StrictMock<TestStream>(kServerInitiatedStreamId, &session_,
+                                 WRITE_UNIDIRECTIONAL, &memory_cache_backend_);
+  session_.ActivateStream(absl::WrapUnique(server_initiated_stream));
+
+  const std::string kHost = "www.foo.com";
+  const std::string kPath = "/bar";
+  spdy::Http2HeaderBlock headers;
+  headers[":path"] = kPath;
+  headers[":authority"] = kHost;
+  headers[":method"] = "GET";
+
+  response_headers_[":status"] = "200";
+  response_headers_["content-length"] = "5";
+  const std::string kBody = "Hello";
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body_.length(), quiche::SimpleBufferAllocator::Get());
+  memory_cache_backend_.AddResponse(kHost, kPath, std::move(response_headers_),
+                                    kBody);
+
+  // Call PushResponse() should trigger stream to fetch response from cache
+  // and send it back.
+  InSequence s;
+  EXPECT_CALL(*server_initiated_stream, WriteHeadersMock(false));
+
+  if (UsesHttp3()) {
+    EXPECT_CALL(session_, WritevData(kServerInitiatedStreamId, header.size(), _,
+                                     NO_FIN, _, _));
+  }
+  EXPECT_CALL(session_,
+              WritevData(kServerInitiatedStreamId, kBody.size(), _, FIN, _, _));
+  server_initiated_stream->PushResponse(std::move(headers));
+  EXPECT_EQ(kPath, server_initiated_stream->GetHeader(":path"));
+  EXPECT_EQ("GET", server_initiated_stream->GetHeader(":method"));
+}
+
+TEST_P(QuicSimpleServerStreamTest, TestSendErrorResponse) {
+  QuicStreamPeer::SetFinReceived(stream_);
+
+  InSequence s;
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  if (UsesHttp3()) {
+    EXPECT_CALL(session_,
+                WritevData(_, kDataFrameHeaderLength, _, NO_FIN, _, _));
+  }
+  EXPECT_CALL(session_, WritevData(_, kErrorLength, _, FIN, _, _));
+
+  stream_->DoSendErrorResponse();
+  EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_));
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSimpleServerStreamTest, InvalidMultipleContentLength) {
+  spdy::Http2HeaderBlock request_headers;
+  // \000 is a way to write the null byte when followed by a literal digit.
+  header_list_.OnHeader("content-length", absl::string_view("11\00012", 5));
+
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+      .WillRepeatedly(
+          Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+  stream_->OnStreamHeaderList(true, kFakeFrameLen, header_list_);
+
+  EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream_));
+  EXPECT_TRUE(stream_->reading_stopped());
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSimpleServerStreamTest, InvalidLeadingNullContentLength) {
+  spdy::Http2HeaderBlock request_headers;
+  // \000 is a way to write the null byte when followed by a literal digit.
+  header_list_.OnHeader("content-length", absl::string_view("\00012", 3));
+
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+      .WillRepeatedly(
+          Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+  stream_->OnStreamHeaderList(true, kFakeFrameLen, header_list_);
+
+  EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream_));
+  EXPECT_TRUE(stream_->reading_stopped());
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSimpleServerStreamTest, ValidMultipleContentLength) {
+  spdy::Http2HeaderBlock request_headers;
+  // \000 is a way to write the null byte when followed by a literal digit.
+  header_list_.OnHeader("content-length", absl::string_view("11\00011", 5));
+
+  stream_->OnStreamHeaderList(false, kFakeFrameLen, header_list_);
+
+  EXPECT_EQ(11, stream_->content_length());
+  EXPECT_FALSE(QuicStreamPeer::read_side_closed(stream_));
+  EXPECT_FALSE(stream_->reading_stopped());
+  EXPECT_FALSE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSimpleServerStreamTest,
+       DoNotSendQuicRstStreamNoErrorWithRstReceived) {
+  EXPECT_FALSE(stream_->reading_stopped());
+
+  if (VersionUsesHttp3(connection_->transport_version())) {
+    // Unidirectional stream type and then a Stream Cancellation instruction is
+    // sent on the QPACK decoder stream.  Ignore these writes without any
+    // assumption on their number or size.
+    auto* qpack_decoder_stream =
+        QuicSpdySessionPeer::GetQpackDecoderSendStream(&session_);
+    EXPECT_CALL(session_, WritevData(qpack_decoder_stream->id(), _, _, _, _, _))
+        .Times(AnyNumber());
+  }
+
+  EXPECT_CALL(
+      session_,
+      MaybeSendRstStreamFrame(
+          _,
+          session_.version().UsesHttp3()
+              ? QuicResetStreamError::FromInternal(QUIC_STREAM_CANCELLED)
+              : QuicResetStreamError::FromInternal(QUIC_RST_ACKNOWLEDGEMENT),
+          _))
+      .Times(1);
+  QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
+                               QUIC_STREAM_CANCELLED, 1234);
+  stream_->OnStreamReset(rst_frame);
+  if (VersionHasIetfQuicFrames(connection_->transport_version())) {
+    EXPECT_CALL(session_owner_, OnStopSendingReceived(_));
+    // Create and inject a STOP SENDING frame to complete the close
+    // of the stream. This is only needed for version 99/IETF QUIC.
+    QuicStopSendingFrame stop_sending(kInvalidControlFrameId, stream_->id(),
+                                      QUIC_STREAM_CANCELLED);
+    session_.OnStopSendingFrame(stop_sending);
+  }
+  EXPECT_TRUE(stream_->reading_stopped());
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSimpleServerStreamTest, InvalidHeadersWithFin) {
+  char arr[] = {
+      0x3a,   0x68, 0x6f, 0x73,  // :hos
+      0x74,   0x00, 0x00, 0x00,  // t...
+      0x00,   0x00, 0x00, 0x00,  // ....
+      0x07,   0x3a, 0x6d, 0x65,  // .:me
+      0x74,   0x68, 0x6f, 0x64,  // thod
+      0x00,   0x00, 0x00, 0x03,  // ....
+      0x47,   0x45, 0x54, 0x00,  // GET.
+      0x00,   0x00, 0x05, 0x3a,  // ...:
+      0x70,   0x61, 0x74, 0x68,  // path
+      0x00,   0x00, 0x00, 0x04,  // ....
+      0x2f,   0x66, 0x6f, 0x6f,  // /foo
+      0x00,   0x00, 0x00, 0x07,  // ....
+      0x3a,   0x73, 0x63, 0x68,  // :sch
+      0x65,   0x6d, 0x65, 0x00,  // eme.
+      0x00,   0x00, 0x00, 0x00,  // ....
+      0x00,   0x00, 0x08, 0x3a,  // ...:
+      0x76,   0x65, 0x72, 0x73,  // vers
+      '\x96', 0x6f, 0x6e, 0x00,  // <i(69)>on.
+      0x00,   0x00, 0x08, 0x48,  // ...H
+      0x54,   0x54, 0x50, 0x2f,  // TTP/
+      0x31,   0x2e, 0x31,        // 1.1
+  };
+  absl::string_view data(arr, ABSL_ARRAYSIZE(arr));
+  QuicStreamFrame frame(stream_->id(), true, 0, data);
+  // Verify that we don't crash when we get a invalid headers in stream frame.
+  stream_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicSimpleServerStreamTest, ConnectSendsResponseBeforeFinReceived) {
+  EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+      .WillRepeatedly(
+          Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+  QuicHeaderList header_list;
+  header_list.OnHeaderBlockStart();
+  header_list.OnHeader(":authority", "www.google.com:4433");
+  header_list.OnHeader(":method", "CONNECT");
+  header_list.OnHeaderBlockEnd(128, 128);
+  EXPECT_CALL(*stream_, WriteHeadersMock(/*fin=*/false));
+  stream_->OnStreamHeaderList(/*fin=*/false, kFakeFrameLen, header_list);
+  quiche::QuicheBuffer header = HttpEncoder::SerializeDataFrameHeader(
+      body_.length(), quiche::SimpleBufferAllocator::Get());
+  std::string data =
+      UsesHttp3() ? absl::StrCat(header.AsStringView(), body_) : body_;
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+  EXPECT_EQ("CONNECT", StreamHeadersValue(":method"));
+  EXPECT_EQ(body_, StreamBody());
+  EXPECT_TRUE(stream_->send_response_was_called());
+  EXPECT_FALSE(stream_->send_error_response_was_called());
+}
+
+TEST_P(QuicSimpleServerStreamTest, ConnectWithInvalidHeader) {
+  EXPECT_CALL(session_, WritevData(_, _, _, _, _, _))
+      .WillRepeatedly(
+          Invoke(&session_, &MockQuicSimpleServerSession::ConsumeData));
+  QuicHeaderList header_list;
+  header_list.OnHeaderBlockStart();
+  header_list.OnHeader(":authority", "www.google.com:4433");
+  header_list.OnHeader(":method", "CONNECT");
+  // QUIC requires lower-case header names.
+  header_list.OnHeader("InVaLiD-HeAdEr", "Well that's just wrong!");
+  header_list.OnHeaderBlockEnd(128, 128);
+
+  if (UsesHttp3()) {
+    EXPECT_CALL(session_,
+                MaybeSendStopSendingFrame(_, QuicResetStreamError::FromInternal(
+                                                 QUIC_STREAM_NO_ERROR)))
+        .Times(1);
+  } else {
+    EXPECT_CALL(
+        session_,
+        MaybeSendRstStreamFrame(
+            _, QuicResetStreamError::FromInternal(QUIC_STREAM_NO_ERROR), _))
+        .Times(1);
+  }
+  EXPECT_CALL(*stream_, WriteHeadersMock(/*fin=*/false));
+  stream_->OnStreamHeaderList(/*fin=*/false, kFakeFrameLen, header_list);
+  EXPECT_FALSE(stream_->send_response_was_called());
+  EXPECT_TRUE(stream_->send_error_response_was_called());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_spdy_client_base.cc b/quiche/quic/tools/quic_spdy_client_base.cc
new file mode 100644
index 0000000..f61a26d
--- /dev/null
+++ b/quiche/quic/tools/quic_spdy_client_base.cc
@@ -0,0 +1,296 @@
+// 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 "quiche/quic/tools/quic_spdy_client_base.h"
+
+#include <utility>
+
+#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/http/spdy_utils.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/common/quiche_text_utils.h"
+
+using spdy::Http2HeaderBlock;
+
+namespace quic {
+
+void QuicSpdyClientBase::ClientQuicDataToResend::Resend() {
+  client_->SendRequest(*headers_, body_, fin_);
+  headers_ = nullptr;
+}
+
+QuicSpdyClientBase::QuicDataToResend::QuicDataToResend(
+    std::unique_ptr<Http2HeaderBlock> headers,
+    absl::string_view body,
+    bool fin)
+    : headers_(std::move(headers)), body_(body), fin_(fin) {}
+
+QuicSpdyClientBase::QuicDataToResend::~QuicDataToResend() = default;
+
+QuicSpdyClientBase::QuicSpdyClientBase(
+    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,
+    std::unique_ptr<SessionCache> session_cache)
+    : QuicClientBase(server_id,
+                     supported_versions,
+                     config,
+                     helper,
+                     alarm_factory,
+                     std::move(network_helper),
+                     std::move(proof_verifier),
+                     std::move(session_cache)),
+      store_response_(false),
+      latest_response_code_(-1) {}
+
+QuicSpdyClientBase::~QuicSpdyClientBase() {
+  // We own the push promise index. We need to explicitly kill
+  // the session before the push promise index goes out of scope.
+  ResetSession();
+}
+
+QuicSpdyClientSession* QuicSpdyClientBase::client_session() {
+  return static_cast<QuicSpdyClientSession*>(QuicClientBase::session());
+}
+
+const QuicSpdyClientSession* QuicSpdyClientBase::client_session() const {
+  return static_cast<const QuicSpdyClientSession*>(QuicClientBase::session());
+}
+
+void QuicSpdyClientBase::InitializeSession() {
+  if (max_inbound_header_list_size_ > 0) {
+    client_session()->set_max_inbound_header_list_size(
+        max_inbound_header_list_size_);
+  }
+  client_session()->Initialize();
+  client_session()->CryptoConnect();
+}
+
+void QuicSpdyClientBase::OnClose(QuicSpdyStream* stream) {
+  QUICHE_DCHECK(stream != nullptr);
+  QuicSpdyClientStream* client_stream =
+      static_cast<QuicSpdyClientStream*>(stream);
+
+  const Http2HeaderBlock& response_headers = client_stream->response_headers();
+  if (response_listener_ != nullptr) {
+    response_listener_->OnCompleteResponse(stream->id(), response_headers,
+                                           client_stream->data());
+  }
+
+  // Store response headers and body.
+  if (store_response_) {
+    auto status = response_headers.find(":status");
+    if (status == response_headers.end()) {
+      QUIC_LOG(ERROR) << "Missing :status response header";
+    } else if (!absl::SimpleAtoi(status->second, &latest_response_code_)) {
+      QUIC_LOG(ERROR) << "Invalid :status response header: " << status->second;
+    }
+    latest_response_headers_ = response_headers.DebugString();
+    preliminary_response_headers_ =
+        client_stream->preliminary_headers().DebugString();
+    latest_response_header_block_ = response_headers.Clone();
+    latest_response_body_ = client_stream->data();
+    latest_response_trailers_ =
+        client_stream->received_trailers().DebugString();
+  }
+}
+
+std::unique_ptr<QuicSession> QuicSpdyClientBase::CreateQuicClientSession(
+    const quic::ParsedQuicVersionVector& supported_versions,
+    QuicConnection* connection) {
+  return std::make_unique<QuicSpdyClientSession>(
+      *config(), supported_versions, connection, server_id(), crypto_config(),
+      &push_promise_index_);
+}
+
+void QuicSpdyClientBase::SendRequest(const Http2HeaderBlock& headers,
+                                     absl::string_view body,
+                                     bool fin) {
+  if (GetQuicFlag(FLAGS_quic_client_convert_http_header_name_to_lowercase)) {
+    QUIC_CODE_COUNT(quic_client_convert_http_header_name_to_lowercase);
+    Http2HeaderBlock sanitized_headers;
+    for (const auto& p : headers) {
+      sanitized_headers[quiche::QuicheTextUtils::ToLower(p.first)] = p.second;
+    }
+
+    SendRequestInternal(std::move(sanitized_headers), body, fin);
+  } else {
+    SendRequestInternal(headers.Clone(), body, fin);
+  }
+}
+
+void QuicSpdyClientBase::SendRequestInternal(Http2HeaderBlock sanitized_headers,
+                                             absl::string_view body,
+                                             bool fin) {
+  QuicClientPushPromiseIndex::TryHandle* handle;
+  QuicAsyncStatus rv =
+      push_promise_index()->Try(sanitized_headers, this, &handle);
+  if (rv == QUIC_SUCCESS)
+    return;
+
+  if (rv == QUIC_PENDING) {
+    // May need to retry request if asynchronous rendezvous fails.
+    AddPromiseDataToResend(sanitized_headers, body, fin);
+    return;
+  }
+
+  QuicSpdyClientStream* stream = CreateClientStream();
+  if (stream == nullptr) {
+    QUIC_BUG(quic_bug_10949_1) << "stream creation failed!";
+    return;
+  }
+  stream->SendRequest(std::move(sanitized_headers), body, fin);
+}
+
+void QuicSpdyClientBase::SendRequestAndWaitForResponse(
+    const Http2HeaderBlock& headers,
+    absl::string_view body,
+    bool fin) {
+  SendRequest(headers, body, fin);
+  while (WaitForEvents()) {
+  }
+}
+
+void QuicSpdyClientBase::SendRequestsAndWaitForResponse(
+    const std::vector<std::string>& url_list) {
+  for (size_t i = 0; i < url_list.size(); ++i) {
+    Http2HeaderBlock headers;
+    if (!SpdyUtils::PopulateHeaderBlockFromUrl(url_list[i], &headers)) {
+      QUIC_BUG(quic_bug_10949_2) << "Unable to create request";
+      continue;
+    }
+    SendRequest(headers, "", true);
+  }
+  while (WaitForEvents()) {
+  }
+}
+
+QuicSpdyClientStream* QuicSpdyClientBase::CreateClientStream() {
+  if (!connected()) {
+    return nullptr;
+  }
+  if (VersionHasIetfQuicFrames(client_session()->transport_version())) {
+    // Process MAX_STREAMS from peer or wait for liveness testing succeeds.
+    while (!client_session()->CanOpenNextOutgoingBidirectionalStream()) {
+      network_helper()->RunEventLoop();
+    }
+  }
+  auto* stream = static_cast<QuicSpdyClientStream*>(
+      client_session()->CreateOutgoingBidirectionalStream());
+  if (stream) {
+    stream->set_visitor(this);
+  }
+  return stream;
+}
+
+bool QuicSpdyClientBase::goaway_received() const {
+  return client_session() && client_session()->goaway_received();
+}
+
+bool QuicSpdyClientBase::EarlyDataAccepted() {
+  return client_session()->EarlyDataAccepted();
+}
+
+bool QuicSpdyClientBase::ReceivedInchoateReject() {
+  return client_session()->ReceivedInchoateReject();
+}
+
+int QuicSpdyClientBase::GetNumSentClientHellosFromSession() {
+  return client_session()->GetNumSentClientHellos();
+}
+
+int QuicSpdyClientBase::GetNumReceivedServerConfigUpdatesFromSession() {
+  return client_session()->GetNumReceivedServerConfigUpdates();
+}
+
+void QuicSpdyClientBase::MaybeAddQuicDataToResend(
+    std::unique_ptr<QuicDataToResend> data_to_resend) {
+  data_to_resend_on_connect_.push_back(std::move(data_to_resend));
+}
+
+void QuicSpdyClientBase::ClearDataToResend() {
+  data_to_resend_on_connect_.clear();
+}
+
+void QuicSpdyClientBase::ResendSavedData() {
+  // Calling Resend will re-enqueue the data, so swap out
+  //  data_to_resend_on_connect_ before iterating.
+  std::vector<std::unique_ptr<QuicDataToResend>> old_data;
+  old_data.swap(data_to_resend_on_connect_);
+  for (const auto& data : old_data) {
+    data->Resend();
+  }
+}
+
+void QuicSpdyClientBase::AddPromiseDataToResend(const Http2HeaderBlock& headers,
+                                                absl::string_view body,
+                                                bool fin) {
+  std::unique_ptr<Http2HeaderBlock> new_headers(
+      new Http2HeaderBlock(headers.Clone()));
+  push_promise_data_to_resend_.reset(
+      new ClientQuicDataToResend(std::move(new_headers), body, fin, this));
+}
+
+bool QuicSpdyClientBase::CheckVary(
+    const Http2HeaderBlock& /*client_request*/,
+    const Http2HeaderBlock& /*promise_request*/,
+    const Http2HeaderBlock& /*promise_response*/) {
+  return true;
+}
+
+void QuicSpdyClientBase::OnRendezvousResult(QuicSpdyStream* stream) {
+  std::unique_ptr<ClientQuicDataToResend> data_to_resend =
+      std::move(push_promise_data_to_resend_);
+  if (stream) {
+    stream->set_visitor(this);
+    stream->OnBodyAvailable();
+  } else if (data_to_resend) {
+    data_to_resend->Resend();
+  }
+}
+
+int QuicSpdyClientBase::latest_response_code() const {
+  QUIC_BUG_IF(quic_bug_10949_3, !store_response_) << "Response not stored!";
+  return latest_response_code_;
+}
+
+const std::string& QuicSpdyClientBase::latest_response_headers() const {
+  QUIC_BUG_IF(quic_bug_10949_4, !store_response_) << "Response not stored!";
+  return latest_response_headers_;
+}
+
+const std::string& QuicSpdyClientBase::preliminary_response_headers() const {
+  QUIC_BUG_IF(quic_bug_10949_5, !store_response_) << "Response not stored!";
+  return preliminary_response_headers_;
+}
+
+const Http2HeaderBlock& QuicSpdyClientBase::latest_response_header_block()
+    const {
+  QUIC_BUG_IF(quic_bug_10949_6, !store_response_) << "Response not stored!";
+  return latest_response_header_block_;
+}
+
+const std::string& QuicSpdyClientBase::latest_response_body() const {
+  QUIC_BUG_IF(quic_bug_10949_7, !store_response_) << "Response not stored!";
+  return latest_response_body_;
+}
+
+const std::string& QuicSpdyClientBase::latest_response_trailers() const {
+  QUIC_BUG_IF(quic_bug_10949_8, !store_response_) << "Response not stored!";
+  return latest_response_trailers_;
+}
+
+bool QuicSpdyClientBase::HasActiveRequests() {
+  return client_session()->HasActiveRequestStreams();
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_spdy_client_base.h b/quiche/quic/tools/quic_spdy_client_base.h
new file mode 100644
index 0000000..921718a
--- /dev/null
+++ b/quiche/quic/tools/quic_spdy_client_base.h
@@ -0,0 +1,243 @@
+// 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.
+
+// A base class for the toy client, which connects to a specified port and sends
+// QUIC request to that endpoint.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SPDY_CLIENT_BASE_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SPDY_CLIENT_BASE_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/crypto_handshake.h"
+#include "quiche/quic/core/http/quic_client_push_promise_index.h"
+#include "quiche/quic/core/http/quic_spdy_client_session.h"
+#include "quiche/quic/core/http/quic_spdy_client_stream.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_client_base.h"
+
+namespace quic {
+
+class ProofVerifier;
+class QuicServerId;
+class SessionCache;
+
+class QuicSpdyClientBase : public QuicClientBase,
+                           public QuicClientPushPromiseIndex::Delegate,
+                           public QuicSpdyStream::Visitor {
+ public:
+  // A ResponseListener is notified when a complete response is received.
+  class ResponseListener {
+   public:
+    ResponseListener() {}
+    virtual ~ResponseListener() {}
+    virtual void OnCompleteResponse(
+        QuicStreamId id,
+        const spdy::Http2HeaderBlock& response_headers,
+        const std::string& response_body) = 0;
+  };
+
+  // A piece of data that can be sent multiple times. For example, it can be a
+  // HTTP request that is resent after a connect=>version negotiation=>reconnect
+  // sequence.
+  class QuicDataToResend {
+   public:
+    // |headers| may be null, since it's possible to send data without headers.
+    QuicDataToResend(std::unique_ptr<spdy::Http2HeaderBlock> headers,
+                     absl::string_view body,
+                     bool fin);
+    QuicDataToResend(const QuicDataToResend&) = delete;
+    QuicDataToResend& operator=(const QuicDataToResend&) = delete;
+
+    virtual ~QuicDataToResend();
+
+    // Must be overridden by specific classes with the actual method for
+    // re-sending data.
+    virtual void Resend() = 0;
+
+   protected:
+    std::unique_ptr<spdy::Http2HeaderBlock> headers_;
+    absl::string_view body_;
+    bool fin_;
+  };
+
+  QuicSpdyClientBase(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,
+                     std::unique_ptr<SessionCache> session_cache);
+  QuicSpdyClientBase(const QuicSpdyClientBase&) = delete;
+  QuicSpdyClientBase& operator=(const QuicSpdyClientBase&) = delete;
+
+  ~QuicSpdyClientBase() override;
+
+  // QuicSpdyStream::Visitor
+  void OnClose(QuicSpdyStream* stream) override;
+
+  // A spdy session has to call CryptoConnect on top of the regular
+  // initialization.
+  void InitializeSession() override;
+
+  // Sends an HTTP request and does not wait for response before returning.
+  void SendRequest(const spdy::Http2HeaderBlock& headers,
+                   absl::string_view body,
+                   bool fin);
+
+  // Sends an HTTP request and waits for response before returning.
+  void SendRequestAndWaitForResponse(const spdy::Http2HeaderBlock& headers,
+                                     absl::string_view body,
+                                     bool fin);
+
+  // Sends a request simple GET for each URL in |url_list|, and then waits for
+  // each to complete.
+  void SendRequestsAndWaitForResponse(const std::vector<std::string>& url_list);
+
+  // Returns a newly created QuicSpdyClientStream.
+  QuicSpdyClientStream* CreateClientStream();
+
+  // Returns a the session used for this client downcasted to a
+  // QuicSpdyClientSession.
+  QuicSpdyClientSession* client_session();
+  const QuicSpdyClientSession* client_session() const;
+
+  QuicClientPushPromiseIndex* push_promise_index() {
+    return &push_promise_index_;
+  }
+
+  bool CheckVary(const spdy::Http2HeaderBlock& client_request,
+                 const spdy::Http2HeaderBlock& promise_request,
+                 const spdy::Http2HeaderBlock& promise_response) override;
+  void OnRendezvousResult(QuicSpdyStream*) override;
+
+  // If the crypto handshake has not yet been confirmed, adds the data to the
+  // queue of data to resend if the client receives a stateless reject.
+  // Otherwise, deletes the data.
+  void MaybeAddQuicDataToResend(
+      std::unique_ptr<QuicDataToResend> data_to_resend);
+
+  void set_store_response(bool val) { store_response_ = val; }
+
+  int latest_response_code() const;
+  const std::string& latest_response_headers() const;
+  const std::string& preliminary_response_headers() const;
+  const spdy::Http2HeaderBlock& latest_response_header_block() const;
+  const std::string& latest_response_body() const;
+  const std::string& latest_response_trailers() const;
+
+  void set_response_listener(std::unique_ptr<ResponseListener> listener) {
+    response_listener_ = std::move(listener);
+  }
+
+  void set_drop_response_body(bool drop_response_body) {
+    drop_response_body_ = drop_response_body;
+  }
+  bool drop_response_body() const { return drop_response_body_; }
+
+  void set_enable_web_transport(bool enable_web_transport) {
+    enable_web_transport_ = enable_web_transport;
+  }
+  bool enable_web_transport() const { return enable_web_transport_; }
+
+  void set_use_datagram_contexts(bool use_datagram_contexts) {
+    use_datagram_contexts_ = use_datagram_contexts;
+  }
+  bool use_datagram_contexts() const { return use_datagram_contexts_; }
+
+  // QuicClientBase methods.
+  bool goaway_received() const override;
+  bool EarlyDataAccepted() override;
+  bool ReceivedInchoateReject() override;
+
+  void set_max_inbound_header_list_size(size_t size) {
+    max_inbound_header_list_size_ = size;
+  }
+
+ protected:
+  int GetNumSentClientHellosFromSession() override;
+  int GetNumReceivedServerConfigUpdatesFromSession() override;
+
+  // Takes ownership of |connection|.
+  std::unique_ptr<QuicSession> CreateQuicClientSession(
+      const quic::ParsedQuicVersionVector& supported_versions,
+      QuicConnection* connection) override;
+
+  void ClearDataToResend() override;
+
+  void ResendSavedData() override;
+
+  void AddPromiseDataToResend(const spdy::Http2HeaderBlock& headers,
+                              absl::string_view body,
+                              bool fin);
+  bool HasActiveRequests() override;
+
+ private:
+  // Specific QuicClient class for storing data to resend.
+  class ClientQuicDataToResend : public QuicDataToResend {
+   public:
+    ClientQuicDataToResend(std::unique_ptr<spdy::Http2HeaderBlock> headers,
+                           absl::string_view body,
+                           bool fin,
+                           QuicSpdyClientBase* client)
+        : QuicDataToResend(std::move(headers), body, fin), client_(client) {
+      QUICHE_DCHECK(headers_);
+      QUICHE_DCHECK(client);
+    }
+
+    ClientQuicDataToResend(const ClientQuicDataToResend&) = delete;
+    ClientQuicDataToResend& operator=(const ClientQuicDataToResend&) = delete;
+    ~ClientQuicDataToResend() override {}
+
+    void Resend() override;
+
+   private:
+    QuicSpdyClientBase* client_;
+  };
+
+  void SendRequestInternal(spdy::Http2HeaderBlock sanitized_headers,
+                           absl::string_view body,
+                           bool fin);
+
+  // Index of pending promised streams. Must outlive |session_|.
+  QuicClientPushPromiseIndex push_promise_index_;
+
+  // If true, store the latest response code, headers, and body.
+  bool store_response_;
+  // HTTP response code from most recent response.
+  int latest_response_code_;
+  // HTTP/2 headers from most recent response.
+  std::string latest_response_headers_;
+  // preliminary 100 Continue HTTP/2 headers from most recent response, if any.
+  std::string preliminary_response_headers_;
+  // HTTP/2 headers from most recent response.
+  spdy::Http2HeaderBlock latest_response_header_block_;
+  // Body of most recent response.
+  std::string latest_response_body_;
+  // HTTP/2 trailers from most recent response.
+  std::string latest_response_trailers_;
+
+  // Listens for full responses.
+  std::unique_ptr<ResponseListener> response_listener_;
+
+  // Keeps track of any data that must be resent upon a subsequent successful
+  // connection, in case the client receives a stateless reject.
+  std::vector<std::unique_ptr<QuicDataToResend>> data_to_resend_on_connect_;
+
+  std::unique_ptr<ClientQuicDataToResend> push_promise_data_to_resend_;
+
+  bool drop_response_body_ = false;
+  bool enable_web_transport_ = false;
+  bool use_datagram_contexts_ = false;
+  // If not zero, used to set client's max inbound header size before session
+  // initialize.
+  size_t max_inbound_header_list_size_ = 0;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SPDY_CLIENT_BASE_H_
diff --git a/quiche/quic/tools/quic_spdy_server_base.h b/quiche/quic/tools/quic_spdy_server_base.h
new file mode 100644
index 0000000..39bde90
--- /dev/null
+++ b/quiche/quic/tools/quic_spdy_server_base.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+// A toy server, which connects to a specified port and sends QUIC
+// requests to that endpoint.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_SPDY_SERVER_BASE_H_
+#define QUICHE_QUIC_TOOLS_QUIC_SPDY_SERVER_BASE_H_
+
+#include "quiche/quic/platform/api/quic_socket_address.h"
+
+namespace quic {
+
+// Base class for service instances to be used with QuicToyServer.
+class QuicSpdyServerBase {
+ public:
+  virtual ~QuicSpdyServerBase() = default;
+
+  // Creates a UDP socket and listens on |address|. Returns true on success
+  // and false otherwise.
+  virtual bool CreateUDPSocketAndListen(const QuicSocketAddress& address) = 0;
+
+  // Handles incoming requests. Does not return.
+  virtual void HandleEventsForever() = 0;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_SPDY_SERVER_BASE_H_
diff --git a/quiche/quic/tools/quic_tcp_like_trace_converter.cc b/quiche/quic/tools/quic_tcp_like_trace_converter.cc
new file mode 100644
index 0000000..f1ae0c1
--- /dev/null
+++ b/quiche/quic/tools/quic_tcp_like_trace_converter.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2018 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 "quiche/quic/tools/quic_tcp_like_trace_converter.h"
+
+#include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+
+namespace quic {
+
+QuicTcpLikeTraceConverter::QuicTcpLikeTraceConverter()
+    : largest_observed_control_frame_id_(kInvalidControlFrameId),
+      connection_offset_(0) {}
+
+QuicTcpLikeTraceConverter::StreamOffsetSegment::StreamOffsetSegment()
+    : connection_offset(0) {}
+
+QuicTcpLikeTraceConverter::StreamOffsetSegment::StreamOffsetSegment(
+    QuicStreamOffset stream_offset,
+    uint64_t connection_offset,
+    QuicByteCount data_length)
+    : stream_data(stream_offset, stream_offset + data_length),
+      connection_offset(connection_offset) {}
+
+QuicTcpLikeTraceConverter::StreamInfo::StreamInfo() : fin(false) {}
+
+QuicIntervalSet<uint64_t> QuicTcpLikeTraceConverter::OnCryptoFrameSent(
+    EncryptionLevel level,
+    QuicStreamOffset offset,
+    QuicByteCount data_length) {
+  if (level >= NUM_ENCRYPTION_LEVELS) {
+    QUIC_BUG(quic_bug_10907_1) << "Invalid encryption level";
+    return {};
+  }
+  return OnFrameSent(offset, data_length, /*fin=*/false,
+                     &crypto_frames_info_[level]);
+}
+
+QuicIntervalSet<uint64_t> QuicTcpLikeTraceConverter::OnStreamFrameSent(
+    QuicStreamId stream_id,
+    QuicStreamOffset offset,
+    QuicByteCount data_length,
+    bool fin) {
+  return OnFrameSent(
+      offset, data_length, fin,
+      &streams_info_.emplace(stream_id, StreamInfo()).first->second);
+}
+
+QuicIntervalSet<uint64_t> QuicTcpLikeTraceConverter::OnFrameSent(
+    QuicStreamOffset offset,
+    QuicByteCount data_length,
+    bool fin,
+    StreamInfo* info) {
+  QuicIntervalSet<uint64_t> connection_offsets;
+  if (fin) {
+    // Stream fin consumes a connection offset.
+    ++data_length;
+  }
+  // Get connection offsets of retransmission data in this frame.
+  for (const auto& segment : info->segments) {
+    QuicInterval<QuicStreamOffset> retransmission(offset, offset + data_length);
+    retransmission.IntersectWith(segment.stream_data);
+    if (retransmission.Empty()) {
+      continue;
+    }
+    const uint64_t connection_offset = segment.connection_offset +
+                                       retransmission.min() -
+                                       segment.stream_data.min();
+    connection_offsets.Add(connection_offset,
+                           connection_offset + retransmission.Length());
+  }
+
+  if (info->fin) {
+    return connection_offsets;
+  }
+
+  // Get connection offsets of new data in this frame.
+  QuicStreamOffset least_unsent_offset =
+      info->segments.empty() ? 0 : info->segments.back().stream_data.max();
+  if (least_unsent_offset >= offset + data_length) {
+    return connection_offsets;
+  }
+  // Ignore out-of-order stream data so that as connection offset increases,
+  // stream offset increases.
+  QuicStreamOffset new_data_offset = std::max(least_unsent_offset, offset);
+  QuicByteCount new_data_length = offset + data_length - new_data_offset;
+  connection_offsets.Add(connection_offset_,
+                         connection_offset_ + new_data_length);
+  if (!info->segments.empty() && new_data_offset == least_unsent_offset &&
+      connection_offset_ == info->segments.back().connection_offset +
+                                info->segments.back().stream_data.Length()) {
+    // Extend the last segment if both stream and connection offsets are
+    // contiguous.
+    info->segments.back().stream_data.SetMax(new_data_offset + new_data_length);
+  } else {
+    info->segments.emplace_back(new_data_offset, connection_offset_,
+                                new_data_length);
+  }
+  info->fin = fin;
+  connection_offset_ += new_data_length;
+
+  return connection_offsets;
+}
+
+QuicInterval<uint64_t> QuicTcpLikeTraceConverter::OnControlFrameSent(
+    QuicControlFrameId control_frame_id,
+    QuicByteCount control_frame_length) {
+  if (control_frame_id > largest_observed_control_frame_id_) {
+    // New control frame.
+    QuicInterval<uint64_t> connection_offset = QuicInterval<uint64_t>(
+        connection_offset_, connection_offset_ + control_frame_length);
+    connection_offset_ += control_frame_length;
+    control_frames_info_[control_frame_id] = connection_offset;
+    largest_observed_control_frame_id_ = control_frame_id;
+    return connection_offset;
+  }
+  const auto iter = control_frames_info_.find(control_frame_id);
+  if (iter == control_frames_info_.end()) {
+    // Ignore out of order control frames.
+    return {};
+  }
+  return iter->second;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_tcp_like_trace_converter.h b/quiche/quic/tools/quic_tcp_like_trace_converter.h
new file mode 100644
index 0000000..78cbaf9
--- /dev/null
+++ b/quiche/quic/tools/quic_tcp_like_trace_converter.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2018 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_TCP_LIKE_TRACE_CONVERTER_H_
+#define QUICHE_QUIC_TOOLS_QUIC_TCP_LIKE_TRACE_CONVERTER_H_
+
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "quiche/quic/core/frames/quic_stream_frame.h"
+#include "quiche/quic/core/quic_interval.h"
+#include "quiche/quic/core/quic_interval_set.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+
+namespace quic {
+
+// This converter converts sent QUIC frames to connection byte offset (just like
+// TCP byte sequence number).
+class QuicTcpLikeTraceConverter {
+ public:
+  // StreamOffsetSegment stores a stream offset range which has contiguous
+  // connection offset.
+  struct StreamOffsetSegment {
+    StreamOffsetSegment();
+    StreamOffsetSegment(QuicStreamOffset stream_offset,
+                        uint64_t connection_offset,
+                        QuicByteCount data_length);
+
+    QuicInterval<QuicStreamOffset> stream_data;
+    uint64_t connection_offset;
+  };
+
+  QuicTcpLikeTraceConverter();
+  QuicTcpLikeTraceConverter(const QuicTcpLikeTraceConverter& other) = delete;
+  QuicTcpLikeTraceConverter(QuicTcpLikeTraceConverter&& other) = delete;
+
+  ~QuicTcpLikeTraceConverter() {}
+
+  // Called when a crypto frame is sent. Returns the corresponding connection
+  // offsets.
+  QuicIntervalSet<uint64_t> OnCryptoFrameSent(EncryptionLevel level,
+                                              QuicStreamOffset offset,
+                                              QuicByteCount data_length);
+
+  // Called when a stream frame is sent. Returns the corresponding connection
+  // offsets.
+  QuicIntervalSet<uint64_t> OnStreamFrameSent(QuicStreamId stream_id,
+                                              QuicStreamOffset offset,
+                                              QuicByteCount data_length,
+                                              bool fin);
+
+  // Called when a control frame is sent. Returns the corresponding connection
+  // offsets.
+  QuicInterval<uint64_t> OnControlFrameSent(QuicControlFrameId control_frame_id,
+                                            QuicByteCount control_frame_length);
+
+ private:
+  struct StreamInfo {
+    StreamInfo();
+
+    // Stores contiguous connection offset pieces.
+    std::vector<StreamOffsetSegment> segments;
+    // Indicates whether fin has been sent.
+    bool fin;
+  };
+
+  // Called when frame with |offset|, |data_length| and |fin| has been sent.
+  // Update |info| and returns connection offsets.
+  QuicIntervalSet<uint64_t> OnFrameSent(QuicStreamOffset offset,
+                                        QuicByteCount data_length,
+                                        bool fin,
+                                        StreamInfo* info);
+
+  StreamInfo crypto_frames_info_[NUM_ENCRYPTION_LEVELS];
+  absl::flat_hash_map<QuicStreamId, StreamInfo> streams_info_;
+  absl::flat_hash_map<QuicControlFrameId, QuicInterval<uint64_t>>
+      control_frames_info_;
+
+  QuicControlFrameId largest_observed_control_frame_id_;
+
+  uint64_t connection_offset_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_TCP_LIKE_TRACE_CONVERTER_H_
diff --git a/quiche/quic/tools/quic_tcp_like_trace_converter_test.cc b/quiche/quic/tools/quic_tcp_like_trace_converter_test.cc
new file mode 100644
index 0000000..287a319
--- /dev/null
+++ b/quiche/quic/tools/quic_tcp_like_trace_converter_test.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 2018 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 "quiche/quic/tools/quic_tcp_like_trace_converter.h"
+
+#include "quiche/quic/platform/api/quic_test.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+TEST(QuicTcpLikeTraceConverterTest, BasicTest) {
+  QuicTcpLikeTraceConverter converter;
+
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(0, 100),
+            converter.OnStreamFrameSent(1, 0, 100, false));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(100, 200),
+            converter.OnStreamFrameSent(3, 0, 100, false));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(200, 300),
+            converter.OnStreamFrameSent(3, 100, 100, false));
+  EXPECT_EQ(QuicInterval<uint64_t>(300, 450),
+            converter.OnControlFrameSent(2, 150));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(450, 550),
+            converter.OnStreamFrameSent(1, 100, 100, false));
+  EXPECT_EQ(QuicInterval<uint64_t>(550, 650),
+            converter.OnControlFrameSent(3, 100));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(650, 850),
+            converter.OnStreamFrameSent(3, 200, 200, false));
+  EXPECT_EQ(QuicInterval<uint64_t>(850, 1050),
+            converter.OnControlFrameSent(4, 200));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(1050, 1100),
+            converter.OnStreamFrameSent(1, 200, 50, false));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(1100, 1150),
+            converter.OnStreamFrameSent(1, 250, 50, false));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(1150, 1350),
+            converter.OnStreamFrameSent(3, 400, 200, false));
+
+  // Stream 1 retransmits [50, 300) and sends new data [300, 350) in the same
+  // frame.
+  QuicIntervalSet<uint64_t> expected;
+  expected.Add(50, 100);
+  expected.Add(450, 550);
+  expected.Add(1050, 1150);
+  expected.Add(1350, 1401);
+  EXPECT_EQ(expected, converter.OnStreamFrameSent(1, 50, 300, true));
+
+  expected.Clear();
+  // Stream 3 retransmits [150, 500).
+  expected.Add(250, 300);
+  expected.Add(650, 850);
+  expected.Add(1150, 1250);
+  EXPECT_EQ(expected, converter.OnStreamFrameSent(3, 150, 350, false));
+
+  // Stream 3 retransmits [300, 600) and sends new data [600, 800) in the same
+  // frame.
+  expected.Clear();
+  expected.Add(750, 850);
+  expected.Add(1150, 1350);
+  expected.Add(1401, 1602);
+  EXPECT_EQ(expected, converter.OnStreamFrameSent(3, 300, 500, true));
+
+  // Stream 3 retransmits fin only frame.
+  expected.Clear();
+  expected.Add(1601, 1602);
+  EXPECT_EQ(expected, converter.OnStreamFrameSent(3, 800, 0, true));
+
+  QuicInterval<uint64_t> expected2;
+  // Ignore out of order control frames.
+  EXPECT_EQ(expected2, converter.OnControlFrameSent(1, 100));
+
+  // Ignore passed in length for retransmitted frame.
+  expected2 = {300, 450};
+  EXPECT_EQ(expected2, converter.OnControlFrameSent(2, 200));
+
+  expected2 = {1602, 1702};
+  EXPECT_EQ(expected2, converter.OnControlFrameSent(10, 100));
+}
+
+TEST(QuicTcpLikeTraceConverterTest, FuzzerTest) {
+  QuicTcpLikeTraceConverter converter;
+  // Stream does not start from offset 0.
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(0, 100),
+            converter.OnStreamFrameSent(1, 100, 100, false));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(100, 300),
+            converter.OnStreamFrameSent(3, 200, 200, false));
+  // Stream does not send data contiguously.
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(300, 400),
+            converter.OnStreamFrameSent(1, 300, 100, false));
+
+  // Stream fills existing holes.
+  QuicIntervalSet<uint64_t> expected;
+  expected.Add(0, 100);
+  expected.Add(300, 501);
+  EXPECT_EQ(expected, converter.OnStreamFrameSent(1, 0, 500, true));
+
+  // Stream sends frame after fin.
+  EXPECT_EQ(expected, converter.OnStreamFrameSent(1, 50, 600, false));
+}
+
+TEST(QuicTcpLikeTraceConverterTest, OnCryptoFrameSent) {
+  QuicTcpLikeTraceConverter converter;
+
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(0, 100),
+            converter.OnCryptoFrameSent(ENCRYPTION_INITIAL, 0, 100));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(100, 200),
+            converter.OnStreamFrameSent(1, 0, 100, false));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(200, 300),
+            converter.OnStreamFrameSent(1, 100, 100, false));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(300, 400),
+            converter.OnCryptoFrameSent(ENCRYPTION_HANDSHAKE, 0, 100));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(400, 500),
+            converter.OnCryptoFrameSent(ENCRYPTION_HANDSHAKE, 100, 100));
+
+  // Verify crypto frame retransmission works as intended.
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(0, 100),
+            converter.OnCryptoFrameSent(ENCRYPTION_INITIAL, 0, 100));
+  EXPECT_EQ(QuicIntervalSet<uint64_t>(400, 500),
+            converter.OnCryptoFrameSent(ENCRYPTION_HANDSHAKE, 100, 100));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_toy_client.cc b/quiche/quic/tools/quic_toy_client.cc
new file mode 100644
index 0000000..9baccbf
--- /dev/null
+++ b/quiche/quic/tools/quic_toy_client.cc
@@ -0,0 +1,501 @@
+// Copyright (c) 2012 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.
+
+// A binary wrapper for QuicClient.
+// Connects to a host using QUIC, sends a request to the provided URL, and
+// displays the response.
+//
+// Some usage examples:
+//
+// Standard request/response:
+//   quic_client www.google.com
+//   quic_client www.google.com --quiet
+//   quic_client www.google.com --port=443
+//
+// Use a specific version:
+//   quic_client www.google.com --quic_version=23
+//
+// Send a POST instead of a GET:
+//   quic_client www.google.com --body="this is a POST body"
+//
+// Append additional headers to the request:
+//   quic_client www.google.com --headers="Header-A: 1234; Header-B: 5678"
+//
+// Connect to a host different to the URL being requested:
+//   quic_client mail.google.com --host=www.google.com
+//
+// Connect to a specific IP:
+//   IP=`dig www.google.com +short | head -1`
+//   quic_client www.google.com --host=${IP}
+//
+// Send repeated requests and change ephemeral port between requests
+//   quic_client www.google.com --num_requests=10
+//
+// Try to connect to a host which does not speak QUIC:
+//   quic_client www.example.com
+//
+// This tool is available as a built binary at:
+// /google/data/ro/teams/quic/tools/quic_client
+// After submitting changes to this file, you will need to follow the
+// instructions at go/quic_client_binary_update
+
+#include "quiche/quic/tools/quic_toy_client.h"
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/quic_client_session_cache.h"
+#include "quiche/quic/core/quic_packets.h"
+#include "quiche/quic/core/quic_server_id.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_default_proof_providers.h"
+#include "quiche/quic/platform/api/quic_ip_address.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/fake_proof_verifier.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/quiche_text_utils.h"
+
+namespace {
+
+using quiche::QuicheTextUtils;
+
+}  // namespace
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, host, "",
+    "The IP or hostname to connect to. If not provided, the host "
+    "will be derived from the provided URL.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, ip_version_for_host_lookup, "",
+                                "Only used if host address lookup is needed. "
+                                "4=ipv4; 6=ipv6; otherwise=don't care.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, body, "",
+                                "If set, send a POST with this body.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, body_hex, "",
+    "If set, contents are converted from hex to ascii, before "
+    "sending as body of a POST. e.g. --body_hex=\"68656c6c6f\"");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, headers, "",
+    "A semicolon separated list of key:value pairs to "
+    "add to request headers.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quiet, false,
+                                "Set to true for a quieter output experience.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, quic_version, "",
+    "QUIC version to speak, e.g. 21. If not set, then all available "
+    "versions are offered in the handshake. Also supports wire versions "
+    "such as Q043 or T099.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, connection_options, "",
+    "Connection options as ASCII tags separated by commas, "
+    "e.g. \"ABCD,EFGH\"");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, client_connection_options, "",
+    "Client connection options as ASCII tags separated by commas, "
+    "e.g. \"ABCD,EFGH\"");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quic_ietf_draft, false,
+                                "Use the IETF draft version. This also enables "
+                                "required internal QUIC flags.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    bool, version_mismatch_ok, false,
+    "If true, a version mismatch in the handshake is not considered a "
+    "failure. Useful for probing a server to determine if it speaks "
+    "any version of QUIC.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    bool, force_version_negotiation, false,
+    "If true, start by proposing a version that is reserved for version "
+    "negotiation.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    bool, multi_packet_chlo, false,
+    "If true, add a transport parameter to make the ClientHello span two "
+    "packets. Only works with QUIC+TLS.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    bool, redirect_is_success, true,
+    "If true, an HTTP response code of 3xx is considered to be a "
+    "successful response, otherwise a failure.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, initial_mtu, 0,
+                                "Initial MTU of the connection.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    int32_t, num_requests, 1,
+    "How many sequential requests to make on a single connection.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    bool, disable_certificate_verification, false,
+    "If true, don't verify the server certificate.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, default_client_cert, "",
+    "The path to the file containing PEM-encoded client default certificate to "
+    "be sent to the server, if server requested client certs.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, default_client_cert_key, "",
+    "The path to the file containing PEM-encoded private key of the client's "
+    "default certificate for signing, if server requested client certs.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    bool, drop_response_body, false,
+    "If true, drop response body immediately after it is received.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    bool, disable_port_changes, false,
+    "If true, do not change local port after each request.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, one_connection_per_request, false,
+                                "If true, close the connection after each "
+                                "request. This allows testing 0-RTT.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, server_connection_id_length, -1,
+                                "Length of the server connection ID used.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, client_connection_id_length, -1,
+                                "Length of the client connection ID used.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, max_time_before_crypto_handshake_ms,
+                                10000,
+                                "Max time to wait before handshake completes.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    int32_t, max_inbound_header_list_size, 128 * 1024,
+    "Max inbound header list size. 0 means default.");
+
+namespace quic {
+namespace {
+
+// Creates a ClientProofSource which only contains a default client certificate.
+// Return nullptr for failure.
+std::unique_ptr<ClientProofSource> CreateTestClientProofSource(
+    absl::string_view default_client_cert_file,
+    absl::string_view default_client_cert_key_file) {
+  std::ifstream cert_stream(std::string{default_client_cert_file},
+                            std::ios::binary);
+  std::vector<std::string> certs =
+      CertificateView::LoadPemFromStream(&cert_stream);
+  if (certs.empty()) {
+    std::cerr << "Failed to load client certs." << std::endl;
+    return nullptr;
+  }
+
+  std::ifstream key_stream(std::string{default_client_cert_key_file},
+                           std::ios::binary);
+  std::unique_ptr<CertificatePrivateKey> private_key =
+      CertificatePrivateKey::LoadPemFromStream(&key_stream);
+  if (private_key == nullptr) {
+    std::cerr << "Failed to load client cert key." << std::endl;
+    return nullptr;
+  }
+
+  auto proof_source = std::make_unique<DefaultClientProofSource>();
+  proof_source->AddCertAndKey(
+      {"*"},
+      quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>(
+          new ClientProofSource::Chain(certs)),
+      std::move(*private_key));
+
+  return proof_source;
+}
+
+}  // namespace
+
+QuicToyClient::QuicToyClient(ClientFactory* client_factory)
+    : client_factory_(client_factory) {}
+
+int QuicToyClient::SendRequestsAndPrintResponses(
+    std::vector<std::string> urls) {
+  QuicUrl url(urls[0], "https");
+  std::string host = GetQuicFlag(FLAGS_host);
+  if (host.empty()) {
+    host = url.host();
+  }
+  int port = GetQuicFlag(FLAGS_port);
+  if (port == 0) {
+    port = url.port();
+  }
+
+  quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions();
+
+  if (GetQuicFlag(FLAGS_quic_ietf_draft)) {
+    quic::QuicVersionInitializeSupportForIetfDraft();
+    versions = {};
+    for (const ParsedQuicVersion& version : AllSupportedVersions()) {
+      if (version.HasIetfQuicFrames() &&
+          version.handshake_protocol == quic::PROTOCOL_TLS1_3) {
+        versions.push_back(version);
+      }
+    }
+  }
+
+  std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);
+  if (!quic_version_string.empty()) {
+    versions = quic::ParseQuicVersionVectorString(quic_version_string);
+  }
+
+  if (versions.empty()) {
+    std::cerr << "No known version selected." << std::endl;
+    return 1;
+  }
+
+  for (const quic::ParsedQuicVersion& version : versions) {
+    quic::QuicEnableVersion(version);
+  }
+
+  if (GetQuicFlag(FLAGS_force_version_negotiation)) {
+    versions.insert(versions.begin(),
+                    quic::QuicVersionReservedForNegotiation());
+  }
+
+  const int32_t num_requests(GetQuicFlag(FLAGS_num_requests));
+  std::unique_ptr<quic::ProofVerifier> proof_verifier;
+  if (GetQuicFlag(FLAGS_disable_certificate_verification)) {
+    proof_verifier = std::make_unique<FakeProofVerifier>();
+  } else {
+    proof_verifier = quic::CreateDefaultProofVerifier(url.host());
+  }
+  std::unique_ptr<quic::SessionCache> session_cache;
+  if (num_requests > 1 && GetQuicFlag(FLAGS_one_connection_per_request)) {
+    session_cache = std::make_unique<QuicClientSessionCache>();
+  }
+
+  QuicConfig config;
+  std::string connection_options_string = GetQuicFlag(FLAGS_connection_options);
+  if (!connection_options_string.empty()) {
+    config.SetConnectionOptionsToSend(
+        ParseQuicTagVector(connection_options_string));
+  }
+  std::string client_connection_options_string =
+      GetQuicFlag(FLAGS_client_connection_options);
+  if (!client_connection_options_string.empty()) {
+    config.SetClientConnectionOptions(
+        ParseQuicTagVector(client_connection_options_string));
+  }
+  if (GetQuicFlag(FLAGS_multi_packet_chlo)) {
+    // Make the ClientHello span multiple packets by adding a custom transport
+    // parameter.
+    constexpr auto kCustomParameter =
+        static_cast<TransportParameters::TransportParameterId>(0x173E);
+    std::string custom_value(2000, '?');
+    config.custom_transport_parameters_to_send()[kCustomParameter] =
+        custom_value;
+  }
+  config.set_max_time_before_crypto_handshake(QuicTime::Delta::FromMilliseconds(
+      GetQuicFlag(FLAGS_max_time_before_crypto_handshake_ms)));
+
+  int address_family_for_lookup = AF_UNSPEC;
+  if (GetQuicFlag(FLAGS_ip_version_for_host_lookup) == "4") {
+    address_family_for_lookup = AF_INET;
+  } else if (GetQuicFlag(FLAGS_ip_version_for_host_lookup) == "6") {
+    address_family_for_lookup = AF_INET6;
+  }
+
+  // Build the client, and try to connect.
+  std::unique_ptr<QuicSpdyClientBase> client = client_factory_->CreateClient(
+      url.host(), host, address_family_for_lookup, port, versions, config,
+      std::move(proof_verifier), std::move(session_cache));
+
+  if (client == nullptr) {
+    std::cerr << "Failed to create client." << std::endl;
+    return 1;
+  }
+
+  if (!GetQuicFlag(FLAGS_default_client_cert).empty() &&
+      !GetQuicFlag(FLAGS_default_client_cert_key).empty()) {
+    std::unique_ptr<ClientProofSource> proof_source =
+        CreateTestClientProofSource(GetQuicFlag(FLAGS_default_client_cert),
+                                    GetQuicFlag(FLAGS_default_client_cert_key));
+    if (proof_source == nullptr) {
+      std::cerr << "Failed to create client proof source." << std::endl;
+      return 1;
+    }
+    client->crypto_config()->set_proof_source(std::move(proof_source));
+  }
+
+  int32_t initial_mtu = GetQuicFlag(FLAGS_initial_mtu);
+  client->set_initial_max_packet_length(
+      initial_mtu != 0 ? initial_mtu : quic::kDefaultMaxPacketSize);
+  client->set_drop_response_body(GetQuicFlag(FLAGS_drop_response_body));
+  const int32_t server_connection_id_length =
+      GetQuicFlag(FLAGS_server_connection_id_length);
+  if (server_connection_id_length >= 0) {
+    client->set_server_connection_id_length(server_connection_id_length);
+  }
+  const int32_t client_connection_id_length =
+      GetQuicFlag(FLAGS_client_connection_id_length);
+  if (client_connection_id_length >= 0) {
+    client->set_client_connection_id_length(client_connection_id_length);
+  }
+  const size_t max_inbound_header_list_size =
+      GetQuicFlag(FLAGS_max_inbound_header_list_size);
+  if (max_inbound_header_list_size > 0) {
+    client->set_max_inbound_header_list_size(max_inbound_header_list_size);
+  }
+  if (!client->Initialize()) {
+    std::cerr << "Failed to initialize client." << std::endl;
+    return 1;
+  }
+  if (!client->Connect()) {
+    quic::QuicErrorCode error = client->session()->error();
+    if (error == quic::QUIC_INVALID_VERSION) {
+      std::cerr << "Failed to negotiate version with " << host << ":" << port
+                << ". " << client->session()->error_details() << std::endl;
+      // 0: No error.
+      // 20: Failed to connect due to QUIC_INVALID_VERSION.
+      return GetQuicFlag(FLAGS_version_mismatch_ok) ? 0 : 20;
+    }
+    std::cerr << "Failed to connect to " << host << ":" << port << ". "
+              << quic::QuicErrorCodeToString(error) << " "
+              << client->session()->error_details() << std::endl;
+    return 1;
+  }
+  std::cerr << "Connected to " << host << ":" << port << std::endl;
+
+  // Construct the string body from flags, if provided.
+  std::string body = GetQuicFlag(FLAGS_body);
+  if (!GetQuicFlag(FLAGS_body_hex).empty()) {
+    QUICHE_DCHECK(GetQuicFlag(FLAGS_body).empty())
+        << "Only set one of --body and --body_hex.";
+    body = absl::HexStringToBytes(GetQuicFlag(FLAGS_body_hex));
+  }
+
+  // Construct a GET or POST request for supplied URL.
+  spdy::Http2HeaderBlock header_block;
+  header_block[":method"] = body.empty() ? "GET" : "POST";
+  header_block[":scheme"] = url.scheme();
+  header_block[":authority"] = url.HostPort();
+  header_block[":path"] = url.PathParamsQuery();
+
+  // Append any additional headers supplied on the command line.
+  const std::string headers = GetQuicFlag(FLAGS_headers);
+  for (absl::string_view sp : absl::StrSplit(headers, ';')) {
+    QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&sp);
+    if (sp.empty()) {
+      continue;
+    }
+    std::vector<absl::string_view> kv =
+        absl::StrSplit(sp, absl::MaxSplits(':', 1));
+    QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]);
+    QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[1]);
+    header_block[kv[0]] = kv[1];
+  }
+
+  // Make sure to store the response, for later output.
+  client->set_store_response(true);
+
+  for (int i = 0; i < num_requests; ++i) {
+    // Send the request.
+    client->SendRequestAndWaitForResponse(header_block, body, /*fin=*/true);
+
+    // Print request and response details.
+    if (!GetQuicFlag(FLAGS_quiet)) {
+      std::cout << "Request:" << std::endl;
+      std::cout << "headers:" << header_block.DebugString();
+      if (!GetQuicFlag(FLAGS_body_hex).empty()) {
+        // Print the user provided hex, rather than binary body.
+        std::cout << "body:\n"
+                  << QuicheTextUtils::HexDump(
+                         absl::HexStringToBytes(GetQuicFlag(FLAGS_body_hex)))
+                  << std::endl;
+      } else {
+        std::cout << "body: " << body << std::endl;
+      }
+      std::cout << std::endl;
+
+      if (!client->preliminary_response_headers().empty()) {
+        std::cout << "Preliminary response headers: "
+                  << client->preliminary_response_headers() << std::endl;
+        std::cout << std::endl;
+      }
+
+      std::cout << "Response:" << std::endl;
+      std::cout << "headers: " << client->latest_response_headers()
+                << std::endl;
+      std::string response_body = client->latest_response_body();
+      if (!GetQuicFlag(FLAGS_body_hex).empty()) {
+        // Assume response is binary data.
+        std::cout << "body:\n"
+                  << QuicheTextUtils::HexDump(response_body) << std::endl;
+      } else {
+        std::cout << "body: " << response_body << std::endl;
+      }
+      std::cout << "trailers: " << client->latest_response_trailers()
+                << std::endl;
+    }
+
+    if (!client->connected()) {
+      std::cerr << "Request caused connection failure. Error: "
+                << quic::QuicErrorCodeToString(client->session()->error())
+                << std::endl;
+      return 1;
+    }
+
+    int response_code = client->latest_response_code();
+    if (response_code >= 200 && response_code < 300) {
+      std::cout << "Request succeeded (" << response_code << ")." << std::endl;
+    } else if (response_code >= 300 && response_code < 400) {
+      if (GetQuicFlag(FLAGS_redirect_is_success)) {
+        std::cout << "Request succeeded (redirect " << response_code << ")."
+                  << std::endl;
+      } else {
+        std::cout << "Request failed (redirect " << response_code << ")."
+                  << std::endl;
+        return 1;
+      }
+    } else {
+      std::cout << "Request failed (" << response_code << ")." << std::endl;
+      return 1;
+    }
+
+    if (i + 1 < num_requests) {  // There are more requests to perform.
+      if (GetQuicFlag(FLAGS_one_connection_per_request)) {
+        std::cout << "Disconnecting client between requests." << std::endl;
+        client->Disconnect();
+        if (!client->Initialize()) {
+          std::cerr << "Failed to reinitialize client between requests."
+                    << std::endl;
+          return 1;
+        }
+        if (!client->Connect()) {
+          std::cerr << "Failed to reconnect client between requests."
+                    << std::endl;
+          return 1;
+        }
+      } else if (!GetQuicFlag(FLAGS_disable_port_changes)) {
+        // Change the ephemeral port.
+        if (!client->ChangeEphemeralPort()) {
+          std::cerr << "Failed to change ephemeral port." << std::endl;
+          return 1;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_toy_client.h b/quiche/quic/tools/quic_toy_client.h
new file mode 100644
index 0000000..5f884f0
--- /dev/null
+++ b/quiche/quic/tools/quic_toy_client.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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.
+
+// A toy client, which connects to a specified port and sends QUIC
+// requests to that endpoint.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_TOY_CLIENT_H_
+#define QUICHE_QUIC_TOOLS_QUIC_TOY_CLIENT_H_
+
+#include "quiche/quic/tools/quic_spdy_client_base.h"
+
+namespace quic {
+
+class QuicToyClient {
+ public:
+  class ClientFactory {
+   public:
+    virtual ~ClientFactory() = default;
+
+    // Creates a new client configured to connect to |host_for_lookup:port|
+    // supporting |versions|, using |host_for_handshake| for handshake and
+    // |verifier| to verify proofs.
+    virtual std::unique_ptr<QuicSpdyClientBase> CreateClient(
+        std::string host_for_handshake,
+        std::string host_for_lookup,
+        // AF_INET, AF_INET6, or AF_UNSPEC(=don't care).
+        int address_family_for_lookup,
+        uint16_t port,
+        ParsedQuicVersionVector versions,
+        const QuicConfig& config,
+        std::unique_ptr<ProofVerifier> verifier,
+        std::unique_ptr<SessionCache> session_cache) = 0;
+  };
+
+  // Constructs a new toy client that will use |client_factory| to create the
+  // actual QuicSpdyClientBase instance.
+  QuicToyClient(ClientFactory* client_factory);
+
+  // Connects to the QUIC server based on the various flags defined in the
+  // .cc file, sends requests and prints the responses. Returns 0 on success
+  // and non-zero otherwise.
+  int SendRequestsAndPrintResponses(std::vector<std::string> urls);
+
+ private:
+  ClientFactory* client_factory_;  // Unowned.
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_TOY_CLIENT_H_
diff --git a/quiche/quic/tools/quic_toy_server.cc b/quiche/quic/tools/quic_toy_server.cc
new file mode 100644
index 0000000..03f910c
--- /dev/null
+++ b/quiche/quic/tools/quic_toy_server.cc
@@ -0,0 +1,103 @@
+// Copyright 2014 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 "quiche/quic/tools/quic_toy_server.h"
+
+#include <utility>
+#include <vector>
+
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_default_proof_providers.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/quic_memory_cache_backend.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 6121,
+                                "The port the quic server will listen on.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, quic_response_cache_dir, "",
+    "Specifies the directory used during QuicHttpResponseCache "
+    "construction to seed the cache. Cache directory can be "
+    "generated using `wget -p --save-headers <url>`");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    bool, generate_dynamic_responses, false,
+    "If true, then URLs which have a numeric path will send a dynamically "
+    "generated response of that many bytes.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, quic_ietf_draft, false,
+                                "Only enable IETF draft versions. This also "
+                                "enables required internal QUIC flags.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, quic_versions, "",
+    "QUIC versions to enable, e.g. \"h3-25,h3-27\". If not set, then all "
+    "available versions are enabled.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, enable_webtransport, false,
+                                "If true, WebTransport support is enabled.");
+
+namespace quic {
+
+std::unique_ptr<quic::QuicSimpleServerBackend>
+QuicToyServer::MemoryCacheBackendFactory::CreateBackend() {
+  auto memory_cache_backend = std::make_unique<QuicMemoryCacheBackend>();
+  if (GetQuicFlag(FLAGS_generate_dynamic_responses)) {
+    memory_cache_backend->GenerateDynamicResponses();
+  }
+  if (!GetQuicFlag(FLAGS_quic_response_cache_dir).empty()) {
+    memory_cache_backend->InitializeBackend(
+        GetQuicFlag(FLAGS_quic_response_cache_dir));
+  }
+  if (GetQuicFlag(FLAGS_enable_webtransport)) {
+    memory_cache_backend->EnableWebTransport();
+  }
+  return memory_cache_backend;
+}
+
+QuicToyServer::QuicToyServer(BackendFactory* backend_factory,
+                             ServerFactory* server_factory)
+    : backend_factory_(backend_factory), server_factory_(server_factory) {}
+
+int QuicToyServer::Start() {
+  ParsedQuicVersionVector supported_versions;
+  if (GetQuicFlag(FLAGS_quic_ietf_draft)) {
+    QuicVersionInitializeSupportForIetfDraft();
+    for (const ParsedQuicVersion& version : AllSupportedVersions()) {
+      // Add all versions that supports IETF QUIC.
+      if (version.HasIetfQuicFrames() &&
+          version.handshake_protocol == quic::PROTOCOL_TLS1_3) {
+        supported_versions.push_back(version);
+      }
+    }
+  } else {
+    supported_versions = AllSupportedVersions();
+  }
+  std::string versions_string = GetQuicFlag(FLAGS_quic_versions);
+  if (!versions_string.empty()) {
+    supported_versions = ParseQuicVersionVectorString(versions_string);
+  }
+  if (supported_versions.empty()) {
+    return 1;
+  }
+  for (const auto& version : supported_versions) {
+    QuicEnableVersion(version);
+  }
+  auto proof_source = quic::CreateDefaultProofSource();
+  auto backend = backend_factory_->CreateBackend();
+  auto server = server_factory_->CreateServer(
+      backend.get(), std::move(proof_source), supported_versions);
+
+  if (!server->CreateUDPSocketAndListen(quic::QuicSocketAddress(
+          quic::QuicIpAddress::Any6(), GetQuicFlag(FLAGS_port)))) {
+    return 1;
+  }
+
+  server->HandleEventsForever();
+  return 0;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_toy_server.h b/quiche/quic/tools/quic_toy_server.h
new file mode 100644
index 0000000..fc82ff7
--- /dev/null
+++ b/quiche/quic/tools/quic_toy_server.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_TOY_SERVER_H_
+#define QUICHE_QUIC_TOOLS_QUIC_TOY_SERVER_H_
+
+#include "quiche/quic/core/crypto/proof_source.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/quic/tools/quic_spdy_server_base.h"
+
+namespace quic {
+
+// A binary wrapper for QuicServer.  It listens forever on --port
+// (default 6121) until it's killed or ctrl-cd to death.
+class QuicToyServer {
+ public:
+  // A factory for creating QuicSpdyServerBase instances.
+  class ServerFactory {
+   public:
+    virtual ~ServerFactory() = default;
+
+    // Creates a QuicSpdyServerBase instance using |backend| for generating
+    // responses, and |proof_source| for certificates.
+    virtual std::unique_ptr<QuicSpdyServerBase> CreateServer(
+        QuicSimpleServerBackend* backend,
+        std::unique_ptr<ProofSource> proof_source,
+        const ParsedQuicVersionVector& supported_versions) = 0;
+  };
+
+  // A facotry for creating QuicSimpleServerBackend instances.
+  class BackendFactory {
+   public:
+    virtual ~BackendFactory() = default;
+
+    // Creates a new backend.
+    virtual std::unique_ptr<QuicSimpleServerBackend> CreateBackend() = 0;
+  };
+
+  // A factory for creating QuicMemoryCacheBackend instances, configured
+  // to load files from disk, if necessary.
+  class MemoryCacheBackendFactory : public BackendFactory {
+   public:
+    std::unique_ptr<quic::QuicSimpleServerBackend> CreateBackend() override;
+  };
+
+  // Constructs a new toy server that will use |server_factory| to create the
+  // actual QuicSpdyServerBase instance.
+  QuicToyServer(BackendFactory* backend_factory, ServerFactory* server_factory);
+
+  // Connects to the QUIC server based on the various flags defined in the
+  // .cc file, listends for requests and sends the responses. Returns 1 on
+  // failure and does not return otherwise.
+  int Start();
+
+ private:
+  BackendFactory* backend_factory_;  // Unowned.
+  ServerFactory* server_factory_;    // Unowned.
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_TOY_SERVER_H_
diff --git a/quiche/quic/tools/quic_transport_simple_server_dispatcher.cc b/quiche/quic/tools/quic_transport_simple_server_dispatcher.cc
new file mode 100644
index 0000000..91c7816
--- /dev/null
+++ b/quiche/quic/tools/quic_transport_simple_server_dispatcher.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2019 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 "quiche/quic/tools/quic_transport_simple_server_dispatcher.h"
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_dispatcher.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/tools/quic_transport_simple_server_session.h"
+
+namespace quic {
+
+QuicTransportSimpleServerDispatcher::QuicTransportSimpleServerDispatcher(
+    const QuicConfig* config,
+    const QuicCryptoServerConfig* crypto_config,
+    QuicVersionManager* version_manager,
+    std::unique_ptr<QuicConnectionHelperInterface> helper,
+    std::unique_ptr<QuicCryptoServerStreamBase::Helper> session_helper,
+    std::unique_ptr<QuicAlarmFactory> alarm_factory,
+    uint8_t expected_server_connection_id_length,
+    std::vector<url::Origin> accepted_origins)
+    : QuicDispatcher(config,
+                     crypto_config,
+                     version_manager,
+                     std::move(helper),
+                     std::move(session_helper),
+                     std::move(alarm_factory),
+                     expected_server_connection_id_length),
+      accepted_origins_(accepted_origins) {}
+
+std::unique_ptr<QuicSession>
+QuicTransportSimpleServerDispatcher::CreateQuicSession(
+    QuicConnectionId server_connection_id,
+    const QuicSocketAddress& self_address,
+    const QuicSocketAddress& peer_address, absl::string_view /*alpn*/,
+    const ParsedQuicVersion& version,
+    const ParsedClientHello& /*parsed_chlo*/) {
+  auto connection = std::make_unique<QuicConnection>(
+      server_connection_id, self_address, peer_address, helper(),
+      alarm_factory(), writer(),
+      /*owns_writer=*/false, Perspective::IS_SERVER,
+      ParsedQuicVersionVector{version});
+  auto session = std::make_unique<QuicTransportSimpleServerSession>(
+      connection.release(), /*owns_connection=*/true, this, config(),
+      GetSupportedVersions(), crypto_config(), compressed_certs_cache(),
+      accepted_origins_);
+  session->Initialize();
+  return session;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_transport_simple_server_dispatcher.h b/quiche/quic/tools/quic_transport_simple_server_dispatcher.h
new file mode 100644
index 0000000..a70aae1
--- /dev/null
+++ b/quiche/quic/tools/quic_transport_simple_server_dispatcher.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_DISPATCHER_H_
+#define QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_DISPATCHER_H_
+
+#include "absl/strings/string_view.h"
+#include "url/origin.h"
+#include "quiche/quic/core/quic_dispatcher.h"
+#include "quiche/quic/tools/quic_transport_simple_server_session.h"
+
+namespace quic {
+
+// Dispatcher that creates a QuicTransportSimpleServerSession for every incoming
+// connection.
+class QuicTransportSimpleServerDispatcher : public QuicDispatcher {
+ public:
+  QuicTransportSimpleServerDispatcher(
+      const QuicConfig* config,
+      const QuicCryptoServerConfig* crypto_config,
+      QuicVersionManager* version_manager,
+      std::unique_ptr<QuicConnectionHelperInterface> helper,
+      std::unique_ptr<QuicCryptoServerStreamBase::Helper> session_helper,
+      std::unique_ptr<QuicAlarmFactory> alarm_factory,
+      uint8_t expected_server_connection_id_length,
+      std::vector<url::Origin> accepted_origins);
+
+ protected:
+  std::unique_ptr<QuicSession> CreateQuicSession(
+      QuicConnectionId server_connection_id,
+      const QuicSocketAddress& self_address,
+      const QuicSocketAddress& peer_address, absl::string_view alpn,
+      const ParsedQuicVersion& version,
+      const ParsedClientHello& parsed_chlo) override;
+
+  std::vector<url::Origin> accepted_origins_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_DISPATCHER_H_
diff --git a/quiche/quic/tools/quic_transport_simple_server_session.cc b/quiche/quic/tools/quic_transport_simple_server_session.cc
new file mode 100644
index 0000000..722e131
--- /dev/null
+++ b/quiche/quic/tools/quic_transport_simple_server_session.cc
@@ -0,0 +1,176 @@
+// Copyright (c) 2019 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 "quiche/quic/tools/quic_transport_simple_server_session.h"
+
+#include <memory>
+
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/quic_transport/quic_transport_protocol.h"
+#include "quiche/quic/quic_transport/quic_transport_stream.h"
+#include "quiche/quic/tools/web_transport_test_visitors.h"
+#include "quiche/common/platform/api/quiche_mem_slice.h"
+#include "quiche/common/quiche_buffer_allocator.h"
+
+namespace quic {
+
+QuicTransportSimpleServerSession::QuicTransportSimpleServerSession(
+    QuicConnection* connection,
+    bool owns_connection,
+    Visitor* owner,
+    const QuicConfig& config,
+    const ParsedQuicVersionVector& supported_versions,
+    const QuicCryptoServerConfig* crypto_config,
+    QuicCompressedCertsCache* compressed_certs_cache,
+    std::vector<url::Origin> accepted_origins)
+    : QuicTransportServerSession(connection,
+                                 owner,
+                                 config,
+                                 supported_versions,
+                                 crypto_config,
+                                 compressed_certs_cache,
+                                 this),
+      owns_connection_(owns_connection),
+      mode_(DISCARD),
+      accepted_origins_(accepted_origins) {}
+
+QuicTransportSimpleServerSession::~QuicTransportSimpleServerSession() {
+  if (owns_connection_) {
+    DeleteConnection();
+  }
+}
+
+void QuicTransportSimpleServerSession::OnIncomingDataStream(
+    QuicTransportStream* stream) {
+  switch (mode_) {
+    case DISCARD:
+      stream->SetVisitor(std::make_unique<WebTransportDiscardVisitor>(stream));
+      break;
+
+    case ECHO:
+      switch (stream->type()) {
+        case BIDIRECTIONAL:
+          QUIC_DVLOG(1) << "Opening bidirectional echo stream " << stream->id();
+          stream->SetVisitor(
+              std::make_unique<WebTransportBidirectionalEchoVisitor>(stream));
+          break;
+        case READ_UNIDIRECTIONAL:
+          QUIC_DVLOG(1)
+              << "Started receiving data on unidirectional echo stream "
+              << stream->id();
+          stream->SetVisitor(
+              std::make_unique<WebTransportUnidirectionalEchoReadVisitor>(
+                  stream,
+                  [this](const std::string& s) { this->EchoStreamBack(s); }));
+          break;
+        default:
+          QUIC_NOTREACHED();
+          break;
+      }
+      break;
+
+    case OUTGOING_BIDIRECTIONAL:
+      stream->SetVisitor(std::make_unique<WebTransportDiscardVisitor>(stream));
+      ++pending_outgoing_bidirectional_streams_;
+      MaybeCreateOutgoingBidirectionalStream();
+      break;
+  }
+}
+
+void QuicTransportSimpleServerSession::OnCanCreateNewOutgoingStream(
+    bool unidirectional) {
+  if (mode_ == ECHO && unidirectional) {
+    MaybeEchoStreamsBack();
+  } else if (mode_ == OUTGOING_BIDIRECTIONAL && !unidirectional) {
+    MaybeCreateOutgoingBidirectionalStream();
+  }
+}
+
+bool QuicTransportSimpleServerSession::CheckOrigin(url::Origin origin) {
+  if (accepted_origins_.empty()) {
+    return true;
+  }
+
+  for (const url::Origin& accepted_origin : accepted_origins_) {
+    if (origin.IsSameOriginWith(accepted_origin)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool QuicTransportSimpleServerSession::ProcessPath(const GURL& url) {
+  if (url.path() == "/discard") {
+    mode_ = DISCARD;
+    return true;
+  }
+  if (url.path() == "/echo") {
+    mode_ = ECHO;
+    return true;
+  }
+  if (url.path() == "/receive-bidirectional") {
+    mode_ = OUTGOING_BIDIRECTIONAL;
+    return true;
+  }
+
+  QUIC_DLOG(WARNING) << "Unknown path requested: " << url.path();
+  return false;
+}
+
+void QuicTransportSimpleServerSession::OnMessageReceived(
+    absl::string_view message) {
+  if (mode_ != ECHO) {
+    return;
+  }
+  datagram_queue()->SendOrQueueDatagram(
+      quiche::QuicheMemSlice(quiche::QuicheBuffer::Copy(
+          connection()->helper()->GetStreamSendBufferAllocator(), message)));
+}
+
+void QuicTransportSimpleServerSession::MaybeEchoStreamsBack() {
+  while (!streams_to_echo_back_.empty() &&
+         CanOpenNextOutgoingUnidirectionalStream()) {
+    // Remove the stream from the queue first, in order to avoid accidentally
+    // entering an infinite loop in case any of the following code calls
+    // OnCanCreateNewOutgoingStream().
+    std::string data = std::move(streams_to_echo_back_.front());
+    streams_to_echo_back_.pop_front();
+
+    auto stream_owned = std::make_unique<QuicTransportStream>(
+        GetNextOutgoingUnidirectionalStreamId(), this, this);
+    QuicTransportStream* stream = stream_owned.get();
+    ActivateStream(std::move(stream_owned));
+    QUIC_DVLOG(1) << "Opened echo response stream " << stream->id();
+
+    stream->SetVisitor(
+        std::make_unique<WebTransportUnidirectionalEchoWriteVisitor>(stream,
+                                                                     data));
+    stream->visitor()->OnCanWrite();
+  }
+}
+
+void QuicTransportSimpleServerSession::
+    MaybeCreateOutgoingBidirectionalStream() {
+  while (pending_outgoing_bidirectional_streams_ > 0 &&
+         CanOpenNextOutgoingBidirectionalStream()) {
+    auto stream_owned = std::make_unique<QuicTransportStream>(
+        GetNextOutgoingBidirectionalStreamId(), this, this);
+    QuicTransportStream* stream = stream_owned.get();
+    ActivateStream(std::move(stream_owned));
+    QUIC_DVLOG(1) << "Opened outgoing bidirectional stream " << stream->id();
+    stream->SetVisitor(
+        std::make_unique<WebTransportBidirectionalEchoVisitor>(stream));
+    if (!stream->Write("hello")) {
+      QUIC_DVLOG(1) << "Write failed.";
+    }
+    --pending_outgoing_bidirectional_streams_;
+  }
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_transport_simple_server_session.h b/quiche/quic/tools/quic_transport_simple_server_session.h
new file mode 100644
index 0000000..f5c3934
--- /dev/null
+++ b/quiche/quic/tools/quic_transport_simple_server_session.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2019 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_SESSION_H_
+#define QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_SESSION_H_
+
+#include <memory>
+#include <vector>
+
+#include "url/origin.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_containers.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/quic_transport/quic_transport_server_session.h"
+#include "quiche/quic/quic_transport/quic_transport_stream.h"
+
+namespace quic {
+
+// QuicTransport simple server is a non-production server that can be used for
+// testing QuicTransport.  It has two modes that can be changed using the
+// command line flags, "echo" and "discard".
+class QuicTransportSimpleServerSession
+    : public QuicTransportServerSession,
+      QuicTransportServerSession::ServerVisitor {
+ public:
+  enum Mode {
+    // In DISCARD mode, any data on incoming streams is discarded and no
+    // outgoing streams are initiated.
+    DISCARD,
+    // In ECHO mode, any data sent on a bidirectional stream is echoed back.
+    // Any data sent on a unidirectional stream is buffered, and echoed back on
+    // a server-initiated unidirectional stream that is sent as soon as a FIN is
+    // received on the incoming stream.
+    ECHO,
+    // In OUTGOING_BIDIRECTIONAL mode, a server-originated bidirectional stream
+    // is created on receipt of a unidirectional stream. The contents of the
+    // unidirectional stream are disregarded. The bidirectional stream initially
+    // sends "hello", then any received data is echoed back.
+    // TODO(ricea): Maybe this should be replaced by a more general mechanism
+    // where commands on the unidirectional stream trigger different behaviour?
+    OUTGOING_BIDIRECTIONAL,
+  };
+
+  QuicTransportSimpleServerSession(
+      QuicConnection* connection,
+      bool owns_connection,
+      Visitor* owner,
+      const QuicConfig& config,
+      const ParsedQuicVersionVector& supported_versions,
+      const QuicCryptoServerConfig* crypto_config,
+      QuicCompressedCertsCache* compressed_certs_cache,
+      std::vector<url::Origin> accepted_origins);
+  ~QuicTransportSimpleServerSession();
+
+  void OnIncomingDataStream(QuicTransportStream* stream) override;
+  void OnCanCreateNewOutgoingStream(bool unidirectional) override;
+  bool CheckOrigin(url::Origin origin) override;
+  bool ProcessPath(const GURL& url) override;
+  void OnMessageReceived(absl::string_view message) override;
+
+  void EchoStreamBack(const std::string& data) {
+    streams_to_echo_back_.push_back(data);
+    MaybeEchoStreamsBack();
+  }
+
+ private:
+  void MaybeEchoStreamsBack();
+  void MaybeCreateOutgoingBidirectionalStream();
+
+  const bool owns_connection_;
+  size_t pending_outgoing_bidirectional_streams_ = 0u;
+  Mode mode_;
+  std::vector<url::Origin> accepted_origins_;
+  quiche::QuicheCircularDeque<std::string> streams_to_echo_back_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_TRANSPORT_SIMPLE_SERVER_SESSION_H_
diff --git a/quiche/quic/tools/quic_url.cc b/quiche/quic/tools/quic_url.cc
new file mode 100644
index 0000000..db52720
--- /dev/null
+++ b/quiche/quic/tools/quic_url.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_url.h"
+
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+
+namespace quic {
+
+static constexpr size_t kMaxHostNameLength = 256;
+
+QuicUrl::QuicUrl(absl::string_view url) : url_(static_cast<std::string>(url)) {}
+
+QuicUrl::QuicUrl(absl::string_view url, absl::string_view default_scheme)
+    : QuicUrl(url) {
+  if (url_.has_scheme()) {
+    return;
+  }
+
+  url_ = GURL(absl::StrCat(default_scheme, "://", url));
+}
+
+std::string QuicUrl::ToString() const {
+  if (IsValid()) {
+    return url_.spec();
+  }
+  return "";
+}
+
+bool QuicUrl::IsValid() const {
+  if (!url_.is_valid() || !url_.has_scheme()) {
+    return false;
+  }
+
+  if (url_.has_host() && url_.host().length() > kMaxHostNameLength) {
+    return false;
+  }
+
+  return true;
+}
+
+std::string QuicUrl::HostPort() const {
+  if (!IsValid() || !url_.has_host()) {
+    return "";
+  }
+
+  std::string host = url_.host();
+  int port = url_.IntPort();
+  if (port == url::PORT_UNSPECIFIED) {
+    return host;
+  }
+  return absl::StrCat(host, ":", port);
+}
+
+std::string QuicUrl::PathParamsQuery() const {
+  if (!IsValid() || !url_.has_path()) {
+    return "/";
+  }
+
+  return url_.PathForRequest();
+}
+
+std::string QuicUrl::scheme() const {
+  if (!IsValid()) {
+    return "";
+  }
+
+  return url_.scheme();
+}
+
+std::string QuicUrl::host() const {
+  if (!IsValid()) {
+    return "";
+  }
+
+  return url_.HostNoBrackets();
+}
+
+std::string QuicUrl::path() const {
+  if (!IsValid()) {
+    return "";
+  }
+
+  return url_.path();
+}
+
+uint16_t QuicUrl::port() const {
+  if (!IsValid()) {
+    return 0;
+  }
+
+  int port = url_.EffectiveIntPort();
+  if (port == url::PORT_UNSPECIFIED) {
+    return 0;
+  }
+  return port;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/quic_url.h b/quiche/quic/tools/quic_url.h
new file mode 100644
index 0000000..78b21ec
--- /dev/null
+++ b/quiche/quic/tools/quic_url.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 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.
+
+#ifndef QUICHE_QUIC_TOOLS_QUIC_URL_H_
+#define QUICHE_QUIC_TOOLS_QUIC_URL_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "url/gurl.h"
+#include "quiche/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+// A utility class that wraps GURL.
+class QuicUrl {
+ public:
+  // Constructs an empty QuicUrl.
+  QuicUrl() = default;
+
+  // Constructs a QuicUrl from the url string |url|.
+  //
+  // NOTE: If |url| doesn't have a scheme, it will have an empty scheme
+  // field. If that's not what you want, use the QuicUrlImpl(url,
+  // default_scheme) form below.
+  explicit QuicUrl(absl::string_view url);
+
+  // Constructs a QuicUrlImpl from |url|, assuming that the scheme for the URL
+  // is |default_scheme| if there is no scheme specified in |url|.
+  QuicUrl(absl::string_view url, absl::string_view default_scheme);
+
+  // Returns false if the URL is not valid.
+  bool IsValid() const;
+
+  // Returns full text of the QuicUrl if it is valid. Return empty string
+  // otherwise.
+  std::string ToString() const;
+
+  // Returns host:port.
+  // If the host is empty, it will return an empty string.
+  // If the host is an IPv6 address, it will be bracketed.
+  // If port is not present or is equal to default_port of scheme (e.g., port
+  // 80 for HTTP), it won't be returned.
+  std::string HostPort() const;
+
+  // Returns a string assembles path, parameters and query.
+  std::string PathParamsQuery() const;
+
+  std::string scheme() const;
+  std::string host() const;
+  std::string path() const;
+  uint16_t port() const;
+
+ private:
+  GURL url_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_QUIC_URL_H_
diff --git a/quiche/quic/tools/quic_url_test.cc b/quiche/quic/tools/quic_url_test.cc
new file mode 100644
index 0000000..8f26e01
--- /dev/null
+++ b/quiche/quic/tools/quic_url_test.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 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 "quiche/quic/tools/quic_url.h"
+
+#include <string>
+
+#include "quiche/quic/platform/api/quic_test.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+class QuicUrlTest : public QuicTest {};
+
+TEST_F(QuicUrlTest, Basic) {
+  // No scheme specified.
+  std::string url_str = "www.example.com";
+  QuicUrl url(url_str);
+  EXPECT_FALSE(url.IsValid());
+
+  // scheme is HTTP.
+  url_str = "http://www.example.com";
+  url = QuicUrl(url_str);
+  EXPECT_TRUE(url.IsValid());
+  EXPECT_EQ("http://www.example.com/", url.ToString());
+  EXPECT_EQ("http", url.scheme());
+  EXPECT_EQ("www.example.com", url.HostPort());
+  EXPECT_EQ("/", url.PathParamsQuery());
+  EXPECT_EQ(80u, url.port());
+
+  // scheme is HTTPS.
+  url_str = "https://www.example.com:12345/path/to/resource?a=1&campaign=2";
+  url = QuicUrl(url_str);
+  EXPECT_TRUE(url.IsValid());
+  EXPECT_EQ("https://www.example.com:12345/path/to/resource?a=1&campaign=2",
+            url.ToString());
+  EXPECT_EQ("https", url.scheme());
+  EXPECT_EQ("www.example.com:12345", url.HostPort());
+  EXPECT_EQ("/path/to/resource?a=1&campaign=2", url.PathParamsQuery());
+  EXPECT_EQ(12345u, url.port());
+
+  // scheme is FTP.
+  url_str = "ftp://www.example.com";
+  url = QuicUrl(url_str);
+  EXPECT_TRUE(url.IsValid());
+  EXPECT_EQ("ftp://www.example.com/", url.ToString());
+  EXPECT_EQ("ftp", url.scheme());
+  EXPECT_EQ("www.example.com", url.HostPort());
+  EXPECT_EQ("/", url.PathParamsQuery());
+  EXPECT_EQ(21u, url.port());
+}
+
+TEST_F(QuicUrlTest, DefaultScheme) {
+  // Default scheme to HTTP.
+  std::string url_str = "www.example.com";
+  QuicUrl url(url_str, "http");
+  EXPECT_EQ("http://www.example.com/", url.ToString());
+  EXPECT_EQ("http", url.scheme());
+
+  // URL already has a scheme specified.
+  url_str = "http://www.example.com";
+  url = QuicUrl(url_str, "https");
+  EXPECT_EQ("http://www.example.com/", url.ToString());
+  EXPECT_EQ("http", url.scheme());
+
+  // Default scheme to FTP.
+  url_str = "www.example.com";
+  url = QuicUrl(url_str, "ftp");
+  EXPECT_EQ("ftp://www.example.com/", url.ToString());
+  EXPECT_EQ("ftp", url.scheme());
+}
+
+TEST_F(QuicUrlTest, IsValid) {
+  std::string url_str =
+      "ftp://www.example.com:12345/path/to/resource?a=1&campaign=2";
+  EXPECT_TRUE(QuicUrl(url_str).IsValid());
+
+  // Invalid characters in host name.
+  url_str = "https://www%.example.com:12345/path/to/resource?a=1&campaign=2";
+  EXPECT_FALSE(QuicUrl(url_str).IsValid());
+
+  // Invalid characters in scheme.
+  url_str = "%http://www.example.com:12345/path/to/resource?a=1&campaign=2";
+  EXPECT_FALSE(QuicUrl(url_str).IsValid());
+
+  // Host name too long.
+  std::string host(1024, 'a');
+  url_str = "https://" + host;
+  EXPECT_FALSE(QuicUrl(url_str).IsValid());
+
+  // Invalid port number.
+  url_str = "https://www..example.com:123456/path/to/resource?a=1&campaign=2";
+  EXPECT_FALSE(QuicUrl(url_str).IsValid());
+}
+
+TEST_F(QuicUrlTest, HostPort) {
+  std::string url_str = "http://www.example.com/";
+  QuicUrl url(url_str);
+  EXPECT_EQ("www.example.com", url.HostPort());
+  EXPECT_EQ("www.example.com", url.host());
+  EXPECT_EQ(80u, url.port());
+
+  url_str = "http://www.example.com:80/";
+  url = QuicUrl(url_str);
+  EXPECT_EQ("www.example.com", url.HostPort());
+  EXPECT_EQ("www.example.com", url.host());
+  EXPECT_EQ(80u, url.port());
+
+  url_str = "http://www.example.com:81/";
+  url = QuicUrl(url_str);
+  EXPECT_EQ("www.example.com:81", url.HostPort());
+  EXPECT_EQ("www.example.com", url.host());
+  EXPECT_EQ(81u, url.port());
+
+  url_str = "https://192.168.1.1:443/";
+  url = QuicUrl(url_str);
+  EXPECT_EQ("192.168.1.1", url.HostPort());
+  EXPECT_EQ("192.168.1.1", url.host());
+  EXPECT_EQ(443u, url.port());
+
+  url_str = "http://[2001::1]:80/";
+  url = QuicUrl(url_str);
+  EXPECT_EQ("[2001::1]", url.HostPort());
+  EXPECT_EQ("2001::1", url.host());
+  EXPECT_EQ(80u, url.port());
+
+  url_str = "http://[2001::1]:81/";
+  url = QuicUrl(url_str);
+  EXPECT_EQ("[2001::1]:81", url.HostPort());
+  EXPECT_EQ("2001::1", url.host());
+  EXPECT_EQ(81u, url.port());
+}
+
+TEST_F(QuicUrlTest, PathParamsQuery) {
+  std::string url_str =
+      "https://www.example.com:12345/path/to/resource?a=1&campaign=2";
+  QuicUrl url(url_str);
+  EXPECT_EQ("/path/to/resource?a=1&campaign=2", url.PathParamsQuery());
+  EXPECT_EQ("/path/to/resource", url.path());
+
+  url_str = "https://www.example.com/?";
+  url = QuicUrl(url_str);
+  EXPECT_EQ("/?", url.PathParamsQuery());
+  EXPECT_EQ("/", url.path());
+
+  url_str = "https://www.example.com/";
+  url = QuicUrl(url_str);
+  EXPECT_EQ("/", url.PathParamsQuery());
+  EXPECT_EQ("/", url.path());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quiche/quic/tools/simple_ticket_crypter.cc b/quiche/quic/tools/simple_ticket_crypter.cc
new file mode 100644
index 0000000..ef788d6
--- /dev/null
+++ b/quiche/quic/tools/simple_ticket_crypter.cc
@@ -0,0 +1,112 @@
+// Copyright 2020 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 "quiche/quic/tools/simple_ticket_crypter.h"
+
+#include "third_party/boringssl/src/include/openssl/aead.h"
+#include "third_party/boringssl/src/include/openssl/rand.h"
+
+namespace quic {
+
+namespace {
+
+constexpr QuicTime::Delta kTicketKeyLifetime =
+    QuicTime::Delta::FromSeconds(60 * 60 * 24 * 7);
+
+// The format of an encrypted ticket is 1 byte for the key epoch, followed by
+// 16 bytes of IV, followed by the output from the AES-GCM Seal operation. The
+// seal operation has an overhead of 16 bytes for its auth tag.
+constexpr size_t kEpochSize = 1;
+constexpr size_t kIVSize = 16;
+constexpr size_t kAuthTagSize = 16;
+
+// Offsets into the ciphertext to make message parsing easier.
+constexpr size_t kIVOffset = kEpochSize;
+constexpr size_t kMessageOffset = kIVOffset + kIVSize;
+
+}  // namespace
+
+SimpleTicketCrypter::SimpleTicketCrypter(QuicClock* clock) : clock_(clock) {
+  RAND_bytes(&key_epoch_, 1);
+  current_key_ = NewKey();
+}
+
+SimpleTicketCrypter::~SimpleTicketCrypter() = default;
+
+size_t SimpleTicketCrypter::MaxOverhead() {
+  return kEpochSize + kIVSize + kAuthTagSize;
+}
+
+std::vector<uint8_t> SimpleTicketCrypter::Encrypt(
+    absl::string_view in, absl::string_view encryption_key) {
+  // This class is only used in Chromium, in which the |encryption_key| argument
+  // will never be populated and an internally-cached key should be used for
+  // encrypting tickets.
+  QUICHE_DCHECK(encryption_key.empty());
+  MaybeRotateKeys();
+  std::vector<uint8_t> out(in.size() + MaxOverhead());
+  out[0] = key_epoch_;
+  RAND_bytes(out.data() + kIVOffset, kIVSize);
+  size_t out_len;
+  const EVP_AEAD_CTX* ctx = current_key_->aead_ctx.get();
+  if (!EVP_AEAD_CTX_seal(ctx, out.data() + kMessageOffset, &out_len,
+                         out.size() - kMessageOffset, out.data() + kIVOffset,
+                         kIVSize, reinterpret_cast<const uint8_t*>(in.data()),
+                         in.size(), nullptr, 0)) {
+    return std::vector<uint8_t>();
+  }
+  out.resize(out_len + kMessageOffset);
+  return out;
+}
+
+std::vector<uint8_t> SimpleTicketCrypter::Decrypt(absl::string_view in) {
+  MaybeRotateKeys();
+  if (in.size() < kMessageOffset) {
+    return std::vector<uint8_t>();
+  }
+  const uint8_t* input = reinterpret_cast<const uint8_t*>(in.data());
+  std::vector<uint8_t> out(in.size() - kMessageOffset);
+  size_t out_len;
+  const EVP_AEAD_CTX* ctx = current_key_->aead_ctx.get();
+  if (input[0] != key_epoch_) {
+    if (input[0] == static_cast<uint8_t>(key_epoch_ - 1) && previous_key_) {
+      ctx = previous_key_->aead_ctx.get();
+    } else {
+      return std::vector<uint8_t>();
+    }
+  }
+  if (!EVP_AEAD_CTX_open(ctx, out.data(), &out_len, out.size(),
+                         input + kIVOffset, kIVSize, input + kMessageOffset,
+                         in.size() - kMessageOffset, nullptr, 0)) {
+    return std::vector<uint8_t>();
+  }
+  out.resize(out_len);
+  return out;
+}
+
+void SimpleTicketCrypter::Decrypt(
+    absl::string_view in,
+    std::unique_ptr<quic::ProofSource::DecryptCallback> callback) {
+  callback->Run(Decrypt(in));
+}
+
+void SimpleTicketCrypter::MaybeRotateKeys() {
+  QuicTime now = clock_->ApproximateNow();
+  if (current_key_->expiration < now) {
+    previous_key_ = std::move(current_key_);
+    current_key_ = NewKey();
+    key_epoch_++;
+  }
+}
+
+std::unique_ptr<SimpleTicketCrypter::Key> SimpleTicketCrypter::NewKey() {
+  auto key = std::make_unique<SimpleTicketCrypter::Key>();
+  RAND_bytes(key->key, kKeySize);
+  EVP_AEAD_CTX_init(key->aead_ctx.get(), EVP_aead_aes_128_gcm(), key->key,
+                    kKeySize, EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr);
+  key->expiration = clock_->ApproximateNow() + kTicketKeyLifetime;
+  return key;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/tools/simple_ticket_crypter.h b/quiche/quic/tools/simple_ticket_crypter.h
new file mode 100644
index 0000000..518ea9b
--- /dev/null
+++ b/quiche/quic/tools/simple_ticket_crypter.h
@@ -0,0 +1,56 @@
+// Copyright 2020 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.
+
+#ifndef QUICHE_QUIC_TOOLS_SIMPLE_TICKET_CRYPTER_H_
+#define QUICHE_QUIC_TOOLS_SIMPLE_TICKET_CRYPTER_H_
+
+#include "third_party/boringssl/src/include/openssl/aead.h"
+#include "quiche/quic/core/crypto/proof_source.h"
+#include "quiche/quic/core/quic_clock.h"
+#include "quiche/quic/core/quic_time.h"
+
+namespace quic {
+
+// SimpleTicketCrypter implements the QUIC ProofSource::TicketCrypter interface.
+// It generates a random key at startup and every 7 days it rotates the key,
+// keeping track of the previous key used to facilitate decrypting older
+// tickets. This implementation is not suitable for server setups where multiple
+// servers need to share keys.
+class QUIC_NO_EXPORT SimpleTicketCrypter
+    : public quic::ProofSource::TicketCrypter {
+ public:
+  explicit SimpleTicketCrypter(QuicClock* clock);
+  ~SimpleTicketCrypter() override;
+
+  size_t MaxOverhead() override;
+  std::vector<uint8_t> Encrypt(absl::string_view in,
+                               absl::string_view encryption_key) override;
+  void Decrypt(
+      absl::string_view in,
+      std::unique_ptr<quic::ProofSource::DecryptCallback> callback) override;
+
+ private:
+  std::vector<uint8_t> Decrypt(absl::string_view in);
+
+  void MaybeRotateKeys();
+
+  static constexpr size_t kKeySize = 16;
+
+  struct Key {
+    uint8_t key[kKeySize];
+    bssl::ScopedEVP_AEAD_CTX aead_ctx;
+    QuicTime expiration = QuicTime::Zero();
+  };
+
+  std::unique_ptr<Key> NewKey();
+
+  std::unique_ptr<Key> current_key_;
+  std::unique_ptr<Key> previous_key_;
+  uint8_t key_epoch_ = 0;
+  QuicClock* clock_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_SIMPLE_TICKET_CRYPTER_H_
diff --git a/quiche/quic/tools/simple_ticket_crypter_test.cc b/quiche/quic/tools/simple_ticket_crypter_test.cc
new file mode 100644
index 0000000..0399047
--- /dev/null
+++ b/quiche/quic/tools/simple_ticket_crypter_test.cc
@@ -0,0 +1,111 @@
+// Copyright 2020 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 "quiche/quic/tools/simple_ticket_crypter.h"
+
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/test_tools/mock_clock.h"
+
+namespace quic {
+namespace test {
+
+namespace {
+
+constexpr QuicTime::Delta kOneDay = QuicTime::Delta::FromSeconds(60 * 60 * 24);
+
+}  // namespace
+
+class DecryptCallback : public quic::ProofSource::DecryptCallback {
+ public:
+  explicit DecryptCallback(std::vector<uint8_t>* out) : out_(out) {}
+
+  void Run(std::vector<uint8_t> plaintext) override { *out_ = plaintext; }
+
+ private:
+  std::vector<uint8_t>* out_;
+};
+
+absl::string_view StringPiece(const std::vector<uint8_t>& in) {
+  return absl::string_view(reinterpret_cast<const char*>(in.data()), in.size());
+}
+
+class SimpleTicketCrypterTest : public QuicTest {
+ public:
+  SimpleTicketCrypterTest() : ticket_crypter_(&mock_clock_) {}
+
+ protected:
+  MockClock mock_clock_;
+  SimpleTicketCrypter ticket_crypter_;
+};
+
+TEST_F(SimpleTicketCrypterTest, EncryptDecrypt) {
+  std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
+  std::vector<uint8_t> ciphertext =
+      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+  EXPECT_NE(plaintext, ciphertext);
+
+  std::vector<uint8_t> out_plaintext;
+  ticket_crypter_.Decrypt(StringPiece(ciphertext),
+                          std::make_unique<DecryptCallback>(&out_plaintext));
+  EXPECT_EQ(out_plaintext, plaintext);
+}
+
+TEST_F(SimpleTicketCrypterTest, CiphertextsDiffer) {
+  std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
+  std::vector<uint8_t> ciphertext1 =
+      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+  std::vector<uint8_t> ciphertext2 =
+      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+  EXPECT_NE(ciphertext1, ciphertext2);
+}
+
+TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithModifiedCiphertext) {
+  std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
+  std::vector<uint8_t> ciphertext =
+      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+  EXPECT_NE(plaintext, ciphertext);
+
+  // Check that a bit flip in any byte will cause a decryption failure.
+  for (size_t i = 0; i < ciphertext.size(); i++) {
+    SCOPED_TRACE(i);
+    std::vector<uint8_t> munged_ciphertext = ciphertext;
+    munged_ciphertext[i] ^= 1;
+    std::vector<uint8_t> out_plaintext;
+    ticket_crypter_.Decrypt(StringPiece(munged_ciphertext),
+                            std::make_unique<DecryptCallback>(&out_plaintext));
+    EXPECT_TRUE(out_plaintext.empty());
+  }
+}
+
+TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithEmptyCiphertext) {
+  std::vector<uint8_t> out_plaintext;
+  ticket_crypter_.Decrypt(absl::string_view(),
+                          std::make_unique<DecryptCallback>(&out_plaintext));
+  EXPECT_TRUE(out_plaintext.empty());
+}
+
+TEST_F(SimpleTicketCrypterTest, KeyRotation) {
+  std::vector<uint8_t> plaintext = {1, 2, 3};
+  std::vector<uint8_t> ciphertext =
+      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
+  EXPECT_FALSE(ciphertext.empty());
+
+  // Advance the clock 8 days, so the key used for |ciphertext| is now the
+  // previous key. Check that decryption still works.
+  mock_clock_.AdvanceTime(kOneDay * 8);
+  std::vector<uint8_t> out_plaintext;
+  ticket_crypter_.Decrypt(StringPiece(ciphertext),
+                          std::make_unique<DecryptCallback>(&out_plaintext));
+  EXPECT_EQ(out_plaintext, plaintext);
+
+  // Advance the clock 8 more days. Now the original key should be expired and
+  // decryption should fail.
+  mock_clock_.AdvanceTime(kOneDay * 8);
+  ticket_crypter_.Decrypt(StringPiece(ciphertext),
+                          std::make_unique<DecryptCallback>(&out_plaintext));
+  EXPECT_TRUE(out_plaintext.empty());
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quiche/quic/tools/web_transport_test_visitors.h b/quiche/quic/tools/web_transport_test_visitors.h
new file mode 100644
index 0000000..07d3161
--- /dev/null
+++ b/quiche/quic/tools/web_transport_test_visitors.h
@@ -0,0 +1,263 @@
+// Copyright (c) 2021 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.
+
+#ifndef QUICHE_QUIC_TOOLS_WEB_TRANSPORT_TEST_VISITORS_H_
+#define QUICHE_QUIC_TOOLS_WEB_TRANSPORT_TEST_VISITORS_H_
+
+#include <string>
+
+#include "quiche/quic/core/web_transport_interface.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/common/platform/api/quiche_mem_slice.h"
+#include "quiche/common/quiche_circular_deque.h"
+#include "quiche/common/simple_buffer_allocator.h"
+
+namespace quic {
+
+// Discards any incoming data.
+class WebTransportDiscardVisitor : public WebTransportStreamVisitor {
+ public:
+  WebTransportDiscardVisitor(WebTransportStream* stream) : stream_(stream) {}
+
+  void OnCanRead() override {
+    std::string buffer;
+    WebTransportStream::ReadResult result = stream_->Read(&buffer);
+    QUIC_DVLOG(2) << "Read " << result.bytes_read
+                  << " bytes from WebTransport stream "
+                  << stream_->GetStreamId() << ", fin: " << result.fin;
+  }
+
+  void OnCanWrite() override {}
+
+  void OnResetStreamReceived(WebTransportStreamError /*error*/) override {}
+  void OnStopSendingReceived(WebTransportStreamError /*error*/) override {}
+  void OnWriteSideInDataRecvdState() override {}
+
+ private:
+  WebTransportStream* stream_;
+};
+
+// Echoes any incoming data back on the same stream.
+class WebTransportBidirectionalEchoVisitor : public WebTransportStreamVisitor {
+ public:
+  WebTransportBidirectionalEchoVisitor(WebTransportStream* stream)
+      : stream_(stream) {}
+
+  void OnCanRead() override {
+    WebTransportStream::ReadResult result = stream_->Read(&buffer_);
+    QUIC_DVLOG(1) << "Attempted reading on WebTransport bidirectional stream "
+                  << stream_->GetStreamId()
+                  << ", bytes read: " << result.bytes_read;
+    if (result.fin) {
+      send_fin_ = true;
+    }
+    OnCanWrite();
+  }
+
+  void OnCanWrite() override {
+    if (stop_sending_received_) {
+      return;
+    }
+
+    if (!buffer_.empty()) {
+      bool success = stream_->Write(buffer_);
+      QUIC_DVLOG(1) << "Attempted writing on WebTransport bidirectional stream "
+                    << stream_->GetStreamId()
+                    << ", success: " << (success ? "yes" : "no");
+      if (!success) {
+        return;
+      }
+
+      buffer_ = "";
+    }
+
+    if (send_fin_) {
+      bool success = stream_->SendFin();
+      QUICHE_DCHECK(success);
+    }
+  }
+
+  void OnResetStreamReceived(WebTransportStreamError /*error*/) override {
+    // Send FIN in response to a stream reset.  We want to test that we can
+    // operate one side of the stream cleanly while the other is reset, thus
+    // replying with a FIN rather than a RESET_STREAM is more appropriate here.
+    send_fin_ = true;
+    OnCanWrite();
+  }
+  void OnStopSendingReceived(WebTransportStreamError /*error*/) override {
+    stop_sending_received_ = true;
+  }
+  void OnWriteSideInDataRecvdState() override {}
+
+ protected:
+  WebTransportStream* stream() { return stream_; }
+
+ private:
+  WebTransportStream* stream_;
+  std::string buffer_;
+  bool send_fin_ = false;
+  bool stop_sending_received_ = false;
+};
+
+// Buffers all of the data and calls |callback| with the entirety of the stream
+// data.
+class WebTransportUnidirectionalEchoReadVisitor
+    : public WebTransportStreamVisitor {
+ public:
+  using Callback = std::function<void(const std::string&)>;
+
+  WebTransportUnidirectionalEchoReadVisitor(WebTransportStream* stream,
+                                            Callback callback)
+      : stream_(stream), callback_(std::move(callback)) {}
+
+  void OnCanRead() override {
+    WebTransportStream::ReadResult result = stream_->Read(&buffer_);
+    QUIC_DVLOG(1) << "Attempted reading on WebTransport unidirectional stream "
+                  << stream_->GetStreamId()
+                  << ", bytes read: " << result.bytes_read;
+    if (result.fin) {
+      QUIC_DVLOG(1) << "Finished receiving data on a WebTransport stream "
+                    << stream_->GetStreamId() << ", queueing up the echo";
+      callback_(buffer_);
+    }
+  }
+
+  void OnCanWrite() override { QUIC_NOTREACHED(); }
+
+  void OnResetStreamReceived(WebTransportStreamError /*error*/) override {}
+  void OnStopSendingReceived(WebTransportStreamError /*error*/) override {}
+  void OnWriteSideInDataRecvdState() override {}
+
+ private:
+  WebTransportStream* stream_;
+  std::string buffer_;
+  Callback callback_;
+};
+
+// Sends supplied data.
+class WebTransportUnidirectionalEchoWriteVisitor
+    : public WebTransportStreamVisitor {
+ public:
+  WebTransportUnidirectionalEchoWriteVisitor(WebTransportStream* stream,
+                                             const std::string& data)
+      : stream_(stream), data_(data) {}
+
+  void OnCanRead() override { QUIC_NOTREACHED(); }
+  void OnCanWrite() override {
+    if (data_.empty()) {
+      return;
+    }
+    if (!stream_->Write(data_)) {
+      return;
+    }
+    data_ = "";
+    bool fin_sent = stream_->SendFin();
+    QUICHE_DVLOG(1)
+        << "WebTransportUnidirectionalEchoWriteVisitor finished sending data.";
+    QUICHE_DCHECK(fin_sent);
+  }
+
+  void OnResetStreamReceived(WebTransportStreamError /*error*/) override {}
+  void OnStopSendingReceived(WebTransportStreamError /*error*/) override {}
+  void OnWriteSideInDataRecvdState() override {}
+
+ private:
+  WebTransportStream* stream_;
+  std::string data_;
+};
+
+// A session visitor which sets unidirectional or bidirectional stream visitors
+// to echo.
+class EchoWebTransportSessionVisitor : public WebTransportVisitor {
+ public:
+  EchoWebTransportSessionVisitor(WebTransportSession* session)
+      : session_(session) {}
+
+  void OnSessionReady(const spdy::SpdyHeaderBlock&) override {
+    if (session_->CanOpenNextOutgoingBidirectionalStream()) {
+      OnCanCreateNewOutgoingBidirectionalStream();
+    }
+  }
+
+  void OnSessionClosed(WebTransportSessionError /*error_code*/,
+                       const std::string& /*error_message*/) override {}
+
+  void OnIncomingBidirectionalStreamAvailable() override {
+    while (true) {
+      WebTransportStream* stream =
+          session_->AcceptIncomingBidirectionalStream();
+      if (stream == nullptr) {
+        return;
+      }
+      QUIC_DVLOG(1)
+          << "EchoWebTransportSessionVisitor received a bidirectional stream "
+          << stream->GetStreamId();
+      stream->SetVisitor(
+          std::make_unique<WebTransportBidirectionalEchoVisitor>(stream));
+      stream->visitor()->OnCanRead();
+    }
+  }
+
+  void OnIncomingUnidirectionalStreamAvailable() override {
+    while (true) {
+      WebTransportStream* stream =
+          session_->AcceptIncomingUnidirectionalStream();
+      if (stream == nullptr) {
+        return;
+      }
+      QUIC_DVLOG(1)
+          << "EchoWebTransportSessionVisitor received a unidirectional stream";
+      stream->SetVisitor(
+          std::make_unique<WebTransportUnidirectionalEchoReadVisitor>(
+              stream, [this](const std::string& data) {
+                streams_to_echo_back_.push_back(data);
+                TrySendingUnidirectionalStreams();
+              }));
+      stream->visitor()->OnCanRead();
+    }
+  }
+
+  void OnDatagramReceived(absl::string_view datagram) override {
+    quiche::QuicheMemSlice slice(
+        quiche::QuicheBuffer::Copy(&allocator_, datagram));
+    session_->SendOrQueueDatagram(std::move(slice));
+  }
+
+  void OnCanCreateNewOutgoingBidirectionalStream() override {
+    if (!echo_stream_opened_) {
+      WebTransportStream* stream = session_->OpenOutgoingBidirectionalStream();
+      stream->SetVisitor(
+          std::make_unique<WebTransportBidirectionalEchoVisitor>(stream));
+      echo_stream_opened_ = true;
+    }
+  }
+  void OnCanCreateNewOutgoingUnidirectionalStream() override {
+    TrySendingUnidirectionalStreams();
+  }
+
+  void TrySendingUnidirectionalStreams() {
+    while (!streams_to_echo_back_.empty() &&
+           session_->CanOpenNextOutgoingUnidirectionalStream()) {
+      QUIC_DVLOG(1)
+          << "EchoWebTransportServer echoed a unidirectional stream back";
+      WebTransportStream* stream = session_->OpenOutgoingUnidirectionalStream();
+      stream->SetVisitor(
+          std::make_unique<WebTransportUnidirectionalEchoWriteVisitor>(
+              stream, streams_to_echo_back_.front()));
+      streams_to_echo_back_.pop_front();
+      stream->visitor()->OnCanWrite();
+    }
+  }
+
+ private:
+  WebTransportSession* session_;
+  quiche::SimpleBufferAllocator allocator_;
+  bool echo_stream_opened_ = false;
+
+  quiche::QuicheCircularDeque<std::string> streams_to_echo_back_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_WEB_TRANSPORT_TEST_VISITORS_H_