Project import generated by Copybara.

PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/quartc/counting_packet_filter.h b/quic/quartc/counting_packet_filter.h
new file mode 100644
index 0000000..0f67008
--- /dev/null
+++ b/quic/quartc/counting_packet_filter.h
@@ -0,0 +1,42 @@
+// 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_QUARTC_COUNTING_PACKET_FILTER_H_
+#define QUICHE_QUIC_QUARTC_COUNTING_PACKET_FILTER_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/packet_filter.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/port.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h"
+
+namespace quic {
+namespace simulator {
+
+// Simple packet filter which drops the first N packets it observes.
+class CountingPacketFilter : public simulator::PacketFilter {
+ public:
+  CountingPacketFilter(simulator::Simulator* simulator,
+                       const QuicString& name,
+                       simulator::Endpoint* endpoint)
+      : PacketFilter(simulator, name, endpoint) {}
+
+  void set_packets_to_drop(int count) { packets_to_drop_ = count; }
+
+ protected:
+  bool FilterPacket(const simulator::Packet& packet) override {
+    if (packets_to_drop_ > 0) {
+      --packets_to_drop_;
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  int packets_to_drop_ = 0;
+};
+
+}  // namespace simulator
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_COUNTING_PACKET_FILTER_H_
diff --git a/quic/quartc/quartc_connection_helper.cc b/quic/quartc/quartc_connection_helper.cc
new file mode 100644
index 0000000..6c6ee97
--- /dev/null
+++ b/quic/quartc/quartc_connection_helper.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_connection_helper.h"
+
+namespace quic {
+
+QuartcConnectionHelper::QuartcConnectionHelper(const QuicClock* clock)
+    : clock_(clock) {}
+
+const QuicClock* QuartcConnectionHelper::GetClock() const {
+  return clock_;
+}
+
+QuicRandom* QuartcConnectionHelper::GetRandomGenerator() {
+  return QuicRandom::GetInstance();
+}
+
+QuicBufferAllocator* QuartcConnectionHelper::GetStreamSendBufferAllocator() {
+  return &buffer_allocator_;
+}
+
+}  // namespace quic
diff --git a/quic/quartc/quartc_connection_helper.h b/quic/quartc/quartc_connection_helper.h
new file mode 100644
index 0000000..2b1d62f
--- /dev/null
+++ b/quic/quartc/quartc_connection_helper.h
@@ -0,0 +1,33 @@
+// Copyright (c) 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_QUARTC_QUARTC_CONNECTION_HELPER_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_CONNECTION_HELPER_H_
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/quic_buffer_allocator.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h"
+
+namespace quic {
+
+// Simple implementation of QuicConnectionHelperInterface for Quartc.
+class QuartcConnectionHelper : public QuicConnectionHelperInterface {
+ public:
+  explicit QuartcConnectionHelper(const QuicClock* clock);
+
+  // QuicConnectionHelperInterface overrides.
+  const QuicClock* GetClock() const override;
+  QuicRandom* GetRandomGenerator() override;
+  QuicBufferAllocator* GetStreamSendBufferAllocator() override;
+
+ private:
+  const QuicClock* clock_;
+  SimpleBufferAllocator buffer_allocator_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_CONNECTION_HELPER_H_
diff --git a/quic/quartc/quartc_crypto_helpers.cc b/quic/quartc/quartc_crypto_helpers.cc
new file mode 100644
index 0000000..865fec4
--- /dev/null
+++ b/quic/quartc/quartc_crypto_helpers.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h"
+
+namespace quic {
+
+void DummyProofSource::GetProof(const QuicSocketAddress& server_address,
+                                const QuicString& hostname,
+                                const QuicString& server_config,
+                                QuicTransportVersion transport_version,
+                                QuicStringPiece chlo_hash,
+                                std::unique_ptr<Callback> callback) {
+  QuicReferenceCountedPointer<ProofSource::Chain> chain =
+      GetCertChain(server_address, hostname);
+  QuicCryptoProof proof;
+  proof.signature = "Dummy signature";
+  proof.leaf_cert_scts = "Dummy timestamp";
+  callback->Run(true, chain, proof, nullptr /* details */);
+}
+
+QuicReferenceCountedPointer<DummyProofSource::Chain>
+DummyProofSource::GetCertChain(const QuicSocketAddress& server_address,
+                               const QuicString& hostname) {
+  std::vector<QuicString> certs;
+  certs.push_back(kDummyCertName);
+  return QuicReferenceCountedPointer<ProofSource::Chain>(
+      new ProofSource::Chain(certs));
+}
+
+void DummyProofSource::ComputeTlsSignature(
+    const QuicSocketAddress& server_address,
+    const QuicString& hostname,
+    uint16_t signature_algorithm,
+    QuicStringPiece in,
+    std::unique_ptr<SignatureCallback> callback) {
+  callback->Run(true, "Dummy signature");
+}
+
+QuicAsyncStatus InsecureProofVerifier::VerifyProof(
+    const QuicString& hostname,
+    const uint16_t port,
+    const QuicString& server_config,
+    QuicTransportVersion transport_version,
+    QuicStringPiece chlo_hash,
+    const std::vector<QuicString>& certs,
+    const QuicString& cert_sct,
+    const QuicString& signature,
+    const ProofVerifyContext* context,
+    QuicString* error_details,
+    std::unique_ptr<ProofVerifyDetails>* verify_details,
+    std::unique_ptr<ProofVerifierCallback> callback) {
+  return QUIC_SUCCESS;
+}
+
+QuicAsyncStatus InsecureProofVerifier::VerifyCertChain(
+    const QuicString& hostname,
+    const std::vector<QuicString>& certs,
+    const ProofVerifyContext* context,
+    QuicString* error_details,
+    std::unique_ptr<ProofVerifyDetails>* details,
+    std::unique_ptr<ProofVerifierCallback> callback) {
+  return QUIC_SUCCESS;
+}
+
+std::unique_ptr<ProofVerifyContext>
+InsecureProofVerifier::CreateDefaultContext() {
+  return nullptr;
+}
+
+QuicConnectionId QuartcCryptoServerStreamHelper::GenerateConnectionIdForReject(
+    QuicTransportVersion version,
+    QuicConnectionId connection_id) const {
+  // TODO(b/124399417):  Request a zero-length connection id here when the QUIC
+  // server perspective supports it.  Right now, the stateless rejector requires
+  // a connection id that is not the same as the client-chosen connection id.
+  return QuicUtils::CreateRandomConnectionId();
+}
+
+bool QuartcCryptoServerStreamHelper::CanAcceptClientHello(
+    const CryptoHandshakeMessage& message,
+    const QuicSocketAddress& client_address,
+    const QuicSocketAddress& peer_address,
+    const QuicSocketAddress& self_address,
+    QuicString* error_details) const {
+  return true;
+}
+
+std::unique_ptr<QuicCryptoClientConfig> CreateCryptoClientConfig(
+    QuicStringPiece pre_shared_key) {
+  auto config = QuicMakeUnique<QuicCryptoClientConfig>(
+      QuicMakeUnique<InsecureProofVerifier>(),
+      TlsClientHandshaker::CreateSslCtx());
+  config->set_pad_inchoate_hello(false);
+  config->set_pad_full_hello(false);
+  if (!pre_shared_key.empty()) {
+    config->set_pre_shared_key(pre_shared_key);
+  }
+  return config;
+}
+
+CryptoServerConfig CreateCryptoServerConfig(QuicRandom* random,
+                                            const QuicClock* clock,
+                                            QuicStringPiece pre_shared_key) {
+  CryptoServerConfig crypto_server_config;
+
+  // Generate a random source address token secret. For long-running servers
+  // it's better to not regenerate it for each connection to enable zero-RTT
+  // handshakes, but for transient clients it does not matter.
+  char source_address_token_secret[kInputKeyingMaterialLength];
+  random->RandBytes(source_address_token_secret, kInputKeyingMaterialLength);
+  auto config = QuicMakeUnique<QuicCryptoServerConfig>(
+      QuicString(source_address_token_secret, kInputKeyingMaterialLength),
+      random, QuicMakeUnique<DummyProofSource>(), KeyExchangeSource::Default(),
+      TlsServerHandshaker::CreateSslCtx());
+
+  // We run QUIC over ICE, and ICE is verifying remote side with STUN pings.
+  // We disable source address token validation in order to allow for 0-rtt
+  // setup (plus source ip addresses are changing even during the connection
+  // when ICE is used).
+  config->set_validate_source_address_token(false);
+
+  // Effectively disables the anti-amplification measures (we don't need
+  // them because we use ICE, and we need to disable them because we disable
+  // padding of crypto packets).
+  // This multiplier must be large enough so that the crypto handshake packet
+  // (approx. 300 bytes) multiplied by this multiplier is larger than a fully
+  // sized packet (currently 1200 bytes).
+  // 1500 is a bit extreme: if you can imagine sending a 1 byte packet, and
+  // your largest MTU would be below 1500 bytes, 1500*1 >=
+  // any_packet_that_you_can_imagine_sending.
+  // (again, we hardcode packet size to 1200, so we are not dealing with jumbo
+  // frames).
+  config->set_chlo_multiplier(1500);
+
+  // We are sending small client hello, we must not validate its size.
+  config->set_validate_chlo_size(false);
+
+  // Provide server with serialized config string to prove ownership.
+  QuicCryptoServerConfig::ConfigOptions options;
+  // The |message| is used to handle the return value of AddDefaultConfig
+  // which is raw pointer of the CryptoHandshakeMessage.
+  std::unique_ptr<CryptoHandshakeMessage> message(
+      config->AddDefaultConfig(random, clock, options));
+  config->set_pad_rej(false);
+  config->set_pad_shlo(false);
+  if (!pre_shared_key.empty()) {
+    config->set_pre_shared_key(pre_shared_key);
+  }
+  crypto_server_config.config = std::move(config);
+  const QuicData& data = message->GetSerialized();
+
+  crypto_server_config.serialized_crypto_config =
+      QuicString(data.data(), data.length());
+  return crypto_server_config;
+}
+
+}  // namespace quic
diff --git a/quic/quartc/quartc_crypto_helpers.h b/quic/quartc/quartc_crypto_helpers.h
new file mode 100644
index 0000000..2b280b5
--- /dev/null
+++ b/quic/quartc/quartc_crypto_helpers.h
@@ -0,0 +1,121 @@
+// Copyright (c) 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_QUARTC_QUARTC_CRYPTO_HELPERS_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_CRYPTO_HELPERS_H_
+
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h"
+#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h"
+#include "net/third_party/quiche/src/quic/core/crypto/proof_verifier.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_reference_counted.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// Never, ever, change this certificate name. You will break 0-rtt handshake if
+// you do.
+static constexpr char kDummyCertName[] = "Dummy cert";
+
+struct CryptoServerConfig {
+  std::unique_ptr<QuicCryptoServerConfig> config;
+  QuicString serialized_crypto_config;
+};
+
+// Length of HKDF input keying material, equal to its number of bytes.
+// https://tools.ietf.org/html/rfc5869#section-2.2.
+// TODO(zhihuang): Verify that input keying material length is correct.
+constexpr size_t kInputKeyingMaterialLength = 32;
+
+// Used by QuicCryptoServerConfig to provide dummy proof credentials.
+// TODO(zhihuang): Remove when secure P2P QUIC handshake is possible.
+class DummyProofSource : public ProofSource {
+ public:
+  DummyProofSource() {}
+  ~DummyProofSource() override {}
+
+  // ProofSource overrides.
+  void GetProof(const QuicSocketAddress& server_address,
+                const QuicString& hostname,
+                const QuicString& server_config,
+                QuicTransportVersion transport_version,
+                QuicStringPiece chlo_hash,
+                std::unique_ptr<Callback> callback) override;
+
+  QuicReferenceCountedPointer<Chain> GetCertChain(
+      const QuicSocketAddress& server_address,
+      const QuicString& hostname) override;
+
+  void ComputeTlsSignature(
+      const QuicSocketAddress& server_address,
+      const QuicString& hostname,
+      uint16_t signature_algorithm,
+      QuicStringPiece in,
+      std::unique_ptr<SignatureCallback> callback) override;
+};
+
+// Used by QuicCryptoClientConfig to ignore the peer's credentials
+// and establish an insecure QUIC connection.
+// TODO(zhihuang): Remove when secure P2P QUIC handshake is possible.
+class InsecureProofVerifier : public ProofVerifier {
+ public:
+  InsecureProofVerifier() {}
+  ~InsecureProofVerifier() override {}
+
+  // ProofVerifier overrides.
+  QuicAsyncStatus VerifyProof(
+      const QuicString& hostname,
+      const uint16_t port,
+      const QuicString& server_config,
+      QuicTransportVersion transport_version,
+      QuicStringPiece chlo_hash,
+      const std::vector<QuicString>& certs,
+      const QuicString& cert_sct,
+      const QuicString& signature,
+      const ProofVerifyContext* context,
+      QuicString* error_details,
+      std::unique_ptr<ProofVerifyDetails>* verify_details,
+      std::unique_ptr<ProofVerifierCallback> callback) override;
+
+  QuicAsyncStatus VerifyCertChain(
+      const QuicString& hostname,
+      const std::vector<QuicString>& certs,
+      const ProofVerifyContext* context,
+      QuicString* error_details,
+      std::unique_ptr<ProofVerifyDetails>* details,
+      std::unique_ptr<ProofVerifierCallback> callback) override;
+
+  std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override;
+};
+
+// Implementation of the server-side crypto stream helper.
+class QuartcCryptoServerStreamHelper : public QuicCryptoServerStream::Helper {
+ public:
+  QuicConnectionId GenerateConnectionIdForReject(
+      QuicTransportVersion version,
+      QuicConnectionId connection_id) const override;
+
+  bool CanAcceptClientHello(const CryptoHandshakeMessage& message,
+                            const QuicSocketAddress& client_address,
+                            const QuicSocketAddress& peer_address,
+                            const QuicSocketAddress& self_address,
+                            QuicString* error_details) const override;
+};
+
+std::unique_ptr<QuicCryptoClientConfig> CreateCryptoClientConfig(
+    QuicStringPiece pre_shared_key);
+
+CryptoServerConfig CreateCryptoServerConfig(QuicRandom* random,
+                                            const QuicClock* clock,
+                                            QuicStringPiece pre_shared_key);
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_CRYPTO_HELPERS_H_
diff --git a/quic/quartc/quartc_dispatcher.cc b/quic/quartc/quartc_dispatcher.cc
new file mode 100644
index 0000000..8070203
--- /dev/null
+++ b/quic/quartc/quartc_dispatcher.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_dispatcher.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_factory.h"
+
+namespace quic {
+
+QuartcDispatcher::QuartcDispatcher(
+    std::unique_ptr<QuicConfig> config,
+    std::unique_ptr<QuicCryptoServerConfig> crypto_config,
+    QuicStringPiece crypto_config_serialized,
+    QuicVersionManager* version_manager,
+    std::unique_ptr<QuicConnectionHelperInterface> helper,
+    std::unique_ptr<QuicCryptoServerStream::Helper> session_helper,
+    std::unique_ptr<QuicAlarmFactory> alarm_factory,
+    std::unique_ptr<QuartcPacketWriter> packet_writer,
+    Delegate* delegate)
+    : QuicDispatcher(config.get(),
+                     crypto_config.get(),
+                     version_manager,
+                     std::move(helper),
+                     std::move(session_helper),
+                     std::move(alarm_factory),
+                     kQuicDefaultConnectionIdLength),
+      owned_quic_config_(std::move(config)),
+      owned_crypto_config_(std::move(crypto_config)),
+      crypto_config_(crypto_config_serialized),
+      delegate_(delegate),
+      packet_writer_(packet_writer.get()) {
+  // QuicDispatcher takes ownership of the writer.
+  QuicDispatcher::InitializeWithWriter(packet_writer.release());
+  // NB: This must happen *after* InitializeWithWriter.  It can call us back
+  // with OnTransportCanWrite() immediately, and the dispatcher needs to be
+  // fully initialized to handle that.
+  packet_writer_->SetPacketTransportDelegate(this);
+}
+
+QuartcDispatcher::~QuartcDispatcher() {
+  packet_writer_->SetPacketTransportDelegate(nullptr);
+}
+
+QuartcSession* QuartcDispatcher::CreateQuicSession(
+    QuicConnectionId connection_id,
+    const QuicSocketAddress& client_address,
+    QuicStringPiece alpn,
+    const ParsedQuicVersion& version) {
+  std::unique_ptr<QuicConnection> connection = CreateQuicConnection(
+      connection_id, client_address, helper(), alarm_factory(), writer(),
+      Perspective::IS_SERVER, ParsedQuicVersionVector{version});
+  QuartcSession* session = new QuartcServerSession(
+      std::move(connection), /*visitor=*/this, config(), GetSupportedVersions(),
+      helper()->GetClock(), crypto_config(), compressed_certs_cache(),
+      session_helper());
+  delegate_->OnSessionCreated(session);
+  return session;
+}
+
+void QuartcDispatcher::OnTransportCanWrite() {
+  OnCanWrite();
+}
+
+void QuartcDispatcher::OnTransportReceived(const char* data, size_t data_len) {
+  // QuartcPacketTransport does not surface real peer addresses, so the
+  // dispatcher uses a dummy address when processing incoming packets. Note that
+  // the dispatcher refuses to process anything with port 0.
+  static const QuicSocketAddress* dummy_address =
+      new QuicSocketAddress(QuicIpAddress::Any4(), /*port=*/1);
+
+  QuicReceivedPacket packet(data, data_len, helper()->GetClock()->Now());
+  ProcessPacket(/*self_address=*/*dummy_address,
+                /*peer_address=*/*dummy_address, packet);
+}
+
+}  // namespace quic
diff --git a/quic/quartc/quartc_dispatcher.h b/quic/quartc/quartc_dispatcher.h
new file mode 100644
index 0000000..e660107
--- /dev/null
+++ b/quic/quartc/quartc_dispatcher.h
@@ -0,0 +1,73 @@
+// Copyright (c) 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_QUARTC_QUARTC_DISPATCHER_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_DISPATCHER_H_
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_config.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_dispatcher.h"
+#include "net/third_party/quiche/src/quic/core/quic_version_manager.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_session.h"
+
+namespace quic {
+
+class QuartcDispatcher : public QuicDispatcher,
+                         QuartcPacketTransport::Delegate {
+ public:
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+    virtual void OnSessionCreated(QuartcSession* session) = 0;
+  };
+
+  QuartcDispatcher(
+      std::unique_ptr<QuicConfig> config,
+      std::unique_ptr<QuicCryptoServerConfig> crypto_config,
+      QuicStringPiece crypto_config_serialized,
+      QuicVersionManager* version_manager,
+      std::unique_ptr<QuicConnectionHelperInterface> helper,
+      std::unique_ptr<QuicCryptoServerStream::Helper> session_helper,
+      std::unique_ptr<QuicAlarmFactory> alarm_factory,
+      std::unique_ptr<QuartcPacketWriter> packet_writer,
+      Delegate* delegate);
+  ~QuartcDispatcher() override;
+
+  QuartcSession* CreateQuicSession(QuicConnectionId connection_id,
+                                   const QuicSocketAddress& client_address,
+                                   QuicStringPiece alpn,
+                                   const ParsedQuicVersion& version) override;
+
+  // QuartcPacketTransport::Delegate overrides.
+  void OnTransportCanWrite() override;
+  void OnTransportReceived(const char* data, size_t data_len) override;
+
+  // A serialized server config in quic wire format.
+  QuicStringPiece server_crypto_config() const { return crypto_config_; }
+
+ private:
+  // Members owned by QuartcDispatcher but not QuicDispatcher.
+  std::unique_ptr<QuicConfig> owned_quic_config_;
+  std::unique_ptr<QuicCryptoServerConfig> owned_crypto_config_;
+  QuicString crypto_config_;
+
+  // Delegate invoked when the dispatcher creates a new session.
+  Delegate* delegate_;
+
+  // The packet writer used by this dispatcher.  Owned by the base class, but
+  // the base class upcasts it to QuicPacketWriter (which prevents detaching the
+  // transport delegate without a downcast).
+  QuartcPacketWriter* packet_writer_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_DISPATCHER_H_
diff --git a/quic/quartc/quartc_endpoint.cc b/quic/quartc/quartc_endpoint.cc
new file mode 100644
index 0000000..2882705
--- /dev/null
+++ b/quic/quartc/quartc_endpoint.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_endpoint.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_version_manager.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_connection_helper.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_dispatcher.h"
+
+namespace quic {
+
+namespace {
+
+// Wrapper around a QuicAlarmFactory which delegates to the wrapped factory.
+// Usee to convert an unowned pointer into an owned pointer, so that the new
+// "owner" does not delete the underlying factory.  Note that this is only valid
+// when the unowned pointer is already guaranteed to outlive the new "owner".
+class QuartcAlarmFactoryWrapper : public QuicAlarmFactory {
+ public:
+  explicit QuartcAlarmFactoryWrapper(QuicAlarmFactory* impl) : impl_(impl) {}
+
+  QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) override;
+  QuicArenaScopedPtr<QuicAlarm> CreateAlarm(
+      QuicArenaScopedPtr<QuicAlarm::Delegate> delegate,
+      QuicConnectionArena* arena) override;
+
+ private:
+  QuicAlarmFactory* impl_;
+};
+
+QuicAlarm* QuartcAlarmFactoryWrapper::CreateAlarm(
+    QuicAlarm::Delegate* delegate) {
+  return impl_->CreateAlarm(delegate);
+}
+
+QuicArenaScopedPtr<QuicAlarm> QuartcAlarmFactoryWrapper::CreateAlarm(
+    QuicArenaScopedPtr<QuicAlarm::Delegate> delegate,
+    QuicConnectionArena* arena) {
+  return impl_->CreateAlarm(std::move(delegate), arena);
+}
+
+QuartcFactoryConfig CreateFactoryConfig(QuicAlarmFactory* alarm_factory,
+                                        const QuicClock* clock) {
+  QuartcFactoryConfig config;
+  config.alarm_factory = alarm_factory;
+  config.clock = clock;
+  return config;
+}
+
+}  // namespace
+
+QuartcClientEndpoint::QuartcClientEndpoint(
+    QuicAlarmFactory* alarm_factory,
+    const QuicClock* clock,
+    QuartcEndpoint::Delegate* delegate,
+    const QuartcSessionConfig& config,
+    QuicStringPiece serialized_server_config,
+    std::unique_ptr<QuicVersionManager> version_manager)
+    : alarm_factory_(alarm_factory),
+      clock_(clock),
+      delegate_(delegate),
+      serialized_server_config_(serialized_server_config),
+      version_manager_(version_manager ? std::move(version_manager)
+                                       : QuicMakeUnique<QuicVersionManager>(
+                                             AllSupportedVersions())),
+      create_session_alarm_(QuicWrapUnique(
+          alarm_factory_->CreateAlarm(new CreateSessionDelegate(this)))),
+      factory_(QuicMakeUnique<QuartcFactory>(
+          CreateFactoryConfig(alarm_factory, clock))),
+      config_(config) {}
+
+void QuartcClientEndpoint::Connect(QuartcPacketTransport* packet_transport) {
+  packet_transport_ = packet_transport;
+  create_session_alarm_->Set(clock_->Now());
+}
+
+void QuartcClientEndpoint::OnCreateSessionAlarm() {
+  session_ = factory_->CreateQuartcClientSession(
+      config_, version_manager_->GetSupportedVersions(),
+      serialized_server_config_, packet_transport_);
+  delegate_->OnSessionCreated(session_.get());
+}
+
+QuartcServerEndpoint::QuartcServerEndpoint(
+    QuicAlarmFactory* alarm_factory,
+    const QuicClock* clock,
+    QuartcEndpoint::Delegate* delegate,
+    const QuartcSessionConfig& config,
+    std::unique_ptr<QuicVersionManager> version_manager)
+    : alarm_factory_(alarm_factory),
+      delegate_(delegate),
+      config_(config),
+      version_manager_(version_manager ? std::move(version_manager)
+                                       : QuicMakeUnique<QuicVersionManager>(
+                                             AllSupportedVersions())),
+      pre_connection_helper_(QuicMakeUnique<QuartcConnectionHelper>(clock)),
+      crypto_config_(
+          CreateCryptoServerConfig(pre_connection_helper_->GetRandomGenerator(),
+                                   clock,
+                                   config.pre_shared_key)) {}
+
+void QuartcServerEndpoint::Connect(QuartcPacketTransport* packet_transport) {
+  DCHECK(pre_connection_helper_ != nullptr);
+  dispatcher_ = QuicMakeUnique<QuartcDispatcher>(
+      QuicMakeUnique<QuicConfig>(CreateQuicConfig(config_)),
+      std::move(crypto_config_.config), crypto_config_.serialized_crypto_config,
+      version_manager_.get(), std::move(pre_connection_helper_),
+      QuicMakeUnique<QuartcCryptoServerStreamHelper>(),
+      QuicMakeUnique<QuartcAlarmFactoryWrapper>(alarm_factory_),
+      QuicMakeUnique<QuartcPacketWriter>(packet_transport,
+                                         config_.max_packet_size),
+      this);
+  // The dispatcher requires at least one call to |ProcessBufferedChlos| to
+  // set the number of connections it is allowed to create.
+  dispatcher_->ProcessBufferedChlos(/*max_connections_to_create=*/1);
+}
+
+void QuartcServerEndpoint::OnSessionCreated(QuartcSession* session) {
+  delegate_->OnSessionCreated(session);
+}
+
+}  // namespace quic
diff --git a/quic/quartc/quartc_endpoint.h b/quic/quartc/quartc_endpoint.h
new file mode 100644
index 0000000..ee856ea
--- /dev/null
+++ b/quic/quartc/quartc_endpoint.h
@@ -0,0 +1,183 @@
+// Copyright (c) 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_QUARTC_QUARTC_ENDPOINT_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_ENDPOINT_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_connection_helper.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_dispatcher.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_factory.h"
+
+namespace quic {
+
+// Private implementation of QuartcEndpoint.  Enables different implementations
+// for client and server endpoints.
+class QuartcEndpointImpl {
+ public:
+  virtual ~QuartcEndpointImpl() = default;
+
+  virtual QuicStringPiece server_crypto_config() const = 0;
+};
+
+// Endpoint (client or server) in a peer-to-peer Quartc connection.
+class QuartcEndpoint {
+ public:
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // Called when an endpoint creates a new session, before any packets are
+    // processed or sent.  The callee should perform any additional
+    // configuration required, such as setting a session delegate, before
+    // returning.  |session| is owned by the endpoint, but remains safe to use
+    // until another call to |OnSessionCreated| occurs, at which point previous
+    // session is destroyed.
+    virtual void OnSessionCreated(QuartcSession* session) = 0;
+
+    // Called if the endpoint fails to establish a session after a call to
+    // Connect.  (The most likely cause is a network idle timeout.)
+    virtual void OnConnectError(QuicErrorCode error,
+                                const QuicString& error_details) = 0;
+  };
+
+  virtual ~QuartcEndpoint() = default;
+
+  // Connects the endpoint using the given session config.  After |Connect| is
+  // called, the endpoint will asynchronously create a session, then call
+  // |Delegate::OnSessionCreated|.
+  virtual void Connect(QuartcPacketTransport* packet_transport) = 0;
+};
+
+// Implementation of QuartcEndpoint which immediately (but asynchronously)
+// creates a session by scheduling a QuicAlarm.  Only suitable for use with the
+// client perspective.
+class QuartcClientEndpoint : public QuartcEndpoint {
+ public:
+  // |alarm_factory|, |clock|, and |delegate| are owned by the caller and must
+  // outlive the endpoint.
+  QuartcClientEndpoint(
+      QuicAlarmFactory* alarm_factory,
+      const QuicClock* clock,
+      Delegate* delegate,
+      const QuartcSessionConfig& config,
+      QuicStringPiece serialized_server_config,
+      std::unique_ptr<QuicVersionManager> version_manager = nullptr);
+
+  void Connect(QuartcPacketTransport* packet_transport) override;
+
+ private:
+  friend class CreateSessionDelegate;
+  class CreateSessionDelegate : public QuicAlarm::Delegate {
+   public:
+    CreateSessionDelegate(QuartcClientEndpoint* endpoint)
+        : endpoint_(endpoint) {}
+
+    void OnAlarm() override { endpoint_->OnCreateSessionAlarm(); }
+
+   private:
+    QuartcClientEndpoint* endpoint_;
+  };
+
+  // Callback which occurs when |create_session_alarm_| fires.
+  void OnCreateSessionAlarm();
+
+  // Implementation of QuicAlarmFactory used by this endpoint.  Unowned.
+  QuicAlarmFactory* alarm_factory_;
+
+  // Implementation of QuicClock used by this endpoint.  Unowned.
+  const QuicClock* clock_;
+
+  // Delegate which receives callbacks for newly created sessions.
+  QuartcEndpoint::Delegate* delegate_;
+
+  // Server config.  If valid, used to perform a 0-RTT connection.
+  const QuicString serialized_server_config_;
+
+  // Version manager.  May be injected to control version negotiation in tests.
+  std::unique_ptr<QuicVersionManager> version_manager_;
+
+  // Alarm for creating sessions asynchronously.  The alarm is set when
+  // Connect() is called.  When it fires, the endpoint creates a session and
+  // calls the delegate.
+  std::unique_ptr<QuicAlarm> create_session_alarm_;
+
+  // QuartcFactory used by this endpoint to create sessions.  This is an
+  // implementation detail of the QuartcEndpoint, and will eventually be
+  // replaced by a dispatcher (for servers) or version-negotiation agent (for
+  // clients).
+  std::unique_ptr<QuartcFactory> factory_;
+
+  // Config to be used for new sessions.
+  QuartcSessionConfig config_;
+
+  // The currently-active session.  Nullptr until |Connect| and
+  // |Delegate::OnSessionCreated| are called.
+  std::unique_ptr<QuartcSession> session_;
+
+  QuartcPacketTransport* packet_transport_;
+};
+
+// Implementation of QuartcEndpoint which uses a QuartcDispatcher to listen for
+// an incoming CHLO and create a session when one arrives.  Only suitable for
+// use with the server perspective.
+class QuartcServerEndpoint : public QuartcEndpoint,
+                             public QuartcDispatcher::Delegate {
+ public:
+  QuartcServerEndpoint(
+      QuicAlarmFactory* alarm_factory,
+      const QuicClock* clock,
+      QuartcEndpoint::Delegate* delegate,
+      const QuartcSessionConfig& config,
+      std::unique_ptr<QuicVersionManager> version_manager = nullptr);
+
+  // Implements QuartcEndpoint.
+  void Connect(QuartcPacketTransport* packet_transport) override;
+
+  // Implements QuartcDispatcher::Delegate.
+  void OnSessionCreated(QuartcSession* session) override;
+
+  // Accessor to retrieve the server crypto config.  May only be called after
+  // Connect().
+  QuicStringPiece server_crypto_config() const {
+    return crypto_config_.serialized_crypto_config;
+  }
+
+  const std::vector<ParsedQuicVersion> GetSupportedQuicVersions() const {
+    return version_manager_->GetSupportedVersions();
+  }
+
+ private:
+  // Implementation of QuicAlarmFactory used by this endpoint.  Unowned.
+  QuicAlarmFactory* alarm_factory_;
+
+  // Delegate which receives callbacks for newly created sessions.
+  QuartcEndpoint::Delegate* delegate_;
+
+  // Config to be used for new sessions.
+  QuartcSessionConfig config_;
+
+  // Version manager.  May be injected to control version negotiation in tests.
+  std::unique_ptr<QuicVersionManager> version_manager_;
+
+  // QuartcDispatcher waits for an incoming CHLO, then either rejects it or
+  // creates a session to respond to it.  The dispatcher owns all sessions it
+  // creates.
+  std::unique_ptr<QuartcDispatcher> dispatcher_;
+
+  // This field is only available before connection was started.
+  std::unique_ptr<QuartcConnectionHelper> pre_connection_helper_;
+
+  // A configuration, containing public key, that may need to be passed to the
+  // client to enable 0rtt.
+  CryptoServerConfig crypto_config_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_ENDPOINT_H_
diff --git a/quic/quartc/quartc_endpoint_test.cc b/quic/quartc/quartc_endpoint_test.cc
new file mode 100644
index 0000000..3d7d85e
--- /dev/null
+++ b/quic/quartc/quartc_endpoint_test.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_endpoint.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_fakes.h"
+#include "net/third_party/quiche/src/quic/quartc/simulated_packet_transport.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/link.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h"
+
+namespace quic {
+namespace {
+
+class QuartcEndpointTest : public QuicTest {
+ protected:
+  QuartcEndpointTest()
+      : client_transport_(&simulator_,
+                          "client_transport",
+                          "server_transport",
+                          10 * kDefaultMaxPacketSize),
+        server_transport_(&simulator_,
+                          "server_transport",
+                          "client_transport",
+                          10 * kDefaultMaxPacketSize),
+        client_server_link_(&client_transport_,
+                            &server_transport_,
+                            QuicBandwidth::FromKBitsPerSecond(10000),
+                            QuicTime::Delta::FromMilliseconds(1)),
+        server_session_delegate_(&server_stream_delegate_,
+                                 simulator_.GetClock()),
+        server_endpoint_delegate_(&server_session_delegate_),
+        server_endpoint_(
+            QuicMakeUnique<QuartcServerEndpoint>(simulator_.GetAlarmFactory(),
+                                                 simulator_.GetClock(),
+                                                 &server_endpoint_delegate_,
+                                                 QuartcSessionConfig())),
+        client_session_delegate_(&client_stream_delegate_,
+                                 simulator_.GetClock()),
+        client_endpoint_delegate_(&client_session_delegate_),
+        client_endpoint_(QuicMakeUnique<QuartcClientEndpoint>(
+            simulator_.GetAlarmFactory(),
+            simulator_.GetClock(),
+            &client_endpoint_delegate_,
+            QuartcSessionConfig(),
+            /*serialized_server_config=*/"")) {}
+
+  simulator::Simulator simulator_;
+
+  simulator::SimulatedQuartcPacketTransport client_transport_;
+  simulator::SimulatedQuartcPacketTransport server_transport_;
+  simulator::SymmetricLink client_server_link_;
+
+  FakeQuartcStreamDelegate server_stream_delegate_;
+  FakeQuartcSessionDelegate server_session_delegate_;
+  FakeQuartcEndpointDelegate server_endpoint_delegate_;
+
+  std::unique_ptr<QuartcServerEndpoint> server_endpoint_;
+
+  FakeQuartcStreamDelegate client_stream_delegate_;
+  FakeQuartcSessionDelegate client_session_delegate_;
+  FakeQuartcEndpointDelegate client_endpoint_delegate_;
+
+  std::unique_ptr<QuartcClientEndpoint> client_endpoint_;
+};
+
+// After calling Connect, the client endpoint must wait for an async callback.
+// The callback occurs after a finite amount of time and produces a session.
+TEST_F(QuartcEndpointTest, ClientCreatesSessionAsynchronously) {
+  client_endpoint_->Connect(&client_transport_);
+
+  EXPECT_EQ(client_endpoint_delegate_.session(), nullptr);
+
+  EXPECT_TRUE(simulator_.RunUntil(
+      [this] { return client_endpoint_delegate_.session() != nullptr; }));
+}
+
+// Tests that the server can negotiate for an older QUIC version if the client
+// attempts to connect using a newer version.
+TEST_F(QuartcEndpointTest,
+       QUIC_TEST_DISABLED_IN_CHROME(ServerNegotiatesForOldVersion)) {
+  // Note: for this test, we need support for two versions.  Which two shouldn't
+  // matter, but they must be enabled so that the version manager doesn't filter
+  // them out.
+  SetQuicReloadableFlag(quic_enable_version_46, true);
+  SetQuicReloadableFlag(quic_enable_version_43, true);
+
+  // Reset the client endpoint to prefer version 46 but also be capable of
+  // speaking version 43.
+  ParsedQuicVersionVector client_versions;
+  client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46});
+  client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43});
+  client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>(
+      simulator_.GetAlarmFactory(), simulator_.GetClock(),
+      &client_endpoint_delegate_, QuartcSessionConfig(),
+      /*serialized_server_config=*/"",
+      QuicMakeUnique<QuicVersionManager>(client_versions));
+
+  // Reset the server endpoint to only speak version 43.
+  ParsedQuicVersionVector server_versions;
+  server_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43});
+  server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>(
+      simulator_.GetAlarmFactory(), simulator_.GetClock(),
+      &server_endpoint_delegate_, QuartcSessionConfig(),
+      QuicMakeUnique<QuicVersionManager>(server_versions));
+
+  // The endpoints should be able to establish a connection using version 46.
+  server_endpoint_->Connect(&server_transport_);
+  client_endpoint_->Connect(&client_transport_);
+
+  ASSERT_TRUE(simulator_.RunUntil([this] {
+    return client_endpoint_delegate_.session() != nullptr &&
+           client_endpoint_delegate_.session()->IsEncryptionEstablished() &&
+           server_endpoint_delegate_.session() != nullptr &&
+           server_endpoint_delegate_.session()->IsEncryptionEstablished();
+  }));
+  EXPECT_EQ(client_endpoint_delegate_.session()->connection()->version(),
+            server_versions[0]);
+  EXPECT_EQ(server_endpoint_delegate_.session()->connection()->version(),
+            server_versions[0]);
+}
+
+// Tests that the server can accept connections from clients that use older
+// QUIC versions.
+TEST_F(QuartcEndpointTest,
+       QUIC_TEST_DISABLED_IN_CHROME(ServerAcceptsOldVersion)) {
+  // Note: for this test, we need support for two versions.  Which two shouldn't
+  // matter, but they must be enabled so that the version manager doesn't filter
+  // them out.
+  SetQuicReloadableFlag(quic_enable_version_46, true);
+  SetQuicReloadableFlag(quic_enable_version_43, true);
+
+  // Reset the client endpoint to only speak version 43.
+  ParsedQuicVersionVector client_versions;
+  client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43});
+  client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>(
+      simulator_.GetAlarmFactory(), simulator_.GetClock(),
+      &client_endpoint_delegate_, QuartcSessionConfig(),
+      /*serialized_server_config=*/"",
+      QuicMakeUnique<QuicVersionManager>(client_versions));
+
+  // Reset the server endpoint to prefer version 46 but also be capable of
+  // speaking version 43.
+  ParsedQuicVersionVector server_versions;
+  server_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46});
+  server_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43});
+  server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>(
+      simulator_.GetAlarmFactory(), simulator_.GetClock(),
+      &server_endpoint_delegate_, QuartcSessionConfig(),
+      QuicMakeUnique<QuicVersionManager>(server_versions));
+
+  // The endpoints should be able to establish a connection using version 46.
+  server_endpoint_->Connect(&server_transport_);
+  client_endpoint_->Connect(&client_transport_);
+
+  ASSERT_TRUE(simulator_.RunUntil([this] {
+    return client_endpoint_delegate_.session() != nullptr &&
+           client_endpoint_delegate_.session()->IsEncryptionEstablished() &&
+           server_endpoint_delegate_.session() != nullptr &&
+           server_endpoint_delegate_.session()->IsEncryptionEstablished();
+  }));
+  EXPECT_EQ(client_endpoint_delegate_.session()->connection()->version(),
+            client_versions[0]);
+  EXPECT_EQ(server_endpoint_delegate_.session()->connection()->version(),
+            client_versions[0]);
+}
+
+// Tests that version negotiation fails when the client and server support
+// completely disjoint sets of versions.
+TEST_F(QuartcEndpointTest,
+       QUIC_TEST_DISABLED_IN_CHROME(VersionNegotiationWithDisjointVersions)) {
+  // Note: for this test, we need support for two versions.  Which two shouldn't
+  // matter, but they must be enabled so that the version manager doesn't filter
+  // them out.
+  SetQuicReloadableFlag(quic_enable_version_46, true);
+  SetQuicReloadableFlag(quic_enable_version_43, true);
+
+  // Reset the client endpoint to only speak version 43.
+  ParsedQuicVersionVector client_versions;
+  client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43});
+  client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>(
+      simulator_.GetAlarmFactory(), simulator_.GetClock(),
+      &client_endpoint_delegate_, QuartcSessionConfig(),
+      /*serialized_server_config=*/"",
+      QuicMakeUnique<QuicVersionManager>(client_versions));
+
+  // Reset the server endpoint to only speak version 46.
+  ParsedQuicVersionVector server_versions;
+  server_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46});
+  server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>(
+      simulator_.GetAlarmFactory(), simulator_.GetClock(),
+      &server_endpoint_delegate_, QuartcSessionConfig(),
+      QuicMakeUnique<QuicVersionManager>(server_versions));
+
+  // The endpoints should be unable to establish a connection.
+  server_endpoint_->Connect(&server_transport_);
+  client_endpoint_->Connect(&client_transport_);
+
+  // Note that the error is reported from the client and *not* the server.  The
+  // server sees an invalid version, sends a version negotiation packet, and
+  // never gets a response, because the client stops sending when it can't find
+  // a mutually supported versions.
+  ASSERT_TRUE(simulator_.RunUntil([this] {
+    return client_endpoint_delegate_.session() != nullptr &&
+           client_endpoint_delegate_.session()->error() != QUIC_NO_ERROR;
+  }));
+  EXPECT_EQ(client_endpoint_delegate_.session()->error(), QUIC_INVALID_VERSION);
+}
+
+}  // namespace
+}  // namespace quic
diff --git a/quic/quartc/quartc_factory.cc b/quic/quartc/quartc_factory.cc
new file mode 100644
index 0000000..803122a
--- /dev/null
+++ b/quic/quartc/quartc_factory.cc
@@ -0,0 +1,255 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_factory.h"
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_connection_helper.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_session.h"
+
+namespace quic {
+
+QuartcFactory::QuartcFactory(const QuartcFactoryConfig& factory_config)
+    : alarm_factory_(factory_config.alarm_factory),
+      clock_(factory_config.clock),
+      connection_helper_(QuicMakeUnique<QuartcConnectionHelper>(clock_)),
+      compressed_certs_cache_(QuicMakeUnique<QuicCompressedCertsCache>(
+          QuicCompressedCertsCache::kQuicCompressedCertsCacheSize)),
+      stream_helper_(QuicMakeUnique<QuartcCryptoServerStreamHelper>()) {}
+
+std::unique_ptr<QuartcSession> QuartcFactory::CreateQuartcClientSession(
+    const QuartcSessionConfig& quartc_session_config,
+    const ParsedQuicVersionVector& supported_versions,
+    QuicStringPiece server_crypto_config,
+    QuartcPacketTransport* packet_transport) {
+  DCHECK(packet_transport);
+
+  // QuartcSession will eventually own both |writer| and |quic_connection|.
+  auto writer = QuicMakeUnique<QuartcPacketWriter>(
+      packet_transport, quartc_session_config.max_packet_size);
+
+  // While the QuicConfig is not directly used by the connection, creating it
+  // also sets flag values which must be set before creating the connection.
+  QuicConfig quic_config = CreateQuicConfig(quartc_session_config);
+  std::unique_ptr<QuicConnection> quic_connection = CreateQuicConnection(
+      Perspective::IS_CLIENT, supported_versions, writer.get());
+
+  return QuicMakeUnique<QuartcClientSession>(
+      std::move(quic_connection), quic_config, supported_versions, clock_,
+      std::move(writer),
+      CreateCryptoClientConfig(quartc_session_config.pre_shared_key),
+      server_crypto_config);
+}
+
+void ConfigureGlobalQuicSettings() {
+  // Fixes behavior of StopReading() with level-triggered stream sequencers.
+  SetQuicReloadableFlag(quic_stop_reading_when_level_triggered, true);
+
+  // Fix b/110259444.
+  SetQuicReloadableFlag(quic_fix_spurious_ack_alarm, true);
+
+  // Enable version 46+ to enable SendMessage API and 'quic bit' per draft 17.
+  SetQuicReloadableFlag(quic_enable_version_46, true);
+
+  // Fix for inconsistent reporting of crypto handshake.
+  SetQuicReloadableFlag(quic_fix_has_pending_crypto_data, true);
+
+  // Ensure that we don't drop data because QUIC streams refuse to buffer it.
+  // TODO(b/120099046):  Replace this with correct handling of WriteMemSlices().
+  SetQuicFlag(&FLAGS_quic_buffered_data_threshold,
+              std::numeric_limits<int>::max());
+
+  // TODO(b/117157454): Perform version negotiation for Quartc outside of
+  // QuicSession/QuicConnection. Currently default of
+  // quic_restart_flag_quic_no_server_conn_ver_negotiation2 is false,
+  // but we fail blueprint test that sets all QUIC flags to true.
+  //
+  // Forcing flag to false to pass blueprint tests, but eventually we'll have
+  // to implement negotiation outside of QuicConnection.
+  SetQuicRestartFlag(quic_no_server_conn_ver_negotiation2, false);
+  SetQuicReloadableFlag(quic_no_client_conn_ver_negotiation, false);
+
+  // Enable and request QUIC to include receive timestamps in ACK frames.
+  SetQuicReloadableFlag(quic_send_timestamps, true);
+
+  // Enable ACK_DECIMATION_WITH_REORDERING. It requires ack_decimation to be
+  // false.
+  SetQuicReloadableFlag(quic_enable_ack_decimation, false);
+
+  // Note: flag settings have no effect for Exoblaze builds since
+  // SetQuicReloadableFlag() gets stubbed out.
+  SetQuicReloadableFlag(quic_bbr_less_probe_rtt, true);   // Enable BBR6,7,8.
+  SetQuicReloadableFlag(quic_unified_iw_options, true);   // Enable IWXX opts.
+  SetQuicReloadableFlag(quic_bbr_slower_startup3, true);  // Enable BBQX opts.
+  SetQuicReloadableFlag(quic_bbr_flexible_app_limited, true);  // Enable BBR9.
+}
+
+QuicConfig CreateQuicConfig(const QuartcSessionConfig& quartc_session_config) {
+  // TODO(b/124398962): Figure out a better way to initialize QUIC flags.
+  // Creating a config shouldn't have global side-effects on flags.  However,
+  // this has the advantage of ensuring that flag values stay in sync with the
+  // options requested by configs, so simply splitting the config and flag
+  // settings doesn't seem preferable.
+  ConfigureGlobalQuicSettings();
+
+  // In exoblaze this may return false. DCHECK to avoid problems caused by
+  // incorrect flags configuration.
+  DCHECK(GetQuicReloadableFlag(quic_enable_version_46))
+      << "Your build does not support quic reloadable flags and shouldn't "
+         "place Quartc calls";
+
+  QuicTagVector copt;
+  copt.push_back(kNSTP);
+
+  // Enable and request QUIC to include receive timestamps in ACK frames.
+  copt.push_back(kSTMP);
+
+  // Enable ACK_DECIMATION_WITH_REORDERING. It requires ack_decimation to be
+  // false.
+  copt.push_back(kAKD2);
+
+  // Use unlimited decimation in order to reduce number of unbundled ACKs.
+  copt.push_back(kAKDU);
+
+  // Enable time-based loss detection.
+  copt.push_back(kTIME);
+
+  copt.push_back(kBBR3);  // Stay in low-gain until in-flight < BDP.
+  copt.push_back(kBBR5);  // 40 RTT ack aggregation.
+  copt.push_back(kBBR6);  // Use a 0.75 * BDP cwnd during PROBE_RTT.
+  copt.push_back(kBBR8);  // Skip PROBE_RTT if app-limited.
+  copt.push_back(kBBR9);  // Ignore app-limited if enough data is in flight.
+  copt.push_back(kBBQ1);  // 2.773 pacing gain in STARTUP.
+  copt.push_back(kBBQ2);  // 2.0 CWND gain in STARTUP.
+  copt.push_back(kBBQ4);  // 0.75 pacing gain in DRAIN.
+  copt.push_back(k1RTT);  // Exit STARTUP after 1 RTT with no gains.
+  copt.push_back(kIW10);  // 10-packet (14600 byte) initial cwnd.
+
+  if (!quartc_session_config.enable_tail_loss_probe) {
+    copt.push_back(kNTLP);
+  }
+
+  // TODO(b/112192153):  Test and possible enable slower startup when pipe
+  // filling is ready to use.  Slower startup is kBBRS.
+
+  QuicConfig quic_config;
+
+  // Use the limits for the session & stream flow control. The default 16KB
+  // limit leads to significantly undersending (not reaching BWE on the outgoing
+  // bitrate) due to blocked frames, and it leads to high latency (and one-way
+  // delay). Setting it to its limits is not going to cause issues (our streams
+  // are small generally, and if we were to buffer 24MB it wouldn't be the end
+  // of the world). We can consider setting different limits in future (e.g. 1MB
+  // stream, 1.5MB session). It's worth noting that on 1mbps bitrate, limit of
+  // 24MB can capture approx 4 minutes of the call, and the default increase in
+  // size of the window (half of the window size) is approximately 2 minutes of
+  // the call.
+  quic_config.SetInitialSessionFlowControlWindowToSend(
+      kSessionReceiveWindowLimit);
+  quic_config.SetInitialStreamFlowControlWindowToSend(
+      kStreamReceiveWindowLimit);
+  quic_config.SetConnectionOptionsToSend(copt);
+  quic_config.SetClientConnectionOptions(copt);
+  if (quartc_session_config.max_time_before_crypto_handshake >
+      QuicTime::Delta::Zero()) {
+    quic_config.set_max_time_before_crypto_handshake(
+        quartc_session_config.max_time_before_crypto_handshake);
+  }
+  if (quartc_session_config.max_idle_time_before_crypto_handshake >
+      QuicTime::Delta::Zero()) {
+    quic_config.set_max_idle_time_before_crypto_handshake(
+        quartc_session_config.max_idle_time_before_crypto_handshake);
+  }
+  if (quartc_session_config.idle_network_timeout > QuicTime::Delta::Zero()) {
+    quic_config.SetIdleNetworkTimeout(
+        quartc_session_config.idle_network_timeout,
+        quartc_session_config.idle_network_timeout);
+  }
+
+  // The ICE transport provides a unique 5-tuple for each connection. Save
+  // overhead by omitting the connection id.
+  quic_config.SetBytesForConnectionIdToSend(0);
+
+  // Allow up to 1000 incoming streams at once. Quartc streams typically contain
+  // one audio or video frame and close immediately. However, when a video frame
+  // becomes larger than one packet, there is some delay between the start and
+  // end of each stream. The default maximum of 100 only leaves about 1 second
+  // of headroom (Quartc sends ~30 video frames per second) before QUIC starts
+  // to refuse incoming streams. Back-pressure should clear backlogs of
+  // incomplete streams, but targets 1 second for recovery. Increasing the
+  // number of open streams gives sufficient headroom to recover before QUIC
+  // refuses new streams.
+  quic_config.SetMaxIncomingDynamicStreamsToSend(1000);
+
+  return quic_config;
+}
+
+std::unique_ptr<QuicConnection> QuartcFactory::CreateQuicConnection(
+    Perspective perspective,
+    const ParsedQuicVersionVector& supported_versions,
+    QuartcPacketWriter* packet_writer) {
+  // |dummy_id| and |dummy_address| are used because Quartc network layer will
+  // not use these two.
+    char connection_id_bytes[sizeof(uint64_t)] = {};
+    QuicConnectionId dummy_id = QuicConnectionId(
+        static_cast<char*>(connection_id_bytes), sizeof(connection_id_bytes));
+    QuicSocketAddress dummy_address(QuicIpAddress::Any4(), /*port=*/0);
+    return quic::CreateQuicConnection(
+        dummy_id, dummy_address, connection_helper_.get(), alarm_factory_,
+        packet_writer, perspective, supported_versions);
+}
+
+std::unique_ptr<QuicConnection> CreateQuicConnection(
+    QuicConnectionId connection_id,
+    const QuicSocketAddress& peer_address,
+    QuicConnectionHelperInterface* connection_helper,
+    QuicAlarmFactory* alarm_factory,
+    QuicPacketWriter* packet_writer,
+    Perspective perspective,
+    ParsedQuicVersionVector supported_versions) {
+  auto quic_connection = QuicMakeUnique<QuicConnection>(
+      connection_id, peer_address, connection_helper, alarm_factory,
+      packet_writer,
+      /*owns_writer=*/false, perspective, supported_versions);
+
+  QuicSentPacketManager& sent_packet_manager =
+      quic_connection->sent_packet_manager();
+
+  // Default delayed ack time is 25ms.
+  // If data packets are sent less often (e.g. because p-time was modified),
+  // we would force acks to be sent every 25ms regardless, increasing
+  // overhead. Since generally we guarantee a packet every 20ms, changing
+  // this value should have miniscule effect on quality on good connections,
+  // but on poor connections, changing this number significantly reduced the
+  // number of ack-only packets.
+  // The p-time can go up to as high as 120ms, and when it does, it's
+  // when the low overhead is the most important thing. Ideally it should be
+  // above 120ms, but it cannot be higher than 0.5*RTO, which equals to 100ms.
+  sent_packet_manager.set_delayed_ack_time(
+      QuicTime::Delta::FromMilliseconds(100));
+
+  quic_connection->set_fill_up_link_during_probing(true);
+
+  // We start ack decimation after 15 packets. Typically, we would see
+  // 1-2 crypto handshake packets, one media packet, and 10 probing packets.
+  // We want to get acks for the probing packets as soon as possible,
+  // but we can start using ack decimation right after first probing completes.
+  // The default was to not start ack decimation for the first 100 packets.
+  quic_connection->set_min_received_before_ack_decimation(15);
+
+  return quic_connection;
+}
+
+std::unique_ptr<QuartcFactory> CreateQuartcFactory(
+    const QuartcFactoryConfig& factory_config) {
+  return std::unique_ptr<QuartcFactory>(new QuartcFactory(factory_config));
+}
+
+}  // namespace quic
diff --git a/quic/quartc/quartc_factory.h b/quic/quartc/quartc_factory.h
new file mode 100644
index 0000000..5496387
--- /dev/null
+++ b/quic/quartc/quartc_factory.h
@@ -0,0 +1,110 @@
+// Copyright (c) 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_QUARTC_QUARTC_FACTORY_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_FACTORY_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_session.h"
+
+namespace quic {
+
+// The configuration for creating a QuartcFactory.
+struct QuartcFactoryConfig {
+  // Factory for |QuicAlarm|s. Implemented by the Quartc user with different
+  // mechanisms. For example in WebRTC, it is implemented with rtc::Thread.
+  // Owned by the user, and needs to stay alive for as long as the QuartcFactory
+  // exists.
+  QuicAlarmFactory* alarm_factory = nullptr;
+  // The clock used by |QuicAlarm|s. Implemented by the Quartc user. Owned by
+  // the user, and needs to stay alive for as long as the QuartcFactory exists.
+  const QuicClock* clock = nullptr;
+};
+
+struct QuartcSessionConfig {
+  // If a pre-shared cryptographic key is available for this session, specify it
+  // here.  This value will only be used if non-empty.
+  QuicString pre_shared_key;
+
+  // The maximum size of the packet can be written with the packet writer.
+  // 1200 bytes by default.
+  QuicPacketLength max_packet_size = 1200;
+
+  // Timeouts for the crypto handshake. Set them to higher values to
+  // prevent closing the session before it started on a slow network.
+  // Zero entries are ignored and QUIC defaults are used in that case.
+  QuicTime::Delta max_idle_time_before_crypto_handshake =
+      QuicTime::Delta::Zero();
+  QuicTime::Delta max_time_before_crypto_handshake = QuicTime::Delta::Zero();
+  QuicTime::Delta idle_network_timeout = QuicTime::Delta::Zero();
+
+  // Tail loss probes (TLP) are enabled by default, but it may be useful to
+  // disable them in tests. We can also consider disabling them in production
+  // if we discover that tail loss probes add overhead in low bitrate audio.
+  bool enable_tail_loss_probe = true;
+};
+
+// Factory that creates instances of QuartcSession.  Implements the
+// QuicConnectionHelperInterface used by the QuicConnections. Only one
+// QuartcFactory is expected to be created.
+class QuartcFactory {
+ public:
+  explicit QuartcFactory(const QuartcFactoryConfig& factory_config);
+
+  // Creates a new QuartcSession using the given configuration.
+  std::unique_ptr<QuartcSession> CreateQuartcClientSession(
+      const QuartcSessionConfig& quartc_session_config,
+      const ParsedQuicVersionVector& supported_versions,
+      QuicStringPiece server_crypto_config,
+      QuartcPacketTransport* packet_transport);
+
+ private:
+  std::unique_ptr<QuicConnection> CreateQuicConnection(
+      Perspective perspective,
+      const ParsedQuicVersionVector& supported_versions,
+      QuartcPacketWriter* packet_writer);
+
+  // Used to implement QuicAlarmFactory.  Owned by the user and must outlive
+  // QuartcFactory.
+  QuicAlarmFactory* alarm_factory_;
+  // Used to implement the QuicConnectionHelperInterface.  Owned by the user and
+  // must outlive QuartcFactory.
+  const QuicClock* clock_;
+
+  // Helper used by all QuicConnections.
+  std::unique_ptr<QuicConnectionHelperInterface> connection_helper_;
+
+  // Used by QuicCryptoServerStream to track most recently compressed certs.
+  std::unique_ptr<QuicCompressedCertsCache> compressed_certs_cache_;
+
+  // This helper is needed to create QuicCryptoServerStreams.
+  std::unique_ptr<QuicCryptoServerStream::Helper> stream_helper_;
+};
+
+// Configures global settings, such as supported quic versions.
+// Must execute on QUIC thread.
+void ConfigureGlobalQuicSettings();
+
+// Must execute on QUIC thread.
+QuicConfig CreateQuicConfig(const QuartcSessionConfig& quartc_session_config);
+
+std::unique_ptr<QuicConnection> CreateQuicConnection(
+    QuicConnectionId connection_id,
+    const QuicSocketAddress& peer_address,
+    QuicConnectionHelperInterface* connection_helper,
+    QuicAlarmFactory* alarm_factory,
+    QuicPacketWriter* packet_writer,
+    Perspective perspective,
+    ParsedQuicVersionVector supported_versions);
+
+// Creates a new instance of QuartcFactory.
+std::unique_ptr<QuartcFactory> CreateQuartcFactory(
+    const QuartcFactoryConfig& factory_config);
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_FACTORY_H_
diff --git a/quic/quartc/quartc_fakes.h b/quic/quartc/quartc_fakes.h
new file mode 100644
index 0000000..16ff9c7
--- /dev/null
+++ b/quic/quartc/quartc_fakes.h
@@ -0,0 +1,138 @@
+// Copyright (c) 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_QUARTC_QUARTC_FAKES_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_FAKES_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_endpoint.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_session.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_stream.h"
+
+namespace quic {
+
+class FakeQuartcEndpointDelegate : public QuartcEndpoint::Delegate {
+ public:
+  explicit FakeQuartcEndpointDelegate(QuartcSession::Delegate* session_delegate)
+      : session_delegate_(session_delegate) {}
+
+  void OnSessionCreated(QuartcSession* session) override {
+    CHECK_EQ(session_, nullptr);
+    CHECK_NE(session, nullptr);
+    session_ = session;
+    session_->SetDelegate(session_delegate_);
+    session_->StartCryptoHandshake();
+  }
+
+  void OnConnectError(QuicErrorCode error,
+                      const QuicString& error_details) override {
+    LOG(FATAL) << "Unexpected error during QuartcEndpoint::Connect(); error="
+               << error << ", error_details=" << error_details;
+  }
+
+  QuartcSession* session() { return session_; }
+
+ private:
+  QuartcSession::Delegate* session_delegate_;
+  QuartcSession* session_ = nullptr;
+};
+
+class FakeQuartcSessionDelegate : public QuartcSession::Delegate {
+ public:
+  explicit FakeQuartcSessionDelegate(QuartcStream::Delegate* stream_delegate,
+                                     const QuicClock* clock)
+      : stream_delegate_(stream_delegate), clock_(clock) {}
+
+  void OnConnectionWritable() override {
+    LOG(INFO) << "Connection writable!";
+    if (!writable_time_.IsInitialized()) {
+      writable_time_ = clock_->Now();
+    }
+  }
+
+  // Called when peers have established forward-secure encryption
+  void OnCryptoHandshakeComplete() override {
+    LOG(INFO) << "Crypto handshake complete!";
+    crypto_handshake_time_ = clock_->Now();
+  }
+
+  // Called when connection closes locally, or remotely by peer.
+  void OnConnectionClosed(QuicErrorCode error_code,
+                          const QuicString& error_details,
+                          ConnectionCloseSource source) override {
+    connected_ = false;
+  }
+
+  // Called when an incoming QUIC stream is created.
+  void OnIncomingStream(QuartcStream* quartc_stream) override {
+    last_incoming_stream_ = quartc_stream;
+    last_incoming_stream_->SetDelegate(stream_delegate_);
+  }
+
+  void OnMessageReceived(QuicStringPiece message) override {
+    incoming_messages_.emplace_back(message);
+  }
+
+  void OnCongestionControlChange(QuicBandwidth bandwidth_estimate,
+                                 QuicBandwidth pacing_rate,
+                                 QuicTime::Delta latest_rtt) override {}
+
+  QuartcStream* last_incoming_stream() { return last_incoming_stream_; }
+
+  // Returns all received messages.
+  const std::vector<QuicString>& incoming_messages() {
+    return incoming_messages_;
+  }
+
+  bool connected() { return connected_; }
+  QuicTime writable_time() const { return writable_time_; }
+  QuicTime crypto_handshake_time() const { return crypto_handshake_time_; }
+
+ private:
+  QuartcStream* last_incoming_stream_;
+  std::vector<QuicString> incoming_messages_;
+  bool connected_ = true;
+  QuartcStream::Delegate* stream_delegate_;
+  QuicTime writable_time_ = QuicTime::Zero();
+  QuicTime crypto_handshake_time_ = QuicTime::Zero();
+  const QuicClock* clock_;
+};
+
+class FakeQuartcStreamDelegate : public QuartcStream::Delegate {
+ public:
+  size_t OnReceived(QuartcStream* stream,
+                    iovec* iov,
+                    size_t iov_length,
+                    bool fin) override {
+    size_t bytes_consumed = 0;
+    for (size_t i = 0; i < iov_length; ++i) {
+      received_data_[stream->id()] +=
+          QuicString(static_cast<const char*>(iov[i].iov_base), iov[i].iov_len);
+      bytes_consumed += iov[i].iov_len;
+    }
+    return bytes_consumed;
+  }
+
+  void OnClose(QuartcStream* stream) override {
+    errors_[stream->id()] = stream->stream_error();
+  }
+
+  void OnBufferChanged(QuartcStream* stream) override {}
+
+  bool has_data() { return !received_data_.empty(); }
+  std::map<QuicStreamId, QuicString> data() { return received_data_; }
+
+  QuicRstStreamErrorCode stream_error(QuicStreamId id) { return errors_[id]; }
+
+ private:
+  std::map<QuicStreamId, QuicString> received_data_;
+  std::map<QuicStreamId, QuicRstStreamErrorCode> errors_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_FAKES_H_
diff --git a/quic/quartc/quartc_interval_counter.h b/quic/quartc/quartc_interval_counter.h
new file mode 100644
index 0000000..fe3b083
--- /dev/null
+++ b/quic/quartc/quartc_interval_counter.h
@@ -0,0 +1,122 @@
+// 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_QUARTC_QUARTC_INTERVAL_COUNTER_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_INTERVAL_COUNTER_H_
+
+#include <stddef.h>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/quic_interval.h"
+#include "net/third_party/quiche/src/quic/core/quic_interval_set.h"
+
+namespace quic {
+
+// QuartcIntervalCounter counts the number of times each value appears within
+// a set of potentially overlapping intervals.
+//
+// QuartcIntervalCounter is not intended for widespread use.  Consider replacing
+// it with a full interval-map if more use cases arise.
+//
+// QuartcIntervalCounter is only suitable for cases where the maximum count is
+// expected to remain low.  (For example, counting the number of times the same
+// portions of stream data are lost.)  It is inefficient when the maximum count
+// becomes high.
+template <typename T>
+class QuartcIntervalCounter {
+ public:
+  // Adds |interval| to the counter.  The count associated with each value in
+  // |interval| is incremented by one.  |interval| may overlap with previous
+  // intervals added to the counter.
+  //
+  // For each possible value:
+  //  - If the value is present in both |interval| and the counter, the count
+  //  associated with that value is incremented by one.
+  //  - If the value is present in |interval| but not counter, the count
+  //  associated with that value is set to one (incremented from zero).
+  //  - If the value is absent from |interval|, the count is unchanged.
+  //
+  // Time complexity is O(|MaxCount| * the complexity of adding an interval to a
+  // QuicIntervalSet).
+  void AddInterval(QuicInterval<T> interval);
+
+  // Removes an interval from the counter.  This method may be called to prune
+  // irrelevant intervals from the counter.  This is useful to prevent unbounded
+  // growth.
+  //
+  // Time complexity is O(|MaxCount| * the complexity of removing an interval
+  // from a QuicIntervalSet).
+  void RemoveInterval(QuicInterval<T> interval);
+
+  // Returns the maximum number of times any single value has appeared in
+  // intervals added to the counter.
+  //
+  // Time complexity is constant.
+  size_t MaxCount() const { return intervals_by_count_.size(); }
+
+  // Returns the maximum number of times a particular value has appeared in
+  // intervals added to the counter.
+  //
+  // Time complexity is O(|MaxCount| * log(number of non-contiguous intervals)).
+  size_t Count(const T& value) const;
+
+ private:
+  // Each entry in this vector represents the intervals of values counted at
+  // least i + 1 times, where i is the index of the entry.
+  //
+  // Whenever an interval is added to the counter, each value in the interval is
+  // added to the first entry which does not already contain that value.  If
+  // part of an interval is already present in the last entry, a new entry is
+  // added containing that part.
+  //
+  // Note that this means each value present in one of the interval sets will be
+  // present in all previous sets.
+  std::vector<QuicIntervalSet<T>> intervals_by_count_;
+};
+
+template <typename T>
+void QuartcIntervalCounter<T>::AddInterval(QuicInterval<T> interval) {
+  // After the Nth iteration, |leftover| contains the parts of |interval| that
+  // are already present in the first N entries.  These parts of |interval| have
+  // been added to the counter more than N times.
+  QuicIntervalSet<T> leftover(interval);
+  for (auto& intervals : intervals_by_count_) {
+    QuicIntervalSet<T> tmp = leftover;
+    leftover.Intersection(intervals);
+    intervals.Union(tmp);
+  }
+
+  // Whatever ranges are still in |leftover| are already in all the entries
+  // Add a new entry containing |leftover|.
+  if (!leftover.Empty()) {
+    intervals_by_count_.push_back(leftover);
+  }
+}
+
+template <typename T>
+void QuartcIntervalCounter<T>::RemoveInterval(QuicInterval<T> interval) {
+  // Remove the interval from each entry in the vector, popping any entries that
+  // become empty.
+  for (size_t i = intervals_by_count_.size(); i > 0; --i) {
+    intervals_by_count_[i - 1].Difference(interval);
+    if (intervals_by_count_[i - 1].Empty()) {
+      intervals_by_count_.pop_back();
+    }
+  }
+}
+
+template <typename T>
+size_t QuartcIntervalCounter<T>::Count(const T& value) const {
+  // The index of the last entry containing |value| gives its count.
+  for (size_t i = intervals_by_count_.size(); i > 0; --i) {
+    if (intervals_by_count_[i - 1].Contains(value)) {
+      return i;
+    }
+  }
+  return 0;
+}
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_INTERVAL_COUNTER_H_
diff --git a/quic/quartc/quartc_interval_counter_test.cc b/quic/quartc/quartc_interval_counter_test.cc
new file mode 100644
index 0000000..9bc9af5
--- /dev/null
+++ b/quic/quartc/quartc_interval_counter_test.cc
@@ -0,0 +1,110 @@
+// 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 "net/third_party/quiche/src/quic/quartc/quartc_interval_counter.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/quic_interval.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+
+namespace quic {
+namespace {
+
+class QuartcIntervalCounterTest : public QuicTest {
+ protected:
+  QuartcIntervalCounter<int> counter_;
+};
+
+void ExpectCount(const QuartcIntervalCounter<int>& counter,
+                 QuicInterval<int> interval,
+                 size_t count) {
+  for (int i = interval.min(); i < interval.max(); ++i) {
+    EXPECT_EQ(counter.Count(i), count) << "i=" << i;
+  }
+}
+
+TEST_F(QuartcIntervalCounterTest, InitiallyEmpty) {
+  EXPECT_EQ(counter_.MaxCount(), 0u);
+}
+
+TEST_F(QuartcIntervalCounterTest, SameInterval) {
+  counter_.AddInterval(QuicInterval<int>(0, 6));
+  EXPECT_EQ(counter_.MaxCount(), 1u);
+  ExpectCount(counter_, QuicInterval<int>(0, 6), 1);
+
+  counter_.AddInterval(QuicInterval<int>(0, 6));
+  EXPECT_EQ(counter_.MaxCount(), 2u);
+  ExpectCount(counter_, QuicInterval<int>(0, 6), 2);
+}
+
+TEST_F(QuartcIntervalCounterTest, DisjointIntervals) {
+  counter_.AddInterval(QuicInterval<int>(0, 5));
+  EXPECT_EQ(counter_.MaxCount(), 1u);
+  ExpectCount(counter_, QuicInterval<int>(0, 5), 1);
+  ExpectCount(counter_, QuicInterval<int>(5, 10), 0);
+
+  counter_.AddInterval(QuicInterval<int>(5, 10));
+  EXPECT_EQ(counter_.MaxCount(), 1u);
+  ExpectCount(counter_, QuicInterval<int>(0, 5), 1);
+  ExpectCount(counter_, QuicInterval<int>(5, 10), 1);
+}
+
+TEST_F(QuartcIntervalCounterTest, OverlappingIntervals) {
+  counter_.AddInterval(QuicInterval<int>(0, 6));
+  EXPECT_EQ(counter_.MaxCount(), 1u);
+  ExpectCount(counter_, QuicInterval<int>(0, 6), 1);
+  ExpectCount(counter_, QuicInterval<int>(6, 10), 0);
+
+  counter_.AddInterval(QuicInterval<int>(5, 10));
+  EXPECT_EQ(counter_.MaxCount(), 2u);
+  ExpectCount(counter_, QuicInterval<int>(0, 5), 1);
+  EXPECT_EQ(counter_.Count(5), 2u);
+  ExpectCount(counter_, QuicInterval<int>(6, 10), 1);
+}
+
+TEST_F(QuartcIntervalCounterTest, IntervalsWithGapThenOverlap) {
+  counter_.AddInterval(QuicInterval<int>(0, 4));
+  EXPECT_EQ(counter_.MaxCount(), 1u);
+  ExpectCount(counter_, QuicInterval<int>(0, 4), 1);
+  ExpectCount(counter_, QuicInterval<int>(4, 10), 0);
+
+  counter_.AddInterval(QuicInterval<int>(7, 10));
+  EXPECT_EQ(counter_.MaxCount(), 1u);
+  ExpectCount(counter_, QuicInterval<int>(0, 4), 1);
+  ExpectCount(counter_, QuicInterval<int>(4, 7), 0);
+  ExpectCount(counter_, QuicInterval<int>(7, 10), 1);
+
+  counter_.AddInterval(QuicInterval<int>(3, 8));
+  EXPECT_EQ(counter_.MaxCount(), 2u);
+  ExpectCount(counter_, QuicInterval<int>(0, 3), 1);
+  EXPECT_EQ(counter_.Count(3), 2u);
+  ExpectCount(counter_, QuicInterval<int>(4, 7), 1);
+  EXPECT_EQ(counter_.Count(7), 2u);
+  ExpectCount(counter_, QuicInterval<int>(8, 10), 1);
+}
+
+TEST_F(QuartcIntervalCounterTest, RemoveIntervals) {
+  counter_.AddInterval(QuicInterval<int>(0, 5));
+  EXPECT_EQ(counter_.MaxCount(), 1u);
+  ExpectCount(counter_, QuicInterval<int>(0, 5), 1);
+
+  counter_.AddInterval(QuicInterval<int>(4, 10));
+  EXPECT_EQ(counter_.MaxCount(), 2u);
+  ExpectCount(counter_, QuicInterval<int>(0, 4), 1);
+  EXPECT_EQ(counter_.Count(4), 2u);
+  ExpectCount(counter_, QuicInterval<int>(5, 10), 1);
+
+  counter_.RemoveInterval(QuicInterval<int>(0, 5));
+  EXPECT_EQ(counter_.MaxCount(), 1u);
+  ExpectCount(counter_, QuicInterval<int>(0, 5), 0);
+  ExpectCount(counter_, QuicInterval<int>(5, 10), 1);
+
+  counter_.RemoveInterval(QuicInterval<int>(5, 10));
+  EXPECT_EQ(counter_.MaxCount(), 0u);
+  ExpectCount(counter_, QuicInterval<int>(0, 10), 0);
+}
+
+}  // namespace
+}  // namespace quic
diff --git a/quic/quartc/quartc_packet_writer.cc b/quic/quartc/quartc_packet_writer.cc
new file mode 100644
index 0000000..64a72a8
--- /dev/null
+++ b/quic/quartc/quartc_packet_writer.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h"
+
+namespace quic {
+
+std::unique_ptr<PerPacketOptions> QuartcPerPacketOptions::Clone() const {
+  return QuicMakeUnique<QuartcPerPacketOptions>(*this);
+}
+
+QuartcPacketWriter::QuartcPacketWriter(QuartcPacketTransport* packet_transport,
+                                       QuicByteCount max_packet_size)
+    : packet_transport_(packet_transport), max_packet_size_(max_packet_size) {}
+
+WriteResult QuartcPacketWriter::WritePacket(
+    const char* buffer,
+    size_t buf_len,
+    const QuicIpAddress& self_address,
+    const QuicSocketAddress& peer_address,
+    PerPacketOptions* options) {
+  DCHECK(packet_transport_);
+
+  QuartcPacketTransport::PacketInfo info;
+  QuartcPerPacketOptions* quartc_options =
+      static_cast<QuartcPerPacketOptions*>(options);
+  if (quartc_options && quartc_options->connection) {
+    info.packet_number =
+        quartc_options->connection->packet_generator().packet_number();
+  }
+  int bytes_written = packet_transport_->Write(buffer, buf_len, info);
+  if (bytes_written <= 0) {
+    writable_ = false;
+    return WriteResult(WRITE_STATUS_BLOCKED, EWOULDBLOCK);
+  }
+  return WriteResult(WRITE_STATUS_OK, bytes_written);
+}
+
+bool QuartcPacketWriter::IsWriteBlocked() const {
+  return !writable_;
+}
+
+QuicByteCount QuartcPacketWriter::GetMaxPacketSize(
+    const QuicSocketAddress& peer_address) const {
+  return max_packet_size_;
+}
+
+void QuartcPacketWriter::SetWritable() {
+  writable_ = true;
+}
+
+bool QuartcPacketWriter::SupportsReleaseTime() const {
+  return false;
+}
+
+bool QuartcPacketWriter::IsBatchMode() const {
+  return false;
+}
+
+char* QuartcPacketWriter::GetNextWriteLocation(
+    const QuicIpAddress& self_address,
+    const QuicSocketAddress& peer_address) {
+  return nullptr;
+}
+
+WriteResult QuartcPacketWriter::Flush() {
+  return WriteResult(WRITE_STATUS_OK, 0);
+}
+
+void QuartcPacketWriter::SetPacketTransportDelegate(
+    QuartcPacketTransport::Delegate* delegate) {
+  packet_transport_->SetDelegate(delegate);
+}
+
+}  // namespace quic
diff --git a/quic/quartc/quartc_packet_writer.h b/quic/quartc/quartc_packet_writer.h
new file mode 100644
index 0000000..f3eb885
--- /dev/null
+++ b/quic/quartc/quartc_packet_writer.h
@@ -0,0 +1,113 @@
+// Copyright (c) 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_QUARTC_QUARTC_PACKET_WRITER_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_PACKET_WRITER_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_packet_writer.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+
+namespace quic {
+
+// Send and receive packets, like a virtual UDP socket. For example, this
+// could be implemented by WebRTC's IceTransport.
+class QuartcPacketTransport {
+ public:
+  // Additional metadata provided for each packet written.
+  struct PacketInfo {
+    QuicPacketNumber packet_number;
+  };
+
+  // Delegate for packet transport callbacks.  Note that the delegate is not
+  // thread-safe.  Packet transport implementations must ensure that callbacks
+  // are synchronized with all other work done by QUIC.
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // Called whenever the transport can write.
+    virtual void OnTransportCanWrite() = 0;
+
+    // Called when the transport receives a packet.
+    virtual void OnTransportReceived(const char* data, size_t data_len) = 0;
+  };
+
+  virtual ~QuartcPacketTransport() {}
+
+  // Called by the QuartcPacketWriter when writing packets to the network.
+  // Return the number of written bytes. Return 0 if the write is blocked.
+  virtual int Write(const char* buffer,
+                    size_t buf_len,
+                    const PacketInfo& info) = 0;
+
+  // Sets the delegate which must be called when the transport can write or
+  // a packet is received.  QUIC sets |delegate| to a nonnull pointer when it
+  // is ready to process incoming packets and sets |delegate| to nullptr before
+  // QUIC is deleted.  Implementations may assume |delegate| remains valid until
+  // it is set to nullptr.
+  virtual void SetDelegate(Delegate* delegate) = 0;
+};
+
+struct QuartcPerPacketOptions : public PerPacketOptions {
+  std::unique_ptr<PerPacketOptions> Clone() const override;
+
+  // The connection which is sending this packet.
+  QuicConnection* connection = nullptr;
+};
+
+// Implements a QuicPacketWriter using a QuartcPacketTransport, which allows a
+// QuicConnection to use (for example), a WebRTC IceTransport.
+class QuartcPacketWriter : public QuicPacketWriter {
+ public:
+  QuartcPacketWriter(QuartcPacketTransport* packet_transport,
+                     QuicByteCount max_packet_size);
+  ~QuartcPacketWriter() override {}
+
+  // The QuicConnection calls WritePacket and the QuicPacketWriter writes them
+  // to the QuartcSession::PacketTransport.
+  WriteResult WritePacket(const char* buffer,
+                          size_t buf_len,
+                          const QuicIpAddress& self_address,
+                          const QuicSocketAddress& peer_address,
+                          PerPacketOptions* options) override;
+
+  // Whether the underneath |transport_| is blocked. If this returns true,
+  // outgoing QUIC packets are queued by QuicConnection until SetWritable() is
+  // called.
+  bool IsWriteBlocked() const override;
+
+  // Maximum size of the QUIC packet which can be written. Users such as WebRTC
+  // can set the value through the QuartcFactoryConfig without updating the QUIC
+  // code.
+  QuicByteCount GetMaxPacketSize(
+      const QuicSocketAddress& peer_address) const override;
+
+  // Sets the packet writer to a writable (non-blocked) state.
+  void SetWritable() override;
+
+  bool SupportsReleaseTime() const override;
+
+  bool IsBatchMode() const override;
+
+  char* GetNextWriteLocation(const QuicIpAddress& self_address,
+                             const QuicSocketAddress& peer_address) override;
+
+  WriteResult Flush() override;
+
+  void SetPacketTransportDelegate(QuartcPacketTransport::Delegate* delegate);
+
+ private:
+  // QuartcPacketWriter will not own the transport.
+  QuartcPacketTransport* packet_transport_;
+  // The maximum size of the packet can be written by this writer.
+  QuicByteCount max_packet_size_;
+
+  // Whether packets can be written.
+  bool writable_ = false;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_PACKET_WRITER_H_
diff --git a/quic/quartc/quartc_session.cc b/quic/quartc/quartc_session.cc
new file mode 100644
index 0000000..8fa8e96
--- /dev/null
+++ b/quic/quartc/quartc_session.cc
@@ -0,0 +1,397 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_session.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_mem_slice_storage.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h"
+
+namespace quic {
+namespace {
+
+// Arbitrary server port number for net::QuicCryptoClientConfig.
+const int kQuicServerPort = 0;
+
+}  // namespace
+
+QuartcSession::QuartcSession(std::unique_ptr<QuicConnection> connection,
+                             Visitor* visitor,
+                             const QuicConfig& config,
+                             const ParsedQuicVersionVector& supported_versions,
+                             const QuicClock* clock)
+    : QuicSession(connection.get(), visitor, config, supported_versions),
+      connection_(std::move(connection)),
+      clock_(clock),
+      per_packet_options_(QuicMakeUnique<QuartcPerPacketOptions>()) {
+  per_packet_options_->connection = connection_.get();
+  connection_->set_per_packet_options(per_packet_options_.get());
+}
+
+QuartcSession::~QuartcSession() {}
+
+QuartcStream* QuartcSession::CreateOutgoingBidirectionalStream() {
+  // Use default priority for incoming QUIC streams.
+  // TODO(zhihuang): Determine if this value is correct.
+  return ActivateDataStream(CreateDataStream(
+      GetNextOutgoingBidirectionalStreamId(), QuicStream::kDefaultPriority));
+}
+
+bool QuartcSession::SendOrQueueMessage(QuicString message) {
+  if (!CanSendMessage()) {
+    QUIC_LOG(ERROR) << "Quic session does not support SendMessage";
+    return false;
+  }
+
+  if (message.size() > GetLargestMessagePayload()) {
+    QUIC_LOG(ERROR) << "Message is too big, message_size=" << message.size()
+                    << ", GetLargestMessagePayload="
+                    << GetLargestMessagePayload();
+    return false;
+  }
+
+  // There may be other messages in send queue, so we have to add message
+  // to the queue and call queue processing helper.
+  send_message_queue_.emplace_back(std::move(message));
+
+  ProcessSendMessageQueue();
+
+  return true;
+}
+
+void QuartcSession::ProcessSendMessageQueue() {
+  while (!send_message_queue_.empty()) {
+    struct iovec iov = {const_cast<char*>(send_message_queue_.front().data()),
+                        send_message_queue_.front().length()};
+    QuicMemSliceStorage storage(
+        &iov, 1, connection()->helper()->GetStreamSendBufferAllocator(),
+        send_message_queue_.front().length());
+    MessageResult result = SendMessage(storage.ToSpan());
+
+    const size_t message_size = send_message_queue_.front().size();
+
+    // Handle errors.
+    switch (result.status) {
+      case MESSAGE_STATUS_SUCCESS:
+        QUIC_VLOG(1) << "Quartc message sent, message_id=" << result.message_id
+                     << ", message_size=" << message_size;
+        break;
+
+      // If connection is congestion controlled or not writable yet, stop
+      // send loop and we'll retry again when we get OnCanWrite notification.
+      case MESSAGE_STATUS_ENCRYPTION_NOT_ESTABLISHED:
+      case MESSAGE_STATUS_BLOCKED:
+        QUIC_VLOG(1) << "Quartc message not sent because connection is blocked"
+                     << ", message will be retried later, status="
+                     << result.status << ", message_size=" << message_size;
+
+        return;
+
+      // Other errors are unexpected. We do not propagate error to Quartc,
+      // because writes can be delayed.
+      case MESSAGE_STATUS_UNSUPPORTED:
+      case MESSAGE_STATUS_TOO_LARGE:
+      case MESSAGE_STATUS_INTERNAL_ERROR:
+        QUIC_DLOG(DFATAL)
+            << "Failed to send quartc message due to unexpected error"
+            << ", message will not be retried, status=" << result.status
+            << ", message_size=" << message_size;
+        break;
+    }
+
+    send_message_queue_.pop_front();
+  }
+}
+
+void QuartcSession::OnCanWrite() {
+  // TODO(b/119640244): Since we currently use messages for audio and streams
+  // for video, it makes sense to process queued messages first, then call quic
+  // core OnCanWrite, which will resend queued streams. Long term we may need
+  // better solution especially if quic connection is used for both data and
+  // media.
+
+  // Process quartc messages that were previously blocked.
+  ProcessSendMessageQueue();
+
+  QuicSession::OnCanWrite();
+}
+
+void QuartcSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
+  QuicSession::OnCryptoHandshakeEvent(event);
+  switch (event) {
+    case ENCRYPTION_FIRST_ESTABLISHED:
+    case ENCRYPTION_REESTABLISHED:
+      // 1-rtt setup triggers 'ENCRYPTION_REESTABLISHED' (after REJ, when the
+      // CHLO is sent).
+      DCHECK(IsEncryptionEstablished());
+      DCHECK(session_delegate_);
+      session_delegate_->OnConnectionWritable();
+      break;
+    case HANDSHAKE_CONFIRMED:
+      // On the server, handshake confirmed is the first time when you can start
+      // writing packets.
+      DCHECK(IsEncryptionEstablished());
+      DCHECK(IsCryptoHandshakeConfirmed());
+
+      DCHECK(session_delegate_);
+      session_delegate_->OnConnectionWritable();
+      session_delegate_->OnCryptoHandshakeComplete();
+      break;
+  }
+}
+
+void QuartcSession::CancelStream(QuicStreamId stream_id) {
+  ResetStream(stream_id, QuicRstStreamErrorCode::QUIC_STREAM_CANCELLED);
+}
+
+void QuartcSession::ResetStream(QuicStreamId stream_id,
+                                QuicRstStreamErrorCode error) {
+  if (!IsOpenStream(stream_id)) {
+    return;
+  }
+  QuicStream* stream = QuicSession::GetOrCreateStream(stream_id);
+  if (stream) {
+    stream->Reset(error);
+  }
+}
+
+void QuartcSession::OnCongestionWindowChange(QuicTime now) {
+  DCHECK(session_delegate_);
+  const RttStats* rtt_stats = connection_->sent_packet_manager().GetRttStats();
+
+  QuicBandwidth bandwidth_estimate =
+      connection_->sent_packet_manager().BandwidthEstimate();
+
+  QuicByteCount in_flight =
+      connection_->sent_packet_manager().GetBytesInFlight();
+  QuicBandwidth pacing_rate =
+      connection_->sent_packet_manager().GetSendAlgorithm()->PacingRate(
+          in_flight);
+
+  session_delegate_->OnCongestionControlChange(bandwidth_estimate, pacing_rate,
+                                               rtt_stats->latest_rtt());
+}
+
+bool QuartcSession::ShouldKeepConnectionAlive() const {
+  // TODO(mellem): Quartc may want different keepalive logic than HTTP.
+  return GetNumOpenDynamicStreams() > 0;
+}
+
+void QuartcSession::OnConnectionClosed(QuicErrorCode error,
+                                       const QuicString& error_details,
+                                       ConnectionCloseSource source) {
+  QuicSession::OnConnectionClosed(error, error_details, source);
+  DCHECK(session_delegate_);
+  session_delegate_->OnConnectionClosed(error, error_details, source);
+}
+
+void QuartcSession::CloseConnection(const QuicString& details) {
+  connection_->CloseConnection(
+      QuicErrorCode::QUIC_CONNECTION_CANCELLED, details,
+      ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET_WITH_NO_ACK);
+}
+
+void QuartcSession::SetDelegate(Delegate* session_delegate) {
+  if (session_delegate_) {
+    LOG(WARNING) << "The delegate for the session has already been set.";
+  }
+  session_delegate_ = session_delegate;
+  DCHECK(session_delegate_);
+}
+
+void QuartcSession::OnTransportCanWrite() {
+  connection()->writer()->SetWritable();
+  if (HasDataToWrite()) {
+    connection()->OnCanWrite();
+  }
+}
+
+void QuartcSession::OnTransportReceived(const char* data, size_t data_len) {
+  QuicReceivedPacket packet(data, data_len, clock_->Now());
+  ProcessUdpPacket(connection()->self_address(), connection()->peer_address(),
+                   packet);
+}
+
+void QuartcSession::OnMessageReceived(QuicStringPiece message) {
+  session_delegate_->OnMessageReceived(message);
+}
+
+QuicStream* QuartcSession::CreateIncomingStream(QuicStreamId id) {
+  return ActivateDataStream(CreateDataStream(id, QuicStream::kDefaultPriority));
+}
+
+QuicStream* QuartcSession::CreateIncomingStream(PendingStream pending) {
+  return ActivateDataStream(
+      CreateDataStream(std::move(pending), QuicStream::kDefaultPriority));
+}
+
+std::unique_ptr<QuartcStream> QuartcSession::CreateDataStream(
+    QuicStreamId id,
+    spdy::SpdyPriority priority) {
+  if (GetCryptoStream() == nullptr ||
+      !GetCryptoStream()->encryption_established()) {
+    // Encryption not active so no stream created
+    return nullptr;
+  }
+  return InitializeDataStream(QuicMakeUnique<QuartcStream>(id, this), priority);
+}
+
+std::unique_ptr<QuartcStream> QuartcSession::CreateDataStream(
+    PendingStream pending,
+    spdy::SpdyPriority priority) {
+  return InitializeDataStream(QuicMakeUnique<QuartcStream>(std::move(pending)),
+                              priority);
+}
+
+std::unique_ptr<QuartcStream> QuartcSession::InitializeDataStream(
+    std::unique_ptr<QuartcStream> stream,
+    spdy::SpdyPriority priority) {
+  // Register the stream to the QuicWriteBlockedList. |priority| is clamped
+  // between 0 and 7, with 0 being the highest priority and 7 the lowest
+  // priority.
+  write_blocked_streams()->UpdateStreamPriority(stream->id(), priority);
+
+  if (IsIncomingStream(stream->id())) {
+    DCHECK(session_delegate_);
+    // Incoming streams need to be registered with the session_delegate_.
+    session_delegate_->OnIncomingStream(stream.get());
+  }
+  return stream;
+}
+
+QuartcStream* QuartcSession::ActivateDataStream(
+    std::unique_ptr<QuartcStream> stream) {
+  // Transfer ownership of the data stream to the session via ActivateStream().
+  QuartcStream* raw = stream.release();
+  if (raw) {
+    // Make QuicSession take ownership of the stream.
+    ActivateStream(std::unique_ptr<QuicStream>(raw));
+  }
+  return raw;
+}
+
+QuartcClientSession::QuartcClientSession(
+    std::unique_ptr<QuicConnection> connection,
+    const QuicConfig& config,
+    const ParsedQuicVersionVector& supported_versions,
+    const QuicClock* clock,
+    std::unique_ptr<QuartcPacketWriter> packet_writer,
+    std::unique_ptr<QuicCryptoClientConfig> client_crypto_config,
+    QuicStringPiece server_crypto_config)
+    : QuartcSession(std::move(connection),
+                    /*visitor=*/nullptr,
+                    config,
+                    supported_versions,
+                    clock),
+      packet_writer_(std::move(packet_writer)),
+      client_crypto_config_(std::move(client_crypto_config)),
+      server_config_(server_crypto_config) {
+  DCHECK_EQ(QuartcSession::connection()->perspective(), Perspective::IS_CLIENT);
+}
+
+QuartcClientSession::~QuartcClientSession() {
+  // The client session is the packet transport delegate, so it must be unset
+  // before the session is deleted.
+  packet_writer_->SetPacketTransportDelegate(nullptr);
+}
+
+void QuartcClientSession::Initialize() {
+  DCHECK(crypto_stream_) << "Do not call QuartcSession::Initialize(), call "
+                            "StartCryptoHandshake() instead.";
+  QuartcSession::Initialize();
+
+  // QUIC is ready to process incoming packets after Initialize().
+  // Set the packet transport delegate to begin receiving packets.
+  packet_writer_->SetPacketTransportDelegate(this);
+}
+
+const QuicCryptoStream* QuartcClientSession::GetCryptoStream() const {
+  return crypto_stream_.get();
+}
+
+QuicCryptoStream* QuartcClientSession::GetMutableCryptoStream() {
+  return crypto_stream_.get();
+}
+
+void QuartcClientSession::StartCryptoHandshake() {
+  QuicServerId server_id(/*host=*/"", kQuicServerPort,
+                         /*privacy_mode_enabled=*/false);
+
+  if (!server_config_.empty()) {
+    QuicCryptoServerConfig::ConfigOptions options;
+
+    QuicString error;
+    QuicWallTime now = clock()->WallNow();
+    QuicCryptoClientConfig::CachedState::ServerConfigState result =
+        client_crypto_config_->LookupOrCreate(server_id)->SetServerConfig(
+            server_config_, now,
+            /*expiry_time=*/now.Add(QuicTime::Delta::Infinite()), &error);
+
+    if (result == QuicCryptoClientConfig::CachedState::SERVER_CONFIG_VALID) {
+      DCHECK_EQ(error, "");
+      client_crypto_config_->LookupOrCreate(server_id)->SetProof(
+          std::vector<QuicString>{kDummyCertName}, /*cert_sct=*/"",
+          /*chlo_hash=*/"", /*signature=*/"anything");
+    } else {
+      LOG(DFATAL) << "Unable to set server config, error=" << error;
+    }
+  }
+
+  crypto_stream_ = QuicMakeUnique<QuicCryptoClientStream>(
+      server_id, this,
+      client_crypto_config_->proof_verifier()->CreateDefaultContext(),
+      client_crypto_config_.get(), this);
+  Initialize();
+  crypto_stream_->CryptoConnect();
+}
+
+void QuartcClientSession::OnProofValid(
+    const QuicCryptoClientConfig::CachedState& cached) {
+  // TODO(zhihuang): Handle the proof verification.
+}
+
+void QuartcClientSession::OnProofVerifyDetailsAvailable(
+    const ProofVerifyDetails& verify_details) {
+  // TODO(zhihuang): Handle the proof verification.
+}
+
+QuartcServerSession::QuartcServerSession(
+    std::unique_ptr<QuicConnection> connection,
+    Visitor* visitor,
+    const QuicConfig& config,
+    const ParsedQuicVersionVector& supported_versions,
+    const QuicClock* clock,
+    const QuicCryptoServerConfig* server_crypto_config,
+    QuicCompressedCertsCache* const compressed_certs_cache,
+    QuicCryptoServerStream::Helper* const stream_helper)
+    : QuartcSession(std::move(connection),
+                    visitor,
+                    config,
+                    supported_versions,
+                    clock),
+      server_crypto_config_(server_crypto_config),
+      compressed_certs_cache_(compressed_certs_cache),
+      stream_helper_(stream_helper) {
+  DCHECK_EQ(QuartcSession::connection()->perspective(), Perspective::IS_SERVER);
+}
+
+const QuicCryptoStream* QuartcServerSession::GetCryptoStream() const {
+  return crypto_stream_.get();
+}
+
+QuicCryptoStream* QuartcServerSession::GetMutableCryptoStream() {
+  return crypto_stream_.get();
+}
+
+void QuartcServerSession::StartCryptoHandshake() {
+  crypto_stream_ = QuicMakeUnique<QuicCryptoServerStream>(
+      server_crypto_config_, compressed_certs_cache_,
+      /*use_stateless_rejects_if_peer_supported=*/false, this, stream_helper_);
+  Initialize();
+}
+
+}  // namespace quic
diff --git a/quic/quartc/quartc_session.h b/quic/quartc/quartc_session.h
new file mode 100644
index 0000000..7efa186
--- /dev/null
+++ b/quic/quartc/quartc_session.h
@@ -0,0 +1,284 @@
+// Copyright (c) 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_QUARTC_QUARTC_SESSION_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_SESSION_H_
+
+#include <memory>
+#include <string>
+
+#include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_stream.h"
+
+namespace quic {
+
+// QuartcSession owns and manages a QUIC connection.
+class QuartcSession : public QuicSession,
+                      public QuartcPacketTransport::Delegate {
+ public:
+  QuartcSession(std::unique_ptr<QuicConnection> connection,
+                Visitor* visitor,
+                const QuicConfig& config,
+                const ParsedQuicVersionVector& supported_versions,
+                const QuicClock* clock);
+  QuartcSession(const QuartcSession&) = delete;
+  QuartcSession& operator=(const QuartcSession&) = delete;
+  ~QuartcSession() override;
+
+  // QuicSession overrides.
+  QuartcStream* CreateOutgoingBidirectionalStream();
+
+  // Sends short unreliable message using quic message frame (message must fit
+  // in one quic packet). If connection is blocked by congestion control,
+  // message will be queued and resent later after receiving an OnCanWrite
+  // notification.
+  //
+  // Message size must be <= GetLargestMessagePayload().
+  //
+  // Supported in quic version 45 or later.
+  //
+  // Returns false and logs error if message is too long or session does not
+  // support SendMessage API. Other unexpected errors during send will not be
+  // returned, because messages can be sent later if connection is congestion
+  // controlled.
+  bool SendOrQueueMessage(QuicString message);
+
+  // Returns largest message payload acceptable in SendQuartcMessage.
+  QuicPacketLength GetLargestMessagePayload() const {
+    return connection()->GetLargestMessagePayload();
+  }
+
+  // Return true if transport support message frame.
+  bool CanSendMessage() const {
+    return connection()->transport_version() > QUIC_VERSION_44;
+  }
+
+  void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
+
+  // QuicConnectionVisitorInterface overrides.
+  void OnCongestionWindowChange(QuicTime now) override;
+  bool ShouldKeepConnectionAlive() const override;
+
+  void OnCanWrite() override;
+
+  void OnConnectionClosed(QuicErrorCode error,
+                          const QuicString& error_details,
+                          ConnectionCloseSource source) override;
+
+  // QuartcSession methods.
+  virtual void StartCryptoHandshake() = 0;
+
+  // Closes the connection with the given human-readable error details.
+  // The connection closes with the QUIC_CONNECTION_CANCELLED error code to
+  // indicate the application closed it.
+  //
+  // Informs the peer that the connection has been closed.  This prevents the
+  // peer from waiting until the connection times out.
+  //
+  // Cleans up the underlying QuicConnection's state.  Closing the connection
+  // makes it safe to delete the QuartcSession.
+  void CloseConnection(const QuicString& details);
+
+  // If the given stream is still open, sends a reset frame to cancel it.
+  // Note:  This method cancels a stream by QuicStreamId rather than by pointer
+  // (or by a method on QuartcStream) because QuartcSession (and not
+  // the caller) owns the streams.  Streams may finish and be deleted before the
+  // caller tries to cancel them, rendering the caller's pointers invalid.
+  void CancelStream(QuicStreamId stream_id);
+
+  // Callbacks called by the QuartcSession to notify the user of the
+  // QuartcSession of certain events.
+  class Delegate {
+   public:
+    virtual ~Delegate() {}
+
+    // Called when the crypto handshake is complete. Crypto handshake on the
+    // client is only completed _after_ SHLO is received, but we can actually
+    // start sending media data right after CHLO is sent.
+    virtual void OnCryptoHandshakeComplete() = 0;
+
+    // Connection can be writable even before crypto handshake is complete.
+    // In particular, on the client, we can start sending data after sending
+    // full CHLO, without waiting for SHLO. This reduces a send delay by 1-rtt.
+    //
+    // This may be called multiple times.
+    virtual void OnConnectionWritable() = 0;
+
+    // Called when a new stream is received from the remote endpoint.
+    virtual void OnIncomingStream(QuartcStream* stream) = 0;
+
+    // Called when network parameters change in response to an ack frame.
+    virtual void OnCongestionControlChange(QuicBandwidth bandwidth_estimate,
+                                           QuicBandwidth pacing_rate,
+                                           QuicTime::Delta latest_rtt) = 0;
+
+    // Called when the connection is closed. This means all of the streams will
+    // be closed and no new streams can be created.
+    virtual void OnConnectionClosed(QuicErrorCode error_code,
+                                    const QuicString& error_details,
+                                    ConnectionCloseSource source) = 0;
+
+    // Called when message (sent as SendMessage) is received.
+    virtual void OnMessageReceived(QuicStringPiece message) = 0;
+
+    // TODO(zhihuang): Add proof verification.
+  };
+
+  // The |delegate| is not owned by QuartcSession.
+  void SetDelegate(Delegate* session_delegate);
+
+  // Called when CanWrite() changes from false to true.
+  void OnTransportCanWrite() override;
+
+  // Called when a packet has been received and should be handled by the
+  // QuicConnection.
+  void OnTransportReceived(const char* data, size_t data_len) override;
+
+  void OnMessageReceived(QuicStringPiece message) override;
+
+  // Returns number of queued (not sent) messages submitted by
+  // SendOrQueueMessage. Messages are queued if connection is congestion
+  // controlled.
+  size_t send_message_queue_size() const { return send_message_queue_.size(); }
+
+ protected:
+  // QuicSession override.
+  QuicStream* CreateIncomingStream(QuicStreamId id) override;
+  QuicStream* CreateIncomingStream(PendingStream pending) override;
+
+  std::unique_ptr<QuartcStream> CreateDataStream(QuicStreamId id,
+                                                 spdy::SpdyPriority priority);
+  std::unique_ptr<QuartcStream> CreateDataStream(PendingStream pending,
+                                                 spdy::SpdyPriority priority);
+  // Activates a QuartcStream.  The session takes ownership of the stream, but
+  // returns an unowned pointer to the stream for convenience.
+  QuartcStream* ActivateDataStream(std::unique_ptr<QuartcStream> stream);
+
+  void ResetStream(QuicStreamId stream_id, QuicRstStreamErrorCode error);
+
+  const QuicClock* clock() { return clock_; }
+
+ private:
+  std::unique_ptr<QuartcStream> InitializeDataStream(
+      std::unique_ptr<QuartcStream> stream,
+      spdy::SpdyPriority priority);
+
+  void ProcessSendMessageQueue();
+
+  // Take ownership of the QuicConnection.  Note: if |connection_| changes,
+  // the new value of |connection_| must be given to |packet_writer_| before any
+  // packets are written.  Otherwise, |packet_writer_| will crash.
+  std::unique_ptr<QuicConnection> connection_;
+
+  // For recording packet receipt time
+  const QuicClock* clock_;
+
+  // Not owned by QuartcSession.
+  Delegate* session_delegate_ = nullptr;
+
+  // Options passed to the packet writer for each packet.
+  std::unique_ptr<QuartcPerPacketOptions> per_packet_options_;
+
+  // Queue of pending messages sent by SendQuartcMessage that were not sent
+  // yet or blocked by congestion control. Messages are queued in the order
+  // of sent by SendOrQueueMessage().
+  QuicDeque<QuicString> send_message_queue_;
+};
+
+class QuartcClientSession : public QuartcSession,
+                            public QuicCryptoClientStream::ProofHandler {
+ public:
+  QuartcClientSession(
+      std::unique_ptr<QuicConnection> connection,
+      const QuicConfig& config,
+      const ParsedQuicVersionVector& supported_versions,
+      const QuicClock* clock,
+      std::unique_ptr<QuartcPacketWriter> packet_writer,
+      std::unique_ptr<QuicCryptoClientConfig> client_crypto_config,
+      QuicStringPiece server_crypto_config);
+  QuartcClientSession(const QuartcClientSession&) = delete;
+  QuartcClientSession& operator=(const QuartcClientSession&) = delete;
+
+  ~QuartcClientSession() override;
+
+  // Initialize should not be called on a QuartcSession.  Instead, call
+  // StartCryptoHandshake().
+  // TODO(mellem): Move creation of the crypto stream into Initialize() and
+  // remove StartCryptoHandshake() to bring QuartcSession in line with other
+  // implementations of QuicSession, which can be started by calling
+  // Initialize().
+  void Initialize() override;
+
+  // Accessors for the client crypto stream.
+  QuicCryptoStream* GetMutableCryptoStream() override;
+  const QuicCryptoStream* GetCryptoStream() const override;
+
+  // Initializes the session and sends a handshake.
+  void StartCryptoHandshake() override;
+
+  // ProofHandler overrides.
+  void OnProofValid(const QuicCryptoClientConfig::CachedState& cached) override;
+
+  // Called by the client crypto handshake when proof verification details
+  // become available, either because proof verification is complete, or when
+  // cached details are used.
+  void OnProofVerifyDetailsAvailable(
+      const ProofVerifyDetails& verify_details) override;
+
+ private:
+  // Packet writer used by |connection_|.
+  std::unique_ptr<QuartcPacketWriter> packet_writer_;
+
+  // Config for QUIC crypto stream.
+  std::unique_ptr<QuicCryptoClientConfig> client_crypto_config_;
+
+  // Client perspective crypto stream.
+  std::unique_ptr<QuicCryptoClientStream> crypto_stream_;
+
+  const QuicString server_config_;
+};
+
+class QuartcServerSession : public QuartcSession {
+ public:
+  QuartcServerSession(std::unique_ptr<QuicConnection> connection,
+                      Visitor* visitor,
+                      const QuicConfig& config,
+                      const ParsedQuicVersionVector& supported_versions,
+                      const QuicClock* clock,
+                      const QuicCryptoServerConfig* server_crypto_config,
+                      QuicCompressedCertsCache* const compressed_certs_cache,
+                      QuicCryptoServerStream::Helper* const stream_helper);
+  QuartcServerSession(const QuartcServerSession&) = delete;
+  QuartcServerSession& operator=(const QuartcServerSession&) = delete;
+
+  // Accessors for the server crypto stream.
+  QuicCryptoStream* GetMutableCryptoStream() override;
+  const QuicCryptoStream* GetCryptoStream() const override;
+
+  // Initializes the session and prepares to receive a handshake.
+  void StartCryptoHandshake() override;
+
+ private:
+  // Config for QUIC crypto stream.
+  const QuicCryptoServerConfig* server_crypto_config_;
+
+  // Used by QUIC crypto server stream to track most recently compressed certs.
+  QuicCompressedCertsCache* const compressed_certs_cache_;
+
+  // This helper is needed to create QuicCryptoServerStream.
+  QuicCryptoServerStream::Helper* const stream_helper_;
+
+  // Server perspective crypto stream.
+  std::unique_ptr<QuicCryptoServerStream> crypto_stream_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_SESSION_H_
diff --git a/quic/quartc/quartc_session_test.cc b/quic/quartc/quartc_session_test.cc
new file mode 100644
index 0000000..fad4a8f
--- /dev/null
+++ b/quic/quartc/quartc_session_test.cc
@@ -0,0 +1,559 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_session.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test_mem_slice_vector.h"
+#include "net/third_party/quiche/src/quic/quartc/counting_packet_filter.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_endpoint.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_fakes.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h"
+#include "net/third_party/quiche/src/quic/quartc/simulated_packet_transport.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_clock.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/packet_filter.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h"
+
+namespace quic {
+
+namespace {
+
+constexpr QuicTime::Delta kPropagationDelay =
+    QuicTime::Delta::FromMilliseconds(10);
+// Propagation delay and a bit, but no more than full RTT.
+constexpr QuicTime::Delta kPropagationDelayAndABit =
+    QuicTime::Delta::FromMilliseconds(12);
+
+static QuicByteCount kDefaultMaxPacketSize = 1200;
+
+class QuartcSessionTest : public QuicTest {
+ public:
+  ~QuartcSessionTest() override {}
+
+  void Init(bool create_client_endpoint = true) {
+    client_transport_ =
+        QuicMakeUnique<simulator::SimulatedQuartcPacketTransport>(
+            &simulator_, "client_transport", "server_transport",
+            10 * kDefaultMaxPacketSize);
+    server_transport_ =
+        QuicMakeUnique<simulator::SimulatedQuartcPacketTransport>(
+            &simulator_, "server_transport", "client_transport",
+            10 * kDefaultMaxPacketSize);
+
+    client_filter_ = QuicMakeUnique<simulator::CountingPacketFilter>(
+        &simulator_, "client_filter", client_transport_.get());
+
+    client_server_link_ = QuicMakeUnique<simulator::SymmetricLink>(
+        client_filter_.get(), server_transport_.get(),
+        QuicBandwidth::FromKBitsPerSecond(10 * 1000), kPropagationDelay);
+
+    client_stream_delegate_ = QuicMakeUnique<FakeQuartcStreamDelegate>();
+    client_session_delegate_ = QuicMakeUnique<FakeQuartcSessionDelegate>(
+        client_stream_delegate_.get(), simulator_.GetClock());
+    client_endpoint_delegate_ = QuicMakeUnique<FakeQuartcEndpointDelegate>(
+        client_session_delegate_.get());
+
+    server_stream_delegate_ = QuicMakeUnique<FakeQuartcStreamDelegate>();
+    server_session_delegate_ = QuicMakeUnique<FakeQuartcSessionDelegate>(
+        server_stream_delegate_.get(), simulator_.GetClock());
+    server_endpoint_delegate_ = QuicMakeUnique<FakeQuartcEndpointDelegate>(
+        server_session_delegate_.get());
+
+    // No 0-rtt setup, because server config is empty.
+    // CannotCreateDataStreamBeforeHandshake depends on 1-rtt setup.
+    if (create_client_endpoint) {
+      client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>(
+          simulator_.GetAlarmFactory(), simulator_.GetClock(),
+          client_endpoint_delegate_.get(), quic::QuartcSessionConfig(),
+          /*serialized_server_config=*/"");
+    }
+    server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>(
+        simulator_.GetAlarmFactory(), simulator_.GetClock(),
+        server_endpoint_delegate_.get(), quic::QuartcSessionConfig());
+  }
+
+  // Note that input session config will apply to both server and client.
+  // Perspective and packet_transport will be overwritten.
+  void CreateClientAndServerSessions(const QuartcSessionConfig& session_config,
+                                     bool init = true) {
+    if (init) {
+      Init();
+    }
+
+    server_endpoint_->Connect(server_transport_.get());
+    client_endpoint_->Connect(client_transport_.get());
+
+    CHECK(simulator_.RunUntil([this] {
+      return client_endpoint_delegate_->session() != nullptr &&
+             server_endpoint_delegate_->session() != nullptr;
+    }));
+
+    client_peer_ = client_endpoint_delegate_->session();
+    server_peer_ = server_endpoint_delegate_->session();
+  }
+
+  // Runs all tasks scheduled in the next 200 ms.
+  void RunTasks() { simulator_.RunFor(QuicTime::Delta::FromMilliseconds(200)); }
+
+  void AwaitHandshake() {
+    simulator_.RunUntil([this] {
+      return client_peer_->IsCryptoHandshakeConfirmed() &&
+             server_peer_->IsCryptoHandshakeConfirmed();
+    });
+  }
+
+  // Test handshake establishment and sending/receiving of data for two
+  // directions.
+  void TestSendReceiveStreams() {
+    ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed());
+    ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed());
+    ASSERT_TRUE(server_peer_->IsEncryptionEstablished());
+    ASSERT_TRUE(client_peer_->IsEncryptionEstablished());
+
+    // Now we can establish encrypted outgoing stream.
+    QuartcStream* outgoing_stream =
+        server_peer_->CreateOutgoingBidirectionalStream();
+    QuicStreamId stream_id = outgoing_stream->id();
+    ASSERT_NE(nullptr, outgoing_stream);
+    EXPECT_TRUE(server_peer_->ShouldKeepConnectionAlive());
+
+    outgoing_stream->SetDelegate(server_stream_delegate_.get());
+
+    // Send a test message from peer 1 to peer 2.
+    char kTestMessage[] = "Hello";
+    test::QuicTestMemSliceVector data(
+        {std::make_pair(kTestMessage, strlen(kTestMessage))});
+    outgoing_stream->WriteMemSlices(data.span(), /*fin=*/false);
+    RunTasks();
+
+    // Wait for peer 2 to receive messages.
+    ASSERT_TRUE(client_stream_delegate_->has_data());
+
+    QuartcStream* incoming = client_session_delegate_->last_incoming_stream();
+    ASSERT_TRUE(incoming);
+    EXPECT_EQ(incoming->id(), stream_id);
+    EXPECT_TRUE(client_peer_->ShouldKeepConnectionAlive());
+
+    EXPECT_EQ(client_stream_delegate_->data()[stream_id], kTestMessage);
+    // Send a test message from peer 2 to peer 1.
+    char kTestResponse[] = "Response";
+    test::QuicTestMemSliceVector response(
+        {std::make_pair(kTestResponse, strlen(kTestResponse))});
+    incoming->WriteMemSlices(response.span(), /*fin=*/false);
+    RunTasks();
+    // Wait for peer 1 to receive messages.
+    ASSERT_TRUE(server_stream_delegate_->has_data());
+
+    EXPECT_EQ(server_stream_delegate_->data()[stream_id], kTestResponse);
+  }
+
+  // Test sending/receiving of messages for two directions.
+  void TestSendReceiveMessage() {
+    ASSERT_TRUE(server_peer_->CanSendMessage());
+    ASSERT_TRUE(client_peer_->CanSendMessage());
+
+    // Send message from peer 1 to peer 2.
+    ASSERT_TRUE(server_peer_->SendOrQueueMessage("Message from server"));
+
+    // First message in each direction should not be queued.
+    EXPECT_EQ(server_peer_->send_message_queue_size(), 0u);
+
+    // Wait for peer 2 to receive message.
+    RunTasks();
+
+    EXPECT_THAT(client_session_delegate_->incoming_messages(),
+                testing::ElementsAre("Message from server"));
+
+    // Send message from peer 2 to peer 1.
+    ASSERT_TRUE(client_peer_->SendOrQueueMessage("Message from client"));
+
+    // First message in each direction should not be queued.
+    EXPECT_EQ(client_peer_->send_message_queue_size(), 0u);
+
+    // Wait for peer 1 to receive message.
+    RunTasks();
+
+    EXPECT_THAT(server_session_delegate_->incoming_messages(),
+                testing::ElementsAre("Message from client"));
+  }
+
+  // Test for sending multiple messages that also result in queueing.
+  // This is one-way test, which is run in given direction.
+  void TestSendReceiveQueuedMessages(bool direction_from_server) {
+    // Send until queue_size number of messages are queued.
+    constexpr size_t queue_size = 10;
+
+    ASSERT_TRUE(server_peer_->CanSendMessage());
+    ASSERT_TRUE(client_peer_->CanSendMessage());
+
+    QuartcSession* const peer_sending =
+        direction_from_server ? server_peer_ : client_peer_;
+
+    FakeQuartcSessionDelegate* const delegate_receiving =
+        direction_from_server ? client_session_delegate_.get()
+                              : server_session_delegate_.get();
+
+    // There should be no messages in the queue before we start sending.
+    EXPECT_EQ(peer_sending->send_message_queue_size(), 0u);
+
+    // Send messages from peer 1 to peer 2 until required number of messages
+    // are queued in unsent message queue.
+    std::vector<QuicString> sent_messages;
+    while (peer_sending->send_message_queue_size() < queue_size) {
+      sent_messages.push_back(
+          QuicStrCat("Sending message, index=", sent_messages.size()));
+      ASSERT_TRUE(peer_sending->SendOrQueueMessage(sent_messages.back()));
+    }
+
+    // Wait for peer 2 to receive all messages.
+    RunTasks();
+
+    EXPECT_EQ(delegate_receiving->incoming_messages(), sent_messages);
+  }
+
+  // Test sending long messages:
+  // - message of maximum allowed length should succeed
+  // - message of > maximum allowed length should fail.
+  void TestSendLongMessage() {
+    ASSERT_TRUE(server_peer_->CanSendMessage());
+    ASSERT_TRUE(client_peer_->CanSendMessage());
+
+    // Send message of maximum allowed length.
+    QuicString message_max_long =
+        QuicString(server_peer_->GetLargestMessagePayload(), 'A');
+    ASSERT_TRUE(server_peer_->SendOrQueueMessage(message_max_long));
+
+    // Send long message which should fail.
+    QuicString message_too_long =
+        QuicString(server_peer_->GetLargestMessagePayload() + 1, 'B');
+    ASSERT_FALSE(server_peer_->SendOrQueueMessage(message_too_long));
+
+    // Wait for peer 2 to receive message.
+    RunTasks();
+
+    // Client should only receive one message of allowed length.
+    EXPECT_THAT(client_session_delegate_->incoming_messages(),
+                testing::ElementsAre(message_max_long));
+  }
+
+  // Test that client and server are not connected after handshake failure.
+  void TestDisconnectAfterFailedHandshake() {
+    EXPECT_TRUE(!client_session_delegate_->connected());
+    EXPECT_TRUE(!server_session_delegate_->connected());
+
+    EXPECT_FALSE(client_peer_->IsEncryptionEstablished());
+    EXPECT_FALSE(client_peer_->IsCryptoHandshakeConfirmed());
+
+    EXPECT_FALSE(server_peer_->IsEncryptionEstablished());
+    EXPECT_FALSE(server_peer_->IsCryptoHandshakeConfirmed());
+  }
+
+ protected:
+  simulator::Simulator simulator_;
+
+  std::unique_ptr<simulator::SimulatedQuartcPacketTransport> client_transport_;
+  std::unique_ptr<simulator::SimulatedQuartcPacketTransport> server_transport_;
+  std::unique_ptr<simulator::CountingPacketFilter> client_filter_;
+  std::unique_ptr<simulator::SymmetricLink> client_server_link_;
+
+  std::unique_ptr<FakeQuartcStreamDelegate> client_stream_delegate_;
+  std::unique_ptr<FakeQuartcSessionDelegate> client_session_delegate_;
+  std::unique_ptr<FakeQuartcEndpointDelegate> client_endpoint_delegate_;
+  std::unique_ptr<FakeQuartcStreamDelegate> server_stream_delegate_;
+  std::unique_ptr<FakeQuartcSessionDelegate> server_session_delegate_;
+  std::unique_ptr<FakeQuartcEndpointDelegate> server_endpoint_delegate_;
+
+  std::unique_ptr<QuartcClientEndpoint> client_endpoint_;
+  std::unique_ptr<QuartcServerEndpoint> server_endpoint_;
+
+  QuartcSession* client_peer_ = nullptr;
+  QuartcSession* server_peer_ = nullptr;
+};
+
+TEST_F(QuartcSessionTest, SendReceiveStreams) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+  AwaitHandshake();
+  TestSendReceiveStreams();
+}
+
+TEST_F(QuartcSessionTest, SendReceiveMessages) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+  AwaitHandshake();
+  TestSendReceiveMessage();
+}
+
+TEST_F(QuartcSessionTest, SendReceiveQueuedMessages) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+  AwaitHandshake();
+  TestSendReceiveQueuedMessages(/*direction_from_server=*/true);
+  TestSendReceiveQueuedMessages(/*direction_from_server=*/false);
+}
+
+TEST_F(QuartcSessionTest, SendMessageFails) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+  AwaitHandshake();
+  TestSendLongMessage();
+}
+
+TEST_F(QuartcSessionTest, TestCryptoHandshakeCanWriteTriggers) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+
+  AwaitHandshake();
+
+  RunTasks();
+
+  ASSERT_TRUE(client_session_delegate_->writable_time().IsInitialized());
+  ASSERT_TRUE(
+      client_session_delegate_->crypto_handshake_time().IsInitialized());
+  // On client, we are writable 1-rtt before crypto handshake is complete.
+  ASSERT_LT(client_session_delegate_->writable_time(),
+            client_session_delegate_->crypto_handshake_time());
+
+  ASSERT_TRUE(server_session_delegate_->writable_time().IsInitialized());
+  ASSERT_TRUE(
+      server_session_delegate_->crypto_handshake_time().IsInitialized());
+  // On server, the writable time and crypto handshake are the same. (when SHLO
+  // is sent).
+  ASSERT_EQ(server_session_delegate_->writable_time(),
+            server_session_delegate_->crypto_handshake_time());
+}
+
+TEST_F(QuartcSessionTest, PreSharedKeyHandshake) {
+  QuartcSessionConfig config;
+  config.pre_shared_key = "foo";
+  CreateClientAndServerSessions(config);
+  AwaitHandshake();
+  TestSendReceiveStreams();
+  TestSendReceiveMessage();
+}
+
+// Test that data streams are not created before handshake.
+TEST_F(QuartcSessionTest, CannotCreateDataStreamBeforeHandshake) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+  EXPECT_EQ(nullptr, server_peer_->CreateOutgoingBidirectionalStream());
+  EXPECT_EQ(nullptr, client_peer_->CreateOutgoingBidirectionalStream());
+}
+
+TEST_F(QuartcSessionTest, CancelQuartcStream) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+  AwaitHandshake();
+  ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed());
+  ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed());
+
+  QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream();
+  ASSERT_NE(nullptr, stream);
+
+  uint32_t id = stream->id();
+  EXPECT_FALSE(client_peer_->IsClosedStream(id));
+  stream->SetDelegate(client_stream_delegate_.get());
+  client_peer_->CancelStream(id);
+  EXPECT_EQ(stream->stream_error(),
+            QuicRstStreamErrorCode::QUIC_STREAM_CANCELLED);
+  EXPECT_TRUE(client_peer_->IsClosedStream(id));
+}
+
+// TODO(b/112561077):  This is the wrong layer for this test.  We should write a
+// test specifically for QuartcPacketWriter with a stubbed-out
+// QuartcPacketTransport and remove
+// SimulatedQuartcPacketTransport::last_packet_number().
+TEST_F(QuartcSessionTest, WriterGivesPacketNumberToTransport) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+  AwaitHandshake();
+  ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed());
+  ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed());
+
+  QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream();
+  stream->SetDelegate(client_stream_delegate_.get());
+
+  char kClientMessage[] = "Hello";
+  test::QuicTestMemSliceVector stream_data(
+      {std::make_pair(kClientMessage, strlen(kClientMessage))});
+  stream->WriteMemSlices(stream_data.span(), /*fin=*/false);
+  RunTasks();
+
+  // The transport should see the latest packet number sent by QUIC.
+  EXPECT_EQ(
+      client_transport_->last_packet_number(),
+      client_peer_->connection()->sent_packet_manager().GetLargestSentPacket());
+}
+
+TEST_F(QuartcSessionTest, CloseConnection) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+  AwaitHandshake();
+  ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed());
+  ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed());
+
+  client_peer_->CloseConnection("Connection closed by client");
+  EXPECT_FALSE(client_session_delegate_->connected());
+  RunTasks();
+  EXPECT_FALSE(server_session_delegate_->connected());
+}
+
+TEST_F(QuartcSessionTest, StreamRetransmissionEnabled) {
+  CreateClientAndServerSessions(QuartcSessionConfig());
+  AwaitHandshake();
+  ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed());
+  ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed());
+
+  QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream();
+  QuicStreamId stream_id = stream->id();
+  stream->SetDelegate(client_stream_delegate_.get());
+  stream->set_cancel_on_loss(false);
+
+  client_filter_->set_packets_to_drop(1);
+
+  char kClientMessage[] = "Hello";
+  test::QuicTestMemSliceVector stream_data(
+      {std::make_pair(kClientMessage, strlen(kClientMessage))});
+  stream->WriteMemSlices(stream_data.span(), /*fin=*/false);
+  RunTasks();
+
+  // Stream data should make it despite packet loss.
+  ASSERT_TRUE(server_stream_delegate_->has_data());
+  EXPECT_EQ(server_stream_delegate_->data()[stream_id], kClientMessage);
+}
+
+TEST_F(QuartcSessionTest, StreamRetransmissionDisabled) {
+  // Disable tail loss probe, otherwise test maybe flaky because dropped
+  // message will be retransmitted to detect tail loss.
+  QuartcSessionConfig session_config;
+  session_config.enable_tail_loss_probe = false;
+  CreateClientAndServerSessions(session_config);
+
+  // Disable probing retransmissions, otherwise test maybe flaky because dropped
+  // message will be retransmitted to to probe for more bandwidth.
+  client_peer_->connection()->set_fill_up_link_during_probing(false);
+
+  AwaitHandshake();
+  ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed());
+  ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed());
+
+  // The client sends an ACK for the crypto handshake next.  This must be
+  // flushed before we set the filter to drop the next packet, in order to
+  // ensure that the filter drops a data-bearing packet instead of just an ack.
+  RunTasks();
+
+  QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream();
+  QuicStreamId stream_id = stream->id();
+  stream->SetDelegate(client_stream_delegate_.get());
+  stream->set_cancel_on_loss(true);
+
+  client_filter_->set_packets_to_drop(1);
+
+  char kMessage[] = "Hello";
+  test::QuicTestMemSliceVector stream_data(
+      {std::make_pair(kMessage, strlen(kMessage))});
+  stream->WriteMemSlices(stream_data.span(), /*fin=*/false);
+  simulator_.RunFor(QuicTime::Delta::FromMilliseconds(1));
+
+  // Send another packet to trigger loss detection.
+  QuartcStream* stream_1 = client_peer_->CreateOutgoingBidirectionalStream();
+  stream_1->SetDelegate(client_stream_delegate_.get());
+
+  char kMessage1[] = "Second message";
+  test::QuicTestMemSliceVector stream_data_1(
+      {std::make_pair(kMessage1, strlen(kMessage1))});
+  stream_1->WriteMemSlices(stream_data_1.span(), /*fin=*/false);
+  RunTasks();
+
+  // QUIC should try to retransmit the first stream by loss detection.  Instead,
+  // it will cancel itself.
+  EXPECT_THAT(server_stream_delegate_->data()[stream_id], testing::IsEmpty());
+
+  EXPECT_TRUE(client_peer_->IsClosedStream(stream_id));
+  EXPECT_TRUE(server_peer_->IsClosedStream(stream_id));
+  EXPECT_EQ(client_stream_delegate_->stream_error(stream_id),
+            QUIC_STREAM_CANCELLED);
+  EXPECT_EQ(server_stream_delegate_->stream_error(stream_id),
+            QUIC_STREAM_CANCELLED);
+}
+
+TEST_F(QuartcSessionTest, ServerRegistersAsWriteBlocked) {
+  // Initialize client and server session, but with the server write-blocked.
+  Init();
+  server_transport_->SetWritable(false);
+  CreateClientAndServerSessions(QuartcSessionConfig(), /*init=*/false);
+
+  // Let the client send a few copies of the CHLO.  The server can't respond, as
+  // it's still write-blocked.
+  RunTasks();
+
+  // Making the server's transport writable should trigger a callback that
+  // reaches the server session, allowing it to write packets.
+  server_transport_->SetWritable(true);
+
+  // Now the server should respond with the SHLO, encryption should be
+  // established, and data should flow normally.
+  // Note that if the server is *not* correctly registered as write-blocked,
+  // it will crash here (see b/124527328 for details).
+  AwaitHandshake();
+  TestSendReceiveStreams();
+}
+
+TEST_F(QuartcSessionTest, PreSharedKeyHandshakeIs0RTT) {
+  QuartcSessionConfig session_config;
+  session_config.pre_shared_key = "foo";
+
+  // Client endpoint is created below. Destructing client endpoint
+  // causes issues with the simulator.
+  Init(/*create_client_endpoint=*/false);
+
+  server_endpoint_->Connect(server_transport_.get());
+
+  client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>(
+      simulator_.GetAlarmFactory(), simulator_.GetClock(),
+      client_endpoint_delegate_.get(), QuartcSessionConfig(),
+      // This is the key line here. It passes through the server config
+      // from the server to the client.
+      server_endpoint_->server_crypto_config());
+
+  client_endpoint_->Connect(client_transport_.get());
+
+  // Running for 1ms. This is shorter than the RTT, so the
+  // client session should be created, but server won't be created yet.
+  simulator_.RunFor(QuicTime::Delta::FromMilliseconds(1));
+
+  client_peer_ = client_endpoint_delegate_->session();
+  server_peer_ = server_endpoint_delegate_->session();
+
+  ASSERT_NE(client_peer_, nullptr);
+  ASSERT_EQ(server_peer_, nullptr);
+
+  // Write data to the client before running tasks.  This should be sent by the
+  // client and received by the server if the handshake is 0RTT.
+  // If this test fails, add 'RunTasks()' above, and see what error is sent
+  // by the server in the rejection message.
+  QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream();
+  ASSERT_NE(stream, nullptr);
+  QuicStreamId stream_id = stream->id();
+  stream->SetDelegate(client_stream_delegate_.get());
+
+  char message[] = "Hello in 0RTTs!";
+  test::QuicTestMemSliceVector data({std::make_pair(message, strlen(message))});
+  stream->WriteMemSlices(data.span(), /*fin=*/false);
+
+  // This will now run the rest of the connection. But the
+  // Server peer will receive the CHLO and message after 1 delay.
+  simulator_.RunFor(kPropagationDelayAndABit);
+
+  // If we can decrypt the data, it means that 0 rtt was successful.
+  // This is because we waited only a propagation delay. So if the decryption
+  // failed, we would send sREJ instead of SHLO, but it wouldn't be delivered to
+  // the client yet.
+  ASSERT_TRUE(server_stream_delegate_->has_data());
+  EXPECT_EQ(server_stream_delegate_->data()[stream_id], message);
+}
+
+}  // namespace
+
+}  // namespace quic
diff --git a/quic/quartc/quartc_stream.cc b/quic/quartc/quartc_stream.cc
new file mode 100644
index 0000000..49ade23
--- /dev/null
+++ b/quic/quartc/quartc_stream.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_stream.h"
+
+#include <memory>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/quic_ack_listener_interface.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_send_buffer.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer_buffer.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_reference_counted.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+QuartcStream::QuartcStream(QuicStreamId id, QuicSession* session)
+    : QuicStream(id, session, /*is_static=*/false, BIDIRECTIONAL) {
+  sequencer()->set_level_triggered(true);
+}
+
+QuartcStream::QuartcStream(PendingStream pending)
+    : QuicStream(std::move(pending), BIDIRECTIONAL) {
+  sequencer()->set_level_triggered(true);
+}
+
+QuartcStream::~QuartcStream() {}
+
+void QuartcStream::OnDataAvailable() {
+  bool fin = sequencer()->ReadableBytes() + sequencer()->NumBytesConsumed() ==
+             sequencer()->close_offset();
+
+  // Upper bound on number of readable regions.  Each complete block's worth of
+  // data crosses at most one region boundary.  The remainder may cross one more
+  // boundary.  Number of regions is one more than the number of region
+  // boundaries crossed.
+  size_t iov_length = sequencer()->ReadableBytes() /
+                          QuicStreamSequencerBuffer::kBlockSizeBytes +
+                      2;
+  std::unique_ptr<iovec[]> iovecs = QuicMakeUnique<iovec[]>(iov_length);
+  iov_length = sequencer()->GetReadableRegions(iovecs.get(), iov_length);
+
+  sequencer()->MarkConsumed(
+      delegate_->OnReceived(this, iovecs.get(), iov_length, fin));
+  if (sequencer()->IsClosed()) {
+    OnFinRead();
+  }
+}
+
+void QuartcStream::OnClose() {
+  QuicStream::OnClose();
+  DCHECK(delegate_);
+  delegate_->OnClose(this);
+}
+
+void QuartcStream::OnStreamDataConsumed(size_t bytes_consumed) {
+  QuicStream::OnStreamDataConsumed(bytes_consumed);
+
+  DCHECK(delegate_);
+  delegate_->OnBufferChanged(this);
+}
+
+void QuartcStream::OnDataBuffered(
+    QuicStreamOffset offset,
+    QuicByteCount data_length,
+    const QuicReferenceCountedPointer<QuicAckListenerInterface>& ack_listener) {
+  DCHECK(delegate_);
+  delegate_->OnBufferChanged(this);
+}
+
+bool QuartcStream::OnStreamFrameAcked(QuicStreamOffset offset,
+                                      QuicByteCount data_length,
+                                      bool fin_acked,
+                                      QuicTime::Delta ack_delay_time,
+                                      QuicByteCount* newly_acked_length) {
+  // Previous losses of acked data are no longer relevant to the retransmission
+  // count.  Once data is acked, it will never be retransmitted.
+  lost_frame_counter_.RemoveInterval(
+      QuicInterval<QuicStreamOffset>(offset, offset + data_length));
+
+  return QuicStream::OnStreamFrameAcked(offset, data_length, fin_acked,
+                                        ack_delay_time, newly_acked_length);
+}
+
+void QuartcStream::OnStreamFrameRetransmitted(QuicStreamOffset offset,
+                                              QuicByteCount data_length,
+                                              bool fin_retransmitted) {
+  QuicStream::OnStreamFrameRetransmitted(offset, data_length,
+                                         fin_retransmitted);
+
+  DCHECK(delegate_);
+  delegate_->OnBufferChanged(this);
+}
+
+void QuartcStream::OnStreamFrameLost(QuicStreamOffset offset,
+                                     QuicByteCount data_length,
+                                     bool fin_lost) {
+  QuicStream::OnStreamFrameLost(offset, data_length, fin_lost);
+
+  lost_frame_counter_.AddInterval(
+      QuicInterval<QuicStreamOffset>(offset, offset + data_length));
+
+  DCHECK(delegate_);
+  delegate_->OnBufferChanged(this);
+}
+
+void QuartcStream::OnCanWrite() {
+  if (lost_frame_counter_.MaxCount() >
+          static_cast<size_t>(max_retransmission_count_) &&
+      HasPendingRetransmission()) {
+    Reset(QUIC_STREAM_CANCELLED);
+    return;
+  }
+  QuicStream::OnCanWrite();
+}
+
+bool QuartcStream::cancel_on_loss() {
+  return max_retransmission_count_ == 0;
+}
+
+void QuartcStream::set_cancel_on_loss(bool cancel_on_loss) {
+  if (cancel_on_loss) {
+    max_retransmission_count_ = 0;
+  } else {
+    max_retransmission_count_ = std::numeric_limits<int>::max();
+  }
+}
+
+int QuartcStream::max_retransmission_count() const {
+  return max_retransmission_count_;
+}
+
+void QuartcStream::set_max_retransmission_count(int max_retransmission_count) {
+  max_retransmission_count_ = max_retransmission_count;
+}
+
+QuicByteCount QuartcStream::BytesPendingRetransmission() {
+  if (lost_frame_counter_.MaxCount() >
+      static_cast<size_t>(max_retransmission_count_)) {
+    return 0;  // Lost bytes will never be retransmitted.
+  }
+  QuicByteCount bytes = 0;
+  for (const auto& interval : send_buffer().pending_retransmissions()) {
+    bytes += interval.Length();
+  }
+  return bytes;
+}
+
+QuicStreamOffset QuartcStream::ReadOffset() {
+  return sequencer()->NumBytesConsumed();
+}
+
+void QuartcStream::FinishWriting() {
+  WriteOrBufferData(QuicStringPiece(nullptr, 0), true, nullptr);
+}
+
+void QuartcStream::SetDelegate(Delegate* delegate) {
+  if (delegate_) {
+    LOG(WARNING) << "The delegate for Stream " << id()
+                 << " has already been set.";
+  }
+  delegate_ = delegate;
+  DCHECK(delegate_);
+}
+
+}  // namespace quic
diff --git a/quic/quartc/quartc_stream.h b/quic/quartc/quartc_stream.h
new file mode 100644
index 0000000..bf15a68
--- /dev/null
+++ b/quic/quartc/quartc_stream.h
@@ -0,0 +1,142 @@
+// Copyright (c) 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_QUARTC_QUARTC_STREAM_H_
+#define QUICHE_QUIC_QUARTC_QUARTC_STREAM_H_
+
+#include <stddef.h>
+#include <limits>
+
+#include "net/third_party/quiche/src/quic/core/quic_ack_listener_interface.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_mem_slice_span.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_reference_counted.h"
+#include "net/quic/platform/impl/quic_export_impl.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_interval_counter.h"
+
+namespace quic {
+
+// Sends and receives data with a particular QUIC stream ID, reliably and
+// in-order. To send/receive data out of order, use separate streams. To
+// send/receive unreliably, close a stream after reliability is no longer
+// needed.
+class QuartcStream : public QuicStream {
+ public:
+  QuartcStream(QuicStreamId id, QuicSession* session);
+  explicit QuartcStream(PendingStream pending);
+
+  ~QuartcStream() override;
+
+  // QuicStream overrides.
+  void OnDataAvailable() override;
+
+  void OnClose() override;
+
+  void OnStreamDataConsumed(size_t bytes_consumed) override;
+
+  void OnDataBuffered(
+      QuicStreamOffset offset,
+      QuicByteCount data_length,
+      const QuicReferenceCountedPointer<QuicAckListenerInterface>& ack_listener)
+      override;
+
+  bool OnStreamFrameAcked(QuicStreamOffset offset,
+                          QuicByteCount data_length,
+                          bool fin_acked,
+                          QuicTime::Delta ack_delay_time,
+                          QuicByteCount* newly_acked_length) override;
+
+  void OnStreamFrameRetransmitted(QuicStreamOffset offset,
+                                  QuicByteCount data_length,
+                                  bool fin_retransmitted) override;
+
+  void OnStreamFrameLost(QuicStreamOffset offset,
+                         QuicByteCount data_length,
+                         bool fin_lost) override;
+
+  void OnCanWrite() override;
+
+  // QuartcStream interface methods.
+
+  // Whether the stream should be cancelled instead of retransmitted on loss.
+  // If set to true, the stream will reset itself instead of retransmitting lost
+  // stream frames.  Defaults to false.  Setting it to true is equivalent to
+  // setting |max_retransmission_count| to zero.
+  bool cancel_on_loss();
+  void set_cancel_on_loss(bool cancel_on_loss);
+
+  // Maximum number of times this stream's data may be retransmitted.  Each byte
+  // of stream data may be retransmitted this many times.  If any byte (or range
+  // of bytes) is lost and would be retransmitted more than this number of
+  // times, the stream resets itself instead of retransmitting the data again.
+  // Setting this value to zero disables retransmissions.
+  //
+  // Note that this limit applies only to stream data, not to the FIN bit.  If
+  // only the FIN bit needs to be retransmitted, there is no benefit to
+  // cancelling the stream and sending a reset frame instead.
+  int max_retransmission_count() const;
+  void set_max_retransmission_count(int max_retransmission_count);
+
+  QuicByteCount BytesPendingRetransmission();
+
+  // Returns the current read offset for this stream.  During a call to
+  // Delegate::OnReceived, this value is the offset of the first byte read.
+  QuicStreamOffset ReadOffset();
+
+  // Marks this stream as finished writing.  Asynchronously sends a FIN and
+  // closes the write-side.  It is not necessary to call FinishWriting() if the
+  // last call to Write() sends a FIN.
+  void FinishWriting();
+
+  // Implemented by the user of the QuartcStream to receive incoming
+  // data and be notified of state changes.
+  class Delegate {
+   public:
+    virtual ~Delegate() {}
+
+    // Called when the stream receives data. |iov| is a pointer to the first of
+    // |iov_length| readable regions. |iov| points to readable data within
+    // |stream|'s sequencer buffer. QUIC may modify or delete this data after
+    // the application consumes it. |fin| indicates the end of stream data.
+    // Returns the number of bytes consumed. May return 0 if the delegate is
+    // unable to consume any bytes at this time.
+    virtual size_t OnReceived(QuartcStream* stream,
+                              iovec* iov,
+                              size_t iov_length,
+                              bool fin) = 0;
+
+    // Called when the stream is closed, either locally or by the remote
+    // endpoint.  Streams close when (a) fin bits are both sent and received,
+    // (b) Close() is called, or (c) the stream is reset.
+    // TODO(zhihuang) Creates a map from the integer error_code to WebRTC native
+    // error code.
+    virtual void OnClose(QuartcStream* stream) = 0;
+
+    // Called when the contents of the stream's buffer changes.
+    virtual void OnBufferChanged(QuartcStream* stream) = 0;
+  };
+
+  // The |delegate| is not owned by QuartcStream.
+  void SetDelegate(Delegate* delegate);
+
+ private:
+  Delegate* delegate_ = nullptr;
+
+  // Maximum number of times this stream's data may be retransmitted.
+  int max_retransmission_count_ = std::numeric_limits<int>::max();
+
+  // Counter which tracks the number of times each frame has been lost
+  // (accounting for the possibility of overlapping frames).
+  //
+  // If the maximum count of any lost frame exceeds |max_retransmission_count_|,
+  // the stream will cancel itself on the next attempt to retransmit data (the
+  // next call to |OnCanWrite|).
+  QuartcIntervalCounter<QuicStreamOffset> lost_frame_counter_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_QUARTC_STREAM_H_
diff --git a/quic/quartc/quartc_stream_test.cc b/quic/quartc/quartc_stream_test.cc
new file mode 100644
index 0000000..edb0ee0
--- /dev/null
+++ b/quic/quartc/quartc_stream_test.cc
@@ -0,0 +1,640 @@
+// Copyright (c) 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.
+
+#include "net/third_party/quiche/src/quic/quartc/quartc_stream.h"
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_buffer_allocator.h"
+#include "net/third_party/quiche/src/quic/core/quic_config.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_packet_writer.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h"
+#include "net/third_party/quiche/src/quic/core/quic_time.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test_mem_slice_vector.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_factory.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_clock.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+namespace quic {
+
+namespace {
+
+static const QuicStreamId kStreamId = 5;
+
+// MockQuicSession that does not create streams and writes data from
+// QuicStream to a string.
+class MockQuicSession : public QuicSession {
+ public:
+  MockQuicSession(QuicConnection* connection,
+                  const QuicConfig& config,
+                  QuicString* write_buffer)
+      : QuicSession(connection,
+                    nullptr /*visitor*/,
+                    config,
+                    CurrentSupportedVersions()),
+        write_buffer_(write_buffer) {}
+
+  ~MockQuicSession() override {}
+
+  // Writes outgoing data from QuicStream to a string.
+  QuicConsumedData WritevData(QuicStream* stream,
+                              QuicStreamId id,
+                              size_t write_length,
+                              QuicStreamOffset offset,
+                              StreamSendingState state) override {
+    if (!writable_) {
+      return QuicConsumedData(0, false);
+    }
+
+    // WritevData does not pass down a iovec, data is saved in stream before
+    // data is consumed. Retrieve data from stream.
+    char* buf = new char[write_length];
+    QuicDataWriter writer(write_length, buf, NETWORK_BYTE_ORDER);
+    if (write_length > 0) {
+      stream->WriteStreamData(offset, write_length, &writer);
+    }
+    write_buffer_->append(buf, write_length);
+    delete[] buf;
+    return QuicConsumedData(write_length, state != StreamSendingState::NO_FIN);
+  }
+
+  QuartcStream* CreateIncomingStream(QuicStreamId id) override {
+    return nullptr;
+  }
+
+  QuartcStream* CreateIncomingStream(PendingStream pending) override {
+    return nullptr;
+  }
+
+  const QuicCryptoStream* GetCryptoStream() const override { return nullptr; }
+  QuicCryptoStream* GetMutableCryptoStream() override { return nullptr; }
+  bool ShouldKeepConnectionAlive() const override {
+    return GetNumOpenDynamicStreams() > 0;
+  }
+
+  // Called by QuicStream when they want to close stream.
+  void SendRstStream(QuicStreamId id,
+                     QuicRstStreamErrorCode error,
+                     QuicStreamOffset bytes_written) override {}
+
+  // Sets whether data is written to buffer, or else if this is write blocked.
+  void set_writable(bool writable) { writable_ = writable; }
+
+  // Tracks whether the stream is write blocked and its priority.
+  void RegisterReliableStream(QuicStreamId stream_id,
+                              spdy::SpdyPriority priority) {
+    write_blocked_streams()->RegisterStream(stream_id,
+                                            /*is_static_stream=*/false,
+                                            priority);
+  }
+
+  // The session take ownership of the stream.
+  void ActivateReliableStream(std::unique_ptr<QuicStream> stream) {
+    ActivateStream(std::move(stream));
+  }
+
+ private:
+  // Stores written data from ReliableQuicStreamAdapter.
+  QuicString* write_buffer_;
+  // Whether data is written to write_buffer_.
+  bool writable_ = true;
+};
+
+// Packet writer that does nothing. This is required for QuicConnection but
+// isn't used for writing data.
+class DummyPacketWriter : public QuicPacketWriter {
+ public:
+  DummyPacketWriter() {}
+
+  // QuicPacketWriter overrides.
+  WriteResult WritePacket(const char* buffer,
+                          size_t buf_len,
+                          const QuicIpAddress& self_address,
+                          const QuicSocketAddress& peer_address,
+                          PerPacketOptions* options) override {
+    return WriteResult(WRITE_STATUS_ERROR, 0);
+  }
+
+  bool IsWriteBlocked() const override { return false; }
+
+  void SetWritable() override {}
+
+  QuicByteCount GetMaxPacketSize(
+      const QuicSocketAddress& peer_address) const override {
+    return 0;
+  }
+
+  bool SupportsReleaseTime() const override { return false; }
+
+  bool IsBatchMode() const override { return false; }
+
+  char* GetNextWriteLocation(const QuicIpAddress& self_address,
+                             const QuicSocketAddress& peer_address) override {
+    return nullptr;
+  }
+
+  WriteResult Flush() override { return WriteResult(WRITE_STATUS_OK, 0); }
+};
+
+class MockQuartcStreamDelegate : public QuartcStream::Delegate {
+ public:
+  MockQuartcStreamDelegate(QuicStreamId id, QuicString* read_buffer)
+      : id_(id), read_buffer_(read_buffer) {}
+
+  void OnBufferChanged(QuartcStream* stream) override {
+    last_bytes_buffered_ = stream->BufferedDataBytes();
+    last_bytes_pending_retransmission_ = stream->BytesPendingRetransmission();
+  }
+
+  size_t OnReceived(QuartcStream* stream,
+                    iovec* iov,
+                    size_t iov_length,
+                    bool fin) override {
+    EXPECT_EQ(id_, stream->id());
+    EXPECT_EQ(stream->ReadOffset(), read_buffer_->size());
+    size_t bytes_consumed = 0;
+    for (size_t i = 0; i < iov_length; ++i) {
+      read_buffer_->append(static_cast<const char*>(iov[i].iov_base),
+                           iov[i].iov_len);
+      bytes_consumed += iov[i].iov_len;
+    }
+    return bytes_consumed;
+  }
+
+  void OnClose(QuartcStream* stream) override { closed_ = true; }
+
+  bool closed() { return closed_; }
+
+  QuicByteCount last_bytes_buffered() { return last_bytes_buffered_; }
+  QuicByteCount last_bytes_pending_retransmission() {
+    return last_bytes_pending_retransmission_;
+  }
+
+ protected:
+  QuicStreamId id_;
+  // Data read by the QuicStream.
+  QuicString* read_buffer_;
+  // Whether the QuicStream is closed.
+  bool closed_ = false;
+
+  // Last amount of data observed as buffered.
+  QuicByteCount last_bytes_buffered_ = 0;
+  QuicByteCount last_bytes_pending_retransmission_ = 0;
+};
+
+class QuartcStreamTest : public QuicTest, public QuicConnectionHelperInterface {
+ public:
+  QuartcStreamTest() {
+    // Required to correctly handle StopReading().
+    SetQuicReloadableFlag(quic_stop_reading_when_level_triggered, true);
+  }
+
+  ~QuartcStreamTest() override = default;
+
+  void CreateReliableQuicStream() {
+    // Arbitrary values for QuicConnection.
+    Perspective perspective = Perspective::IS_SERVER;
+    QuicIpAddress ip;
+    ip.FromString("0.0.0.0");
+    bool owns_writer = true;
+
+    alarm_factory_ = QuicMakeUnique<test::MockAlarmFactory>();
+
+    connection_ = QuicMakeUnique<QuicConnection>(
+        QuicUtils::CreateZeroConnectionId(
+            CurrentSupportedVersions()[0].transport_version),
+        QuicSocketAddress(ip, 0), this /*QuicConnectionHelperInterface*/,
+        alarm_factory_.get(), new DummyPacketWriter(), owns_writer, perspective,
+        ParsedVersionOfIndex(CurrentSupportedVersions(), 0));
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    session_ = QuicMakeUnique<MockQuicSession>(connection_.get(), QuicConfig(),
+                                               &write_buffer_);
+    mock_stream_delegate_ =
+        QuicMakeUnique<MockQuartcStreamDelegate>(kStreamId, &read_buffer_);
+    stream_ = new QuartcStream(kStreamId, session_.get());
+    stream_->SetDelegate(mock_stream_delegate_.get());
+    session_->ActivateReliableStream(std::unique_ptr<QuartcStream>(stream_));
+  }
+
+  const QuicClock* GetClock() const override { return &clock_; }
+
+  QuicRandom* GetRandomGenerator() override {
+    return QuicRandom::GetInstance();
+  }
+
+  QuicBufferAllocator* GetStreamSendBufferAllocator() override {
+    return &buffer_allocator_;
+  }
+
+ protected:
+  // The QuicSession will take the ownership.
+  QuartcStream* stream_;
+  std::unique_ptr<MockQuartcStreamDelegate> mock_stream_delegate_;
+  std::unique_ptr<MockQuicSession> session_;
+  // Data written by the ReliableQuicStreamAdapterTest.
+  QuicString write_buffer_;
+  // Data read by the ReliableQuicStreamAdapterTest.
+  QuicString read_buffer_;
+  std::unique_ptr<QuicAlarmFactory> alarm_factory_;
+  std::unique_ptr<QuicConnection> connection_;
+  // Used to implement the QuicConnectionHelperInterface.
+  SimpleBufferAllocator buffer_allocator_;
+  MockClock clock_;
+};
+
+// Write an entire string.
+TEST_F(QuartcStreamTest, WriteDataWhole) {
+  CreateReliableQuicStream();
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+  EXPECT_EQ("Foo bar", write_buffer_);
+}
+
+// Write part of a string.
+TEST_F(QuartcStreamTest, WriteDataPartial) {
+  CreateReliableQuicStream();
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 5)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+  EXPECT_EQ("Foo b", write_buffer_);
+}
+
+// Test that a QuartcStream buffers writes correctly.
+TEST_F(QuartcStreamTest, StreamBuffersData) {
+  CreateReliableQuicStream();
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+
+  // The stream is not yet writable, so data will be buffered.
+  session_->set_writable(false);
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  // Check that data is buffered.
+  EXPECT_TRUE(stream_->HasBufferedData());
+  EXPECT_EQ(7u, stream_->BufferedDataBytes());
+
+  // Check that the stream told its delegate about the buffer change.
+  EXPECT_EQ(7u, mock_stream_delegate_->last_bytes_buffered());
+
+  // Check that none of the data was written yet.
+  // Note that |write_buffer_| actually holds data written by the QuicSession
+  // (not data buffered by the stream).
+  EXPECT_EQ(0ul, write_buffer_.size());
+
+  char message1[] = "xyzzy";
+  test::QuicTestMemSliceVector data1({std::make_pair(message1, 5)});
+
+  // More writes go into the buffer.
+  stream_->WriteMemSlices(data1.span(), /*fin=*/false);
+
+  EXPECT_TRUE(stream_->HasBufferedData());
+  EXPECT_EQ(12u, stream_->BufferedDataBytes());
+  EXPECT_EQ(12u, mock_stream_delegate_->last_bytes_buffered());
+  EXPECT_EQ(0ul, write_buffer_.size());
+
+  // The stream becomes writable, so it sends the buffered data.
+  session_->set_writable(true);
+  stream_->OnCanWrite();
+
+  EXPECT_FALSE(stream_->HasBufferedData());
+  EXPECT_EQ(0u, stream_->BufferedDataBytes());
+  EXPECT_EQ(0u, mock_stream_delegate_->last_bytes_buffered());
+  EXPECT_EQ("Foo barxyzzy", write_buffer_);
+}
+
+// Finish writing to a stream.
+// It delivers the fin bit and closes the write-side as soon as possible.
+TEST_F(QuartcStreamTest, FinishWriting) {
+  CreateReliableQuicStream();
+
+  session_->set_writable(false);
+  stream_->FinishWriting();
+  EXPECT_FALSE(stream_->fin_sent());
+
+  // Fin is sent as soon as the stream becomes writable.
+  session_->set_writable(true);
+  stream_->OnCanWrite();
+  EXPECT_TRUE(stream_->fin_sent());
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+// Read an entire string.
+TEST_F(QuartcStreamTest, ReadDataWhole) {
+  CreateReliableQuicStream();
+  QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!");
+  stream_->OnStreamFrame(frame);
+
+  EXPECT_EQ("Hello, World!", read_buffer_);
+}
+
+// Read part of a string.
+TEST_F(QuartcStreamTest, ReadDataPartial) {
+  CreateReliableQuicStream();
+  QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!");
+  frame.data_length = 5;
+  stream_->OnStreamFrame(frame);
+
+  EXPECT_EQ("Hello", read_buffer_);
+}
+
+// Streams do not call OnReceived() after StopReading().
+// Note: this is tested here because Quartc relies on this behavior.
+TEST_F(QuartcStreamTest, StopReading) {
+  CreateReliableQuicStream();
+  stream_->StopReading();
+
+  QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!");
+  stream_->OnStreamFrame(frame);
+
+  EXPECT_EQ(0ul, read_buffer_.size());
+
+  QuicStreamFrame frame2(kStreamId, true, 0, "Hello, World!");
+  stream_->OnStreamFrame(frame2);
+
+  EXPECT_EQ(0ul, read_buffer_.size());
+  EXPECT_TRUE(stream_->fin_received());
+}
+
+// Test that closing the stream results in a callback.
+TEST_F(QuartcStreamTest, CloseStream) {
+  CreateReliableQuicStream();
+  EXPECT_FALSE(mock_stream_delegate_->closed());
+  stream_->OnClose();
+  EXPECT_TRUE(mock_stream_delegate_->closed());
+}
+
+// Both sending and receiving fin automatically closes a stream.
+TEST_F(QuartcStreamTest, CloseOnFins) {
+  CreateReliableQuicStream();
+  QuicStreamFrame frame(kStreamId, true, 0, 0);
+  stream_->OnStreamFrame(frame);
+
+  test::QuicTestMemSliceVector data({});
+  stream_->WriteMemSlices(data.span(), /*fin=*/true);
+
+  // Check that the OnClose() callback occurred.
+  EXPECT_TRUE(mock_stream_delegate_->closed());
+}
+
+TEST_F(QuartcStreamTest, TestCancelOnLossDisabled) {
+  CreateReliableQuicStream();
+
+  // This should be the default state.
+  EXPECT_FALSE(stream_->cancel_on_loss());
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+
+  stream_->OnStreamFrameLost(0, 7, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo bar", write_buffer_);
+  EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_NO_ERROR);
+}
+
+TEST_F(QuartcStreamTest, TestCancelOnLossEnabled) {
+  CreateReliableQuicStream();
+  stream_->set_cancel_on_loss(true);
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+
+  stream_->OnStreamFrameLost(0, 7, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+  EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_CANCELLED);
+}
+
+TEST_F(QuartcStreamTest, MaxRetransmissionsAbsent) {
+  CreateReliableQuicStream();
+
+  // This should be the default state.
+  EXPECT_EQ(stream_->max_retransmission_count(),
+            std::numeric_limits<int>::max());
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+
+  stream_->OnStreamFrameLost(0, 7, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo bar", write_buffer_);
+  EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_NO_ERROR);
+}
+
+TEST_F(QuartcStreamTest, MaxRetransmissionsSet) {
+  CreateReliableQuicStream();
+  stream_->set_max_retransmission_count(2);
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+
+  stream_->OnStreamFrameLost(0, 7, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo bar", write_buffer_);
+
+  stream_->OnStreamFrameLost(0, 7, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo barFoo bar", write_buffer_);
+
+  stream_->OnStreamFrameLost(0, 7, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo barFoo bar", write_buffer_);
+  EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_CANCELLED);
+}
+
+TEST_F(QuartcStreamTest, MaxRetransmissionsDisjointFrames) {
+  CreateReliableQuicStream();
+  stream_->set_max_retransmission_count(2);
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+
+  // Retransmit bytes [0, 3].
+  stream_->OnStreamFrameLost(0, 4, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo ", write_buffer_);
+
+  // Retransmit bytes [4, 6].  Everything has been retransmitted once.
+  stream_->OnStreamFrameLost(4, 3, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo bar", write_buffer_);
+
+  // Retransmit bytes [0, 6].  Everything can be retransmitted a second time.
+  stream_->OnStreamFrameLost(0, 7, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo barFoo bar", write_buffer_);
+}
+
+TEST_F(QuartcStreamTest, MaxRetransmissionsOverlappingFrames) {
+  CreateReliableQuicStream();
+  stream_->set_max_retransmission_count(2);
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+
+  // Retransmit bytes 0 to 3.
+  stream_->OnStreamFrameLost(0, 4, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo ", write_buffer_);
+
+  // Retransmit bytes 3 to 6.  Byte 3 has been retransmitted twice.
+  stream_->OnStreamFrameLost(3, 4, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo  bar", write_buffer_);
+
+  // Retransmit byte 3 a third time.  This should cause cancellation.
+  stream_->OnStreamFrameLost(3, 1, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo  bar", write_buffer_);
+  EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_CANCELLED);
+}
+
+TEST_F(QuartcStreamTest, MaxRetransmissionsWithAckedFrame) {
+  CreateReliableQuicStream();
+  stream_->set_max_retransmission_count(1);
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+
+  // Retransmit bytes [0, 7).
+  stream_->OnStreamFrameLost(0, 7, false);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo bar", write_buffer_);
+
+  // Ack bytes [0, 7).  These bytes should be pruned from the data tracked by
+  // the stream.
+  QuicByteCount newly_acked_length = 0;
+  stream_->OnStreamFrameAcked(0, 7, false, QuicTime::Delta::FromMilliseconds(1),
+                              &newly_acked_length);
+  EXPECT_EQ(7u, newly_acked_length);
+  stream_->OnCanWrite();
+
+  EXPECT_EQ("Foo barFoo bar", write_buffer_);
+
+  // Retransmit bytes [0, 7) again.
+  // QUIC will never mark frames as lost after they've been acked, but this lets
+  // us test that QuartcStream stopped tracking these bytes after the acked.
+  stream_->OnStreamFrameLost(0, 7, false);
+  stream_->OnCanWrite();
+
+  // QuartcStream should be cancelled, but it stopped tracking the lost bytes
+  // after they were acked, so it's not.
+  EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_NO_ERROR);
+}
+
+TEST_F(QuartcStreamTest, TestBytesPendingRetransmission) {
+  CreateReliableQuicStream();
+  stream_->set_cancel_on_loss(false);
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+
+  stream_->OnStreamFrameLost(0, 4, false);
+  EXPECT_EQ(stream_->BytesPendingRetransmission(), 4u);
+  EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 4u);
+
+  stream_->OnStreamFrameLost(4, 3, false);
+  EXPECT_EQ(stream_->BytesPendingRetransmission(), 7u);
+  EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 7u);
+
+  stream_->OnCanWrite();
+  EXPECT_EQ(stream_->BytesPendingRetransmission(), 0u);
+  EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 0u);
+
+  EXPECT_EQ("Foo barFoo bar", write_buffer_);
+  EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_NO_ERROR);
+}
+
+TEST_F(QuartcStreamTest, TestBytesPendingRetransmissionWithCancelOnLoss) {
+  CreateReliableQuicStream();
+  stream_->set_cancel_on_loss(true);
+
+  char message[] = "Foo bar";
+  test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
+  stream_->WriteMemSlices(data.span(), /*fin=*/false);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+
+  stream_->OnStreamFrameLost(0, 4, false);
+  EXPECT_EQ(stream_->BytesPendingRetransmission(), 0u);
+  EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 0u);
+
+  stream_->OnStreamFrameLost(4, 3, false);
+  EXPECT_EQ(stream_->BytesPendingRetransmission(), 0u);
+  EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 0u);
+
+  stream_->OnCanWrite();
+  EXPECT_EQ(stream_->BytesPendingRetransmission(), 0u);
+  EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 0u);
+
+  EXPECT_EQ("Foo bar", write_buffer_);
+  EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_CANCELLED);
+}
+
+}  // namespace
+
+}  // namespace quic
diff --git a/quic/quartc/simulated_packet_transport.cc b/quic/quartc/simulated_packet_transport.cc
new file mode 100644
index 0000000..5c0d374
--- /dev/null
+++ b/quic/quartc/simulated_packet_transport.cc
@@ -0,0 +1,95 @@
+// 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 "net/third_party/quiche/src/quic/quartc/simulated_packet_transport.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+
+namespace quic {
+namespace simulator {
+
+SimulatedQuartcPacketTransport::SimulatedQuartcPacketTransport(
+    Simulator* simulator,
+    const QuicString& name,
+    const QuicString& peer_name,
+    QuicByteCount queue_capacity)
+    : Endpoint(simulator, name),
+      peer_name_(peer_name),
+      egress_queue_(simulator,
+                    QuicStringPrintf("%s (TX Queue)", name.c_str()),
+                    queue_capacity) {
+  egress_queue_.set_listener_interface(this);
+}
+
+int SimulatedQuartcPacketTransport::Write(const char* buffer,
+                                          size_t buf_len,
+                                          const PacketInfo& info) {
+  if (!writable_) {
+    return 0;
+  }
+  if (egress_queue_.bytes_queued() + buf_len > egress_queue_.capacity()) {
+    return 0;
+  }
+
+  last_packet_number_ = info.packet_number;
+
+  auto packet = QuicMakeUnique<Packet>();
+  packet->contents = QuicString(buffer, buf_len);
+  packet->size = buf_len;
+  packet->tx_timestamp = clock_->Now();
+  packet->source = name();
+  packet->destination = peer_name_;
+
+  egress_queue_.AcceptPacket(std::move(packet));
+  return buf_len;
+}
+
+void SimulatedQuartcPacketTransport::SetDelegate(Delegate* delegate) {
+  delegate_ = delegate;
+  Schedule(clock_->Now());
+}
+
+UnconstrainedPortInterface* SimulatedQuartcPacketTransport::GetRxPort() {
+  return this;
+}
+
+void SimulatedQuartcPacketTransport::SetTxPort(ConstrainedPortInterface* port) {
+  egress_queue_.set_tx_port(port);
+  Schedule(clock_->Now());
+}
+
+void SimulatedQuartcPacketTransport::AcceptPacket(
+    std::unique_ptr<Packet> packet) {
+  // Simulated switches broadcast packets to all ports if the cannot determine
+  // the recipient, so we need to drop packets that aren't intended for us.
+  if (packet->destination != name()) {
+    return;
+  }
+
+  if (delegate_) {
+    delegate_->OnTransportReceived(packet->contents.data(), packet->size);
+  }
+}
+
+void SimulatedQuartcPacketTransport::OnPacketDequeued() {
+  if (delegate_ && writable_) {
+    delegate_->OnTransportCanWrite();
+  }
+}
+
+void SimulatedQuartcPacketTransport::Act() {
+  if (delegate_ && writable_) {
+    delegate_->OnTransportCanWrite();
+  }
+}
+
+void SimulatedQuartcPacketTransport::SetWritable(bool writable) {
+  writable_ = writable;
+  if (writable_) {
+    // May need to call |Delegate::OnTransportCanWrite|.
+    Schedule(clock_->Now());
+  }
+}
+
+}  // namespace simulator
+}  // namespace quic
diff --git a/quic/quartc/simulated_packet_transport.h b/quic/quartc/simulated_packet_transport.h
new file mode 100644
index 0000000..1b190a0
--- /dev/null
+++ b/quic/quartc/simulated_packet_transport.h
@@ -0,0 +1,83 @@
+// 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_QUARTC_SIMULATED_PACKET_TRANSPORT_H_
+#define QUICHE_QUIC_QUARTC_SIMULATED_PACKET_TRANSPORT_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/port.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/queue.h"
+
+namespace quic {
+namespace simulator {
+
+// Simulated implementation of QuartcPacketTransport.  This packet transport
+// implementation connects Quartc to a QUIC simulator's network fabric.
+// Assumes that its caller and delegate run on the same thread as the network
+// simulation and therefore require no additional synchronization.
+class SimulatedQuartcPacketTransport : public Endpoint,
+                                       public QuartcPacketTransport,
+                                       public UnconstrainedPortInterface,
+                                       public Queue::ListenerInterface {
+ public:
+  SimulatedQuartcPacketTransport(Simulator* simulator,
+                                 const QuicString& name,
+                                 const QuicString& peer_name,
+                                 QuicByteCount queue_capacity);
+
+  // QuartcPacketTransport methods.
+  int Write(const char* buffer,
+            size_t buf_len,
+            const PacketInfo& info) override;
+  void SetDelegate(Delegate* delegate) override;
+
+  // Simulation methods below.  These are implementation details.
+
+  // Endpoint methods.  Called by the simulation to connect the transport.
+  UnconstrainedPortInterface* GetRxPort() override;
+  void SetTxPort(ConstrainedPortInterface* port) override;
+
+  // UnconstrainedPortInterface method.  Called by the simulation to deliver a
+  // packet to this transport.
+  void AcceptPacket(std::unique_ptr<Packet> packet) override;
+
+  // Queue::ListenerInterface method.  Called when the internal egress queue has
+  // dispatched a packet and may have room for more.
+  void OnPacketDequeued() override;
+
+  // Actor method.  The transport schedules this to run when the delegate is set
+  // in order to trigger an initial call to |Delegate::OnTransportCanWrite()|.
+  // (The Quartc packet writer starts in a blocked state and needs an initial
+  // callback to unblock it.)
+  void Act() override;
+
+  // Changes whether the transport is writable.  If |writable| is false, the
+  // transport will reject calls to |Write| and will not call
+  // |Delegate::OnTransportCanWrite|.  If |writable| is true, the transport will
+  // allow calls to |Write| and will call |Delegate::OnTransportCanWrite|
+  // whenever it is able to write another packet.
+  void SetWritable(bool writable);
+
+  // Last packet number sent over this simulated transport.
+  // TODO(b/112561077):  Reorganize tests so that this method can be deleted.
+  // This exists purely for use by quartc_session_test.cc, to test that the
+  // packet writer passes packet numbers to the transport.
+  QuicPacketNumber last_packet_number() { return last_packet_number_; }
+
+ private:
+  QuicString peer_name_;
+  Delegate* delegate_ = nullptr;
+  Queue egress_queue_;
+  QuicPacketNumber last_packet_number_;
+
+  // Controls whether the transport is considered to be writable.  Used to
+  // simulate behavior that arises when the transport is blocked.
+  bool writable_ = true;
+};
+
+}  // namespace simulator
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_SIMULATED_PACKET_TRANSPORT_H_
diff --git a/quic/quartc/simulated_packet_transport_test.cc b/quic/quartc/simulated_packet_transport_test.cc
new file mode 100644
index 0000000..3fbd18b
--- /dev/null
+++ b/quic/quartc/simulated_packet_transport_test.cc
@@ -0,0 +1,165 @@
+// 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 "net/third_party/quiche/src/quic/quartc/simulated_packet_transport.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/switch.h"
+
+namespace quic {
+namespace simulator {
+namespace {
+
+using ::testing::ElementsAre;
+
+const QuicBandwidth kDefaultBandwidth =
+    QuicBandwidth::FromKBitsPerSecond(10 * 1000);
+const QuicTime::Delta kDefaultPropagationDelay =
+    QuicTime::Delta::FromMilliseconds(20);
+const QuicByteCount kDefaultBdp = kDefaultBandwidth * kDefaultPropagationDelay;
+const QuicByteCount kDefaultPacketSize = 1200;
+const QuicPacketCount kDefaultQueueLength = 10;
+
+class FakeDelegate : public QuartcPacketTransport::Delegate {
+ public:
+  explicit FakeDelegate(QuartcPacketTransport* transport)
+      : transport_(transport) {
+    transport_->SetDelegate(this);
+  }
+
+  ~FakeDelegate() { transport_->SetDelegate(nullptr); }
+
+  void OnTransportCanWrite() override {
+    while (!packets_to_send_.empty()) {
+      const QuicString& packet = packets_to_send_.front();
+      if (transport_->Write(packet.data(), packet.size(),
+                            QuartcPacketTransport::PacketInfo()) <
+          static_cast<int>(packet.size())) {
+        ++write_blocked_count_;
+        return;
+      }
+      packets_to_send_.pop();
+    }
+  }
+
+  void OnTransportReceived(const char* data, size_t data_len) override {
+    packets_received_.emplace_back(data, data_len);
+  }
+
+  void AddPacketToSend(const QuicString& packet) {
+    packets_to_send_.push(packet);
+  }
+
+  size_t packets_to_send() { return packets_to_send_.size(); }
+  const std::vector<QuicString>& packets_received() {
+    return packets_received_;
+  }
+  int write_blocked_count() { return write_blocked_count_; }
+
+ private:
+  QuartcPacketTransport* const transport_ = nullptr;
+  std::queue<QuicString> packets_to_send_;
+  std::vector<QuicString> packets_received_;
+  int write_blocked_count_ = 0;
+};
+
+class SimulatedPacketTransportTest : public QuicTest {
+ protected:
+  SimulatedPacketTransportTest()
+      : switch_(&simulator_, "Switch", /*port_count=*/8, 2 * kDefaultBdp),
+        client_(&simulator_,
+                "sender",
+                "receiver",
+                kDefaultQueueLength * kDefaultPacketSize),
+        server_(&simulator_,
+                "receiver",
+                "sender",
+                kDefaultQueueLength * kDefaultPacketSize),
+        client_link_(&client_,
+                     switch_.port(1),
+                     kDefaultBandwidth,
+                     kDefaultPropagationDelay),
+        server_link_(&server_,
+                     switch_.port(2),
+                     kDefaultBandwidth,
+                     kDefaultPropagationDelay),
+        client_delegate_(&client_),
+        server_delegate_(&server_) {}
+
+  Simulator simulator_;
+  Switch switch_;
+
+  SimulatedQuartcPacketTransport client_;
+  SimulatedQuartcPacketTransport server_;
+
+  SymmetricLink client_link_;
+  SymmetricLink server_link_;
+
+  FakeDelegate client_delegate_;
+  FakeDelegate server_delegate_;
+};
+
+TEST_F(SimulatedPacketTransportTest, OneWayTransmission) {
+  QuicString packet_1(kDefaultPacketSize, 'a');
+  QuicString packet_2(kDefaultPacketSize, 'b');
+  client_delegate_.AddPacketToSend(packet_1);
+  client_delegate_.AddPacketToSend(packet_2);
+
+  simulator_.RunUntil(
+      [this] { return client_delegate_.packets_to_send() == 0; });
+  simulator_.RunFor(3 * kDefaultPropagationDelay);
+
+  EXPECT_THAT(server_delegate_.packets_received(),
+              ElementsAre(packet_1, packet_2));
+  EXPECT_THAT(client_delegate_.packets_received(), ElementsAre());
+}
+
+TEST_F(SimulatedPacketTransportTest, TwoWayTransmission) {
+  QuicString packet_1(kDefaultPacketSize, 'a');
+  QuicString packet_2(kDefaultPacketSize, 'b');
+  QuicString packet_3(kDefaultPacketSize, 'c');
+  QuicString packet_4(kDefaultPacketSize, 'd');
+
+  client_delegate_.AddPacketToSend(packet_1);
+  client_delegate_.AddPacketToSend(packet_2);
+  server_delegate_.AddPacketToSend(packet_3);
+  server_delegate_.AddPacketToSend(packet_4);
+
+  simulator_.RunUntil(
+      [this] { return client_delegate_.packets_to_send() == 0; });
+  simulator_.RunUntil(
+      [this] { return server_delegate_.packets_to_send() == 0; });
+  simulator_.RunFor(3 * kDefaultPropagationDelay);
+
+  EXPECT_THAT(server_delegate_.packets_received(),
+              ElementsAre(packet_1, packet_2));
+  EXPECT_THAT(client_delegate_.packets_received(),
+              ElementsAre(packet_3, packet_4));
+}
+
+TEST_F(SimulatedPacketTransportTest, TestWriteBlocked) {
+  // Add 10 packets beyond what fits in the egress queue.
+  std::vector<QuicString> packets;
+  for (unsigned int i = 0; i < kDefaultQueueLength + 10; ++i) {
+    packets.push_back(QuicString(kDefaultPacketSize, 'a' + i));
+    client_delegate_.AddPacketToSend(packets.back());
+  }
+
+  simulator_.RunUntil(
+      [this] { return client_delegate_.packets_to_send() == 0; });
+  simulator_.RunFor(3 * kDefaultPropagationDelay);
+
+  // Each of the 10 packets in excess of the sender's egress queue length will
+  // block the first time |client_delegate_| tries to write them.
+  EXPECT_EQ(client_delegate_.write_blocked_count(), 10);
+  EXPECT_EQ(server_delegate_.packets_received(), packets);
+}
+
+}  // namespace
+}  // namespace simulator
+}  // namespace quic