Project import generated by Copybara.

PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
new file mode 100644
index 0000000..c26ffa6
--- /dev/null
+++ b/quic/core/http/end_to_end_test.cc
@@ -0,0 +1,3847 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstddef>
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_framer.h"
+#include "net/third_party/quiche/src/quic/core/quic_packet_creator.h"
+#include "net/third_party/quiche/src/quic/core/quic_packet_writer_wrapper.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_error_code_wrappers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_port_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_sleep.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.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"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test_loopback.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/quic/platform/impl/quic_socket_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/bad_packet_writer.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/packet_dropping_test_writer.h"
+#include "net/third_party/quiche/src/quic/test_tools/packet_reordering_writer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_client_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_dispatcher_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_server_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_id_manager_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_sequencer_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_client.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_server.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/server_thread.h"
+#include "net/third_party/quiche/src/quic/tools/quic_backend_response.h"
+#include "net/third_party/quiche/src/quic/tools/quic_client.h"
+#include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h"
+#include "net/third_party/quiche/src/quic/tools/quic_server.h"
+#include "net/third_party/quiche/src/quic/tools/quic_simple_client_stream.h"
+#include "net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h"
+
+using spdy::kV3LowestPriority;
+using spdy::SETTINGS_MAX_HEADER_LIST_SIZE;
+using spdy::SpdyFramer;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdySerializedFrame;
+using spdy::SpdySettingsIR;
+
+namespace quic {
+namespace test {
+namespace {
+
+const char kFooResponseBody[] = "Artichoke hearts make me happy.";
+const char kBarResponseBody[] = "Palm hearts are pretty delicious, also.";
+const float kSessionToStreamRatio = 1.5;
+
+// Run all tests with the cross products of all versions.
+struct TestParams {
+  TestParams(const ParsedQuicVersionVector& client_supported_versions,
+             const ParsedQuicVersionVector& server_supported_versions,
+             ParsedQuicVersion negotiated_version,
+             bool client_supports_stateless_rejects,
+             bool server_uses_stateless_rejects_if_peer_supported,
+             QuicTag congestion_control_tag,
+             bool use_cheap_stateless_reject)
+      : client_supported_versions(client_supported_versions),
+        server_supported_versions(server_supported_versions),
+        negotiated_version(negotiated_version),
+        client_supports_stateless_rejects(client_supports_stateless_rejects),
+        server_uses_stateless_rejects_if_peer_supported(
+            server_uses_stateless_rejects_if_peer_supported),
+        congestion_control_tag(congestion_control_tag),
+        use_cheap_stateless_reject(use_cheap_stateless_reject) {}
+
+  friend std::ostream& operator<<(std::ostream& os, const TestParams& p) {
+    os << "{ server_supported_versions: "
+       << ParsedQuicVersionVectorToString(p.server_supported_versions);
+    os << " client_supported_versions: "
+       << ParsedQuicVersionVectorToString(p.client_supported_versions);
+    os << " negotiated_version: "
+       << ParsedQuicVersionToString(p.negotiated_version);
+    os << " client_supports_stateless_rejects: "
+       << p.client_supports_stateless_rejects;
+    os << " server_uses_stateless_rejects_if_peer_supported: "
+       << p.server_uses_stateless_rejects_if_peer_supported;
+    os << " congestion_control_tag: "
+       << QuicTagToString(p.congestion_control_tag);
+    os << " use_cheap_stateless_reject: " << p.use_cheap_stateless_reject
+       << " }";
+    return os;
+  }
+
+  ParsedQuicVersionVector client_supported_versions;
+  ParsedQuicVersionVector server_supported_versions;
+  ParsedQuicVersion negotiated_version;
+  bool client_supports_stateless_rejects;
+  bool server_uses_stateless_rejects_if_peer_supported;
+  QuicTag congestion_control_tag;
+  bool use_cheap_stateless_reject;
+};
+
+// Constructs various test permutations.
+std::vector<TestParams> GetTestParams(bool use_tls_handshake,
+                                      bool test_stateless_rejects) {
+  QuicFlagSaver flags;
+  // Divide the versions into buckets in which the intra-frame format
+  // is compatible. When clients encounter QUIC version negotiation
+  // they simply retransmit all packets using the new version's
+  // QUIC framing. However, they are unable to change the intra-frame
+  // layout (for example to change HTTP/2 headers to SPDY/3, or a change in the
+  // handshake protocol). So these tests need to ensure that clients are never
+  // attempting to do 0-RTT across incompatible versions. Chromium only
+  // supports a single version at a time anyway. :)
+  FLAGS_quic_supports_tls_handshake = use_tls_handshake;
+  ParsedQuicVersionVector all_supported_versions =
+      FilterSupportedVersions(AllSupportedVersions());
+
+  // Buckets are separated by versions: versions prior to QUIC_VERSION_47 use
+  // STREAM frames for the handshake, and only have QUIC crypto as the handshake
+  // protocol. Version 47 and greater use CRYPTO frames for the handshake, and
+  // must also be split based on the handshake protocol. If the handshake
+  // protocol (QUIC crypto or TLS) changes, the ClientHello/CHLO must be
+  // reconstructed for the correct protocol.
+  ParsedQuicVersionVector version_buckets[3];
+
+  for (const ParsedQuicVersion& version : all_supported_versions) {
+    if (version.transport_version < QUIC_VERSION_47) {
+      version_buckets[0].push_back(version);
+    } else if (version.handshake_protocol == PROTOCOL_QUIC_CRYPTO) {
+      version_buckets[1].push_back(version);
+    } else {
+      version_buckets[2].push_back(version);
+    }
+  }
+
+  // This must be kept in sync with the number of nested for-loops below as it
+  // is used to prune the number of tests that are run.
+  const int kMaxEnabledOptions = 4;
+  int max_enabled_options = 0;
+  std::vector<TestParams> params;
+  for (const QuicTag congestion_control_tag : {kRENO, kTBBR, kQBIC, kTPCC}) {
+    for (bool server_uses_stateless_rejects_if_peer_supported : {true, false}) {
+      for (bool client_supports_stateless_rejects : {true, false}) {
+        for (bool use_cheap_stateless_reject : {true, false}) {
+          int enabled_options = 0;
+          if (congestion_control_tag != kQBIC) {
+            ++enabled_options;
+          }
+          if (client_supports_stateless_rejects) {
+            ++enabled_options;
+          }
+          if (server_uses_stateless_rejects_if_peer_supported) {
+            ++enabled_options;
+          }
+          if (use_cheap_stateless_reject) {
+            ++enabled_options;
+          }
+          CHECK_GE(kMaxEnabledOptions, enabled_options);
+          if (enabled_options > max_enabled_options) {
+            max_enabled_options = enabled_options;
+          }
+
+          // Run tests with no options, a single option, or all the
+          // options enabled to avoid a combinatorial explosion.
+          if (enabled_options > 1 && enabled_options < kMaxEnabledOptions) {
+            continue;
+          }
+
+          // There are many stateless reject combinations, so don't test them
+          // unless requested.
+          if ((server_uses_stateless_rejects_if_peer_supported ||
+               client_supports_stateless_rejects ||
+               use_cheap_stateless_reject) &&
+              !test_stateless_rejects) {
+            continue;
+          }
+
+          for (const ParsedQuicVersionVector& client_versions :
+               version_buckets) {
+            if (FilterSupportedVersions(client_versions).empty()) {
+              continue;
+            }
+            // Add an entry for server and client supporting all
+            // versions.
+            params.push_back(TestParams(
+                client_versions, all_supported_versions,
+                client_versions.front(), client_supports_stateless_rejects,
+                server_uses_stateless_rejects_if_peer_supported,
+                congestion_control_tag, use_cheap_stateless_reject));
+
+            // Run version negotiation tests tests with no options, or
+            // all the options enabled to avoid a combinatorial
+            // explosion.
+            if (enabled_options > 1 && enabled_options < kMaxEnabledOptions) {
+              continue;
+            }
+
+            // Test client supporting all versions and server supporting
+            // 1 version. Simulate an old server and exercise version
+            // downgrade in the client. Protocol negotiation should
+            // occur.  Skip the i = 0 case because it is essentially the
+            // same as the default case.
+            for (size_t i = 1; i < client_versions.size(); ++i) {
+              ParsedQuicVersionVector server_supported_versions;
+              server_supported_versions.push_back(client_versions[i]);
+              if (FilterSupportedVersions(server_supported_versions).empty()) {
+                continue;
+              }
+              params.push_back(TestParams(
+                  client_versions, server_supported_versions,
+                  server_supported_versions.front(),
+                  client_supports_stateless_rejects,
+                  server_uses_stateless_rejects_if_peer_supported,
+                  congestion_control_tag, use_cheap_stateless_reject));
+            }  // End of inner version loop.
+          }    // End of outer version loop.
+        }      // End of use_cheap_stateless_reject loop.
+      }        // End of client_supports_stateless_rejects loop.
+    }          // End of server_uses_stateless_rejects_if_peer_supported loop.
+  }            // End of congestion_control_tag loop.
+  CHECK_EQ(kMaxEnabledOptions, max_enabled_options);
+  return params;
+}
+
+class ServerDelegate : public PacketDroppingTestWriter::Delegate {
+ public:
+  explicit ServerDelegate(QuicDispatcher* dispatcher)
+      : dispatcher_(dispatcher) {}
+  ~ServerDelegate() override = default;
+  void OnCanWrite() override { dispatcher_->OnCanWrite(); }
+
+ private:
+  QuicDispatcher* dispatcher_;
+};
+
+class ClientDelegate : public PacketDroppingTestWriter::Delegate {
+ public:
+  explicit ClientDelegate(QuicClient* client) : client_(client) {}
+  ~ClientDelegate() override = default;
+  void OnCanWrite() override {
+    QuicEpollEvent event(EPOLLOUT);
+    client_->epoll_network_helper()->OnEvent(client_->GetLatestFD(), &event);
+  }
+
+ private:
+  QuicClient* client_;
+};
+
+class EndToEndTest : public QuicTestWithParam<TestParams> {
+ protected:
+  EndToEndTest()
+      : initialized_(false),
+        connect_to_server_on_initialize_(true),
+        server_address_(
+            QuicSocketAddress(TestLoopback(), QuicPickUnusedPortOrDie())),
+        server_hostname_("test.example.com"),
+        client_writer_(nullptr),
+        server_writer_(nullptr),
+        negotiated_version_(UnsupportedQuicVersion()),
+        chlo_multiplier_(0),
+        stream_factory_(nullptr),
+        support_server_push_(false),
+        override_connection_id_(nullptr) {
+    FLAGS_quic_supports_tls_handshake = true;
+    SetQuicRestartFlag(quic_no_server_conn_ver_negotiation2, true);
+    SetQuicReloadableFlag(quic_no_client_conn_ver_negotiation, true);
+    client_supported_versions_ = GetParam().client_supported_versions;
+    server_supported_versions_ = GetParam().server_supported_versions;
+    negotiated_version_ = GetParam().negotiated_version;
+
+    QUIC_LOG(INFO) << "Using Configuration: " << GetParam();
+
+    // Use different flow control windows for client/server.
+    client_config_.SetInitialStreamFlowControlWindowToSend(
+        2 * kInitialStreamFlowControlWindowForTest);
+    client_config_.SetInitialSessionFlowControlWindowToSend(
+        2 * kInitialSessionFlowControlWindowForTest);
+    server_config_.SetInitialStreamFlowControlWindowToSend(
+        3 * kInitialStreamFlowControlWindowForTest);
+    server_config_.SetInitialSessionFlowControlWindowToSend(
+        3 * kInitialSessionFlowControlWindowForTest);
+
+    // The default idle timeouts can be too strict when running on a busy
+    // machine.
+    const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(30);
+    client_config_.set_max_time_before_crypto_handshake(timeout);
+    client_config_.set_max_idle_time_before_crypto_handshake(timeout);
+    server_config_.set_max_time_before_crypto_handshake(timeout);
+    server_config_.set_max_idle_time_before_crypto_handshake(timeout);
+
+    AddToCache("/foo", 200, kFooResponseBody);
+    AddToCache("/bar", 200, kBarResponseBody);
+  }
+
+  ~EndToEndTest() override { QuicRecyclePort(server_address_.port()); }
+
+  virtual void CreateClientWithWriter() {
+    client_.reset(CreateQuicClient(client_writer_));
+  }
+
+  QuicTestClient* CreateQuicClient(QuicPacketWriterWrapper* writer) {
+    QuicTestClient* client =
+        new QuicTestClient(server_address_, server_hostname_, client_config_,
+                           client_supported_versions_,
+                           crypto_test_utils::ProofVerifierForTesting());
+    client->UseWriter(writer);
+    if (!pre_shared_key_client_.empty()) {
+      client->client()->SetPreSharedKey(pre_shared_key_client_);
+    }
+    if (override_connection_id_ != nullptr) {
+      client->UseConnectionId(*override_connection_id_);
+    }
+    client->Connect();
+    return client;
+  }
+
+  void set_smaller_flow_control_receive_window() {
+    const uint32_t kClientIFCW = 64 * 1024;
+    const uint32_t kServerIFCW = 1024 * 1024;
+    set_client_initial_stream_flow_control_receive_window(kClientIFCW);
+    set_client_initial_session_flow_control_receive_window(
+        kSessionToStreamRatio * kClientIFCW);
+    set_server_initial_stream_flow_control_receive_window(kServerIFCW);
+    set_server_initial_session_flow_control_receive_window(
+        kSessionToStreamRatio * kServerIFCW);
+  }
+
+  void set_client_initial_stream_flow_control_receive_window(uint32_t window) {
+    CHECK(client_ == nullptr);
+    QUIC_DLOG(INFO) << "Setting client initial stream flow control window: "
+                    << window;
+    client_config_.SetInitialStreamFlowControlWindowToSend(window);
+  }
+
+  void set_client_initial_session_flow_control_receive_window(uint32_t window) {
+    CHECK(client_ == nullptr);
+    QUIC_DLOG(INFO) << "Setting client initial session flow control window: "
+                    << window;
+    client_config_.SetInitialSessionFlowControlWindowToSend(window);
+  }
+
+  void set_server_initial_stream_flow_control_receive_window(uint32_t window) {
+    CHECK(server_thread_ == nullptr);
+    QUIC_DLOG(INFO) << "Setting server initial stream flow control window: "
+                    << window;
+    server_config_.SetInitialStreamFlowControlWindowToSend(window);
+  }
+
+  void set_server_initial_session_flow_control_receive_window(uint32_t window) {
+    CHECK(server_thread_ == nullptr);
+    QUIC_DLOG(INFO) << "Setting server initial session flow control window: "
+                    << window;
+    server_config_.SetInitialSessionFlowControlWindowToSend(window);
+  }
+
+  const QuicSentPacketManager* GetSentPacketManagerFromFirstServerSession() {
+    return &GetServerConnection()->sent_packet_manager();
+  }
+
+  QuicConnection* GetServerConnection() {
+    return GetServerSession()->connection();
+  }
+
+  QuicSession* GetServerSession() {
+    QuicDispatcher* dispatcher =
+        QuicServerPeer::GetDispatcher(server_thread_->server());
+    EXPECT_EQ(1u, dispatcher->session_map().size());
+    return dispatcher->session_map().begin()->second.get();
+  }
+
+  bool Initialize() {
+    QuicTagVector copt;
+    server_config_.SetConnectionOptionsToSend(copt);
+    copt = client_extra_copts_;
+
+    // TODO(nimia): Consider setting the congestion control algorithm for the
+    // client as well according to the test parameter.
+    copt.push_back(GetParam().congestion_control_tag);
+    if (GetParam().congestion_control_tag == kTPCC &&
+        GetQuicReloadableFlag(quic_enable_pcc3)) {
+      copt.push_back(kTPCC);
+    }
+
+    if (GetParam().client_supports_stateless_rejects) {
+      copt.push_back(kSREJ);
+    }
+    client_config_.SetConnectionOptionsToSend(copt);
+
+    // Start the server first, because CreateQuicClient() attempts
+    // to connect to the server.
+    StartServer();
+
+    if (!connect_to_server_on_initialize_) {
+      initialized_ = true;
+      return true;
+    }
+
+    CreateClientWithWriter();
+    static QuicEpollEvent event(EPOLLOUT);
+    if (client_writer_ != nullptr) {
+      client_writer_->Initialize(
+          QuicConnectionPeer::GetHelper(
+              client_->client()->client_session()->connection()),
+          QuicConnectionPeer::GetAlarmFactory(
+              client_->client()->client_session()->connection()),
+          QuicMakeUnique<ClientDelegate>(client_->client()));
+    }
+    initialized_ = true;
+    return client_->client()->connected();
+  }
+
+  void SetUp() override {
+    // The ownership of these gets transferred to the QuicPacketWriterWrapper
+    // when Initialize() is executed.
+    client_writer_ = new PacketDroppingTestWriter();
+    server_writer_ = new PacketDroppingTestWriter();
+  }
+
+  void TearDown() override {
+    ASSERT_TRUE(initialized_) << "You must call Initialize() in every test "
+                              << "case. Otherwise, your test will leak memory.";
+    StopServer();
+  }
+
+  void StartServer() {
+    SetQuicReloadableFlag(quic_use_cheap_stateless_rejects,
+                          GetParam().use_cheap_stateless_reject);
+
+    uint8_t connection_id_length = override_connection_id_ != nullptr
+                                       ? override_connection_id_->length()
+                                       : kQuicDefaultConnectionIdLength;
+    auto* test_server =
+        new QuicTestServer(crypto_test_utils::ProofSourceForTesting(),
+                           server_config_, server_supported_versions_,
+                           &memory_cache_backend_, connection_id_length);
+    server_thread_ = QuicMakeUnique<ServerThread>(test_server, server_address_);
+    if (chlo_multiplier_ != 0) {
+      server_thread_->server()->SetChloMultiplier(chlo_multiplier_);
+    }
+    if (!pre_shared_key_server_.empty()) {
+      server_thread_->server()->SetPreSharedKey(pre_shared_key_server_);
+    }
+    server_thread_->Initialize();
+    QuicDispatcher* dispatcher =
+        QuicServerPeer::GetDispatcher(server_thread_->server());
+    QuicDispatcherPeer::UseWriter(dispatcher, server_writer_);
+
+    SetQuicReloadableFlag(
+        enable_quic_stateless_reject_support,
+        GetParam().server_uses_stateless_rejects_if_peer_supported);
+
+    server_writer_->Initialize(QuicDispatcherPeer::GetHelper(dispatcher),
+                               QuicDispatcherPeer::GetAlarmFactory(dispatcher),
+                               QuicMakeUnique<ServerDelegate>(dispatcher));
+    if (stream_factory_ != nullptr) {
+      static_cast<QuicTestServer*>(server_thread_->server())
+          ->SetSpdyStreamFactory(stream_factory_);
+    }
+
+    server_thread_->Start();
+  }
+
+  void StopServer() {
+    if (server_thread_) {
+      server_thread_->Quit();
+      server_thread_->Join();
+    }
+  }
+
+  void AddToCache(QuicStringPiece path,
+                  int response_code,
+                  QuicStringPiece body) {
+    memory_cache_backend_.AddSimpleResponse(server_hostname_, path,
+                                            response_code, body);
+  }
+
+  void SetPacketLossPercentage(int32_t loss) {
+    client_writer_->set_fake_packet_loss_percentage(loss);
+    server_writer_->set_fake_packet_loss_percentage(loss);
+  }
+
+  void SetPacketSendDelay(QuicTime::Delta delay) {
+    client_writer_->set_fake_packet_delay(delay);
+    server_writer_->set_fake_packet_delay(delay);
+  }
+
+  void SetReorderPercentage(int32_t reorder) {
+    client_writer_->set_fake_reorder_percentage(reorder);
+    server_writer_->set_fake_reorder_percentage(reorder);
+  }
+
+  // Verifies that the client and server connections were both free of packets
+  // being discarded, based on connection stats.
+  // Calls server_thread_ Pause() and Resume(), which may only be called once
+  // per test.
+  void VerifyCleanConnection(bool had_packet_loss) {
+    QuicConnectionStats client_stats =
+        client_->client()->client_session()->connection()->GetStats();
+    // TODO(ianswett): Determine why this becomes even more flaky with BBR
+    // enabled.  b/62141144
+    if (!had_packet_loss && !GetQuicReloadableFlag(quic_default_to_bbr)) {
+      EXPECT_EQ(0u, client_stats.packets_lost);
+    }
+    EXPECT_EQ(0u, client_stats.packets_discarded);
+    // When doing 0-RTT with stateless rejects, the encrypted requests cause
+    // a retranmission of the SREJ packets which are dropped by the client.
+    // When client starts with an unsupported version, the version negotiation
+    // packet sent by server for the old connection (respond for the connection
+    // close packet) will be dropped by the client.
+    if (!BothSidesSupportStatelessRejects() &&
+        !ServerSendsVersionNegotiation()) {
+      EXPECT_EQ(0u, client_stats.packets_dropped);
+    }
+    if (!ClientSupportsIetfQuicNotSupportedByServer()) {
+      // In this case, if client sends 0-RTT POST with v99, receives IETF
+      // version negotiation packet and speaks a GQUIC version. Server processes
+      // this connection in time wait list and keeps sending IETF version
+      // negotiation packet for incoming packets. But these version negotiation
+      // packets cannot be processed by the client speaking GQUIC.
+      EXPECT_EQ(client_stats.packets_received, client_stats.packets_processed);
+    }
+
+    const int num_expected_stateless_rejects =
+        (BothSidesSupportStatelessRejects() &&
+         client_->client()->client_session()->GetNumSentClientHellos() > 0)
+            ? 1
+            : 0;
+    EXPECT_EQ(num_expected_stateless_rejects,
+              client_->client()->num_stateless_rejects_received());
+
+    server_thread_->Pause();
+    QuicConnectionStats server_stats = GetServerConnection()->GetStats();
+    if (!had_packet_loss) {
+      EXPECT_EQ(0u, server_stats.packets_lost);
+    }
+    EXPECT_EQ(0u, server_stats.packets_discarded);
+    // TODO(ianswett): Restore the check for packets_dropped equals 0.
+    // The expect for packets received is equal to packets processed fails
+    // due to version negotiation packets.
+    server_thread_->Resume();
+  }
+
+  bool BothSidesSupportStatelessRejects() {
+    return (GetParam().server_uses_stateless_rejects_if_peer_supported &&
+            GetParam().client_supports_stateless_rejects);
+  }
+
+  // Client supports IETF QUIC, while it is not supported by server.
+  bool ClientSupportsIetfQuicNotSupportedByServer() {
+    return GetParam().client_supported_versions[0].transport_version >
+               QUIC_VERSION_43 &&
+           FilterSupportedVersions(GetParam().server_supported_versions)[0]
+                   .transport_version <= QUIC_VERSION_43;
+  }
+
+  // Returns true when client starts with an unsupported version, and client
+  // closes connection when version negotiation is received.
+  bool ServerSendsVersionNegotiation() {
+    return GetQuicReloadableFlag(quic_no_client_conn_ver_negotiation) &&
+           GetParam().client_supported_versions[0] !=
+               GetParam().negotiated_version;
+  }
+
+  bool SupportsIetfQuicWithTls(ParsedQuicVersion version) {
+    return version.transport_version > QUIC_VERSION_43 &&
+           version.handshake_protocol == PROTOCOL_TLS1_3;
+  }
+
+  void ExpectFlowControlsSynced(QuicFlowController* client,
+                                QuicFlowController* server) {
+    EXPECT_EQ(QuicFlowControllerPeer::SendWindowSize(client),
+              QuicFlowControllerPeer::ReceiveWindowSize(server));
+    EXPECT_EQ(QuicFlowControllerPeer::ReceiveWindowSize(client),
+              QuicFlowControllerPeer::SendWindowSize(server));
+  }
+
+  // Must be called before Initialize to have effect.
+  void SetSpdyStreamFactory(QuicTestServer::StreamFactory* factory) {
+    stream_factory_ = factory;
+  }
+
+  QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+    return GetNthClientInitiatedBidirectionalStreamId(
+        client_->client()->client_session()->connection()->transport_version(),
+        n);
+  }
+
+  QuicStreamId GetNthServerInitiatedBidirectionalId(int n) {
+    return GetNthServerInitiatedBidirectionalStreamId(
+        client_->client()->client_session()->connection()->transport_version(),
+        n);
+  }
+
+  ScopedEnvironmentForThreads environment_;
+  bool initialized_;
+  // If true, the Initialize() function will create |client_| and starts to
+  // connect to the server.
+  // Default is true.
+  bool connect_to_server_on_initialize_;
+  QuicSocketAddress server_address_;
+  QuicString server_hostname_;
+  QuicMemoryCacheBackend memory_cache_backend_;
+  std::unique_ptr<ServerThread> server_thread_;
+  std::unique_ptr<QuicTestClient> client_;
+  PacketDroppingTestWriter* client_writer_;
+  PacketDroppingTestWriter* server_writer_;
+  QuicConfig client_config_;
+  QuicConfig server_config_;
+  ParsedQuicVersionVector client_supported_versions_;
+  ParsedQuicVersionVector server_supported_versions_;
+  QuicTagVector client_extra_copts_;
+  ParsedQuicVersion negotiated_version_;
+  size_t chlo_multiplier_;
+  QuicTestServer::StreamFactory* stream_factory_;
+  bool support_server_push_;
+  QuicString pre_shared_key_client_;
+  QuicString pre_shared_key_server_;
+  QuicConnectionId* override_connection_id_;
+};
+
+// Run all end to end tests with all supported versions.
+INSTANTIATE_TEST_SUITE_P(EndToEndTests,
+                         EndToEndTest,
+                         ::testing::ValuesIn(GetTestParams(false, false)));
+
+class EndToEndTestWithTls : public EndToEndTest {};
+
+INSTANTIATE_TEST_SUITE_P(EndToEndTestsWithTls,
+                         EndToEndTestWithTls,
+                         ::testing::ValuesIn(GetTestParams(true, false)));
+
+class EndToEndTestWithStatelessReject : public EndToEndTest {};
+
+INSTANTIATE_TEST_SUITE_P(WithStatelessReject,
+                         EndToEndTestWithStatelessReject,
+                         ::testing::ValuesIn(GetTestParams(false, true)));
+
+TEST_P(EndToEndTestWithTls, HandshakeSuccessful) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+  // There have been occasions where it seemed that negotiated_version_ and the
+  // version in the connection are not in sync. If it is happening, it has not
+  // been recreatable; this assert is here just to check and raise a flag if it
+  // happens.
+  ASSERT_EQ(
+      client_->client()->client_session()->connection()->transport_version(),
+      negotiated_version_.transport_version);
+
+  QuicCryptoStream* crypto_stream = QuicSessionPeer::GetMutableCryptoStream(
+      client_->client()->client_session());
+  QuicStreamSequencer* sequencer = QuicStreamPeer::sequencer(crypto_stream);
+  EXPECT_FALSE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+  server_thread_->Pause();
+  crypto_stream = QuicSessionPeer::GetMutableCryptoStream(GetServerSession());
+  sequencer = QuicStreamPeer::sequencer(crypto_stream);
+  EXPECT_FALSE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+}
+
+TEST_P(EndToEndTestWithStatelessReject, SimpleRequestResponseStatless) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  int expected_num_client_hellos = 2;
+  if (ServerSendsVersionNegotiation()) {
+    ++expected_num_client_hellos;
+    if (BothSidesSupportStatelessRejects()) {
+      ++expected_num_client_hellos;
+    }
+  }
+  EXPECT_EQ(expected_num_client_hellos,
+            client_->client()->GetNumSentClientHellos());
+}
+
+TEST_P(EndToEndTest, SimpleRequestResponse) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  int expected_num_client_hellos = 2;
+  if (ServerSendsVersionNegotiation()) {
+    ++expected_num_client_hellos;
+    if (BothSidesSupportStatelessRejects()) {
+      ++expected_num_client_hellos;
+    }
+  }
+  EXPECT_EQ(expected_num_client_hellos,
+            client_->client()->GetNumSentClientHellos());
+}
+
+TEST_P(EndToEndTest, SimpleRequestResponseZeroConnectionID) {
+  QuicConnectionId connection_id = QuicUtils::CreateZeroConnectionId(
+      GetParam().negotiated_version.transport_version);
+  override_connection_id_ = &connection_id;
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  int expected_num_client_hellos = 2;
+  if (ServerSendsVersionNegotiation()) {
+    ++expected_num_client_hellos;
+    if (BothSidesSupportStatelessRejects()) {
+      ++expected_num_client_hellos;
+    }
+  }
+  EXPECT_EQ(expected_num_client_hellos,
+            client_->client()->GetNumSentClientHellos());
+  EXPECT_EQ(client_->client()->client_session()->connection()->connection_id(),
+            QuicUtils::CreateZeroConnectionId(
+                GetParam().negotiated_version.transport_version));
+}
+
+TEST_P(EndToEndTest, SimpleRequestResponseWithLargeReject) {
+  chlo_multiplier_ = 1;
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(4, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+  }
+}
+
+TEST_P(EndToEndTestWithTls, SimpleRequestResponsev6) {
+  server_address_ =
+      QuicSocketAddress(QuicIpAddress::Loopback6(), server_address_.port());
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, SeparateFinPacket) {
+  ASSERT_TRUE(Initialize());
+
+  // Send a request in two parts: the request and then an empty packet with FIN.
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  client_->SendMessage(headers, "", /*fin=*/false);
+  client_->SendData("", true);
+  client_->WaitForResponse();
+  EXPECT_EQ(kFooResponseBody, client_->response_body());
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Now do the same thing but with a content length.
+  headers["content-length"] = "3";
+  client_->SendMessage(headers, "", /*fin=*/false);
+  client_->SendData("foo", true);
+  client_->WaitForResponse();
+  EXPECT_EQ(kFooResponseBody, client_->response_body());
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, MultipleRequestResponse) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTest, MultipleRequestResponseZeroConnectionID) {
+  QuicConnectionId connection_id = QuicUtils::CreateZeroConnectionId(
+      GetParam().negotiated_version.transport_version);
+  override_connection_id_ = &connection_id;
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, MultipleStreams) {
+  // Verifies quic_test_client can track responses of all active streams.
+  ASSERT_TRUE(Initialize());
+
+  const int kNumRequests = 10;
+
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  headers["content-length"] = "3";
+
+  for (int i = 0; i < kNumRequests; ++i) {
+    client_->SendMessage(headers, "bar", /*fin=*/true);
+  }
+
+  while (kNumRequests > client_->num_responses()) {
+    client_->ClearPerRequestState();
+    client_->WaitForResponse();
+    EXPECT_EQ(kFooResponseBody, client_->response_body());
+    EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  }
+}
+
+TEST_P(EndToEndTestWithTls, MultipleClients) {
+  ASSERT_TRUE(Initialize());
+  std::unique_ptr<QuicTestClient> client2(CreateQuicClient(nullptr));
+
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  headers["content-length"] = "3";
+
+  client_->SendMessage(headers, "", /*fin=*/false);
+  client2->SendMessage(headers, "", /*fin=*/false);
+
+  client_->SendData("bar", true);
+  client_->WaitForResponse();
+  EXPECT_EQ(kFooResponseBody, client_->response_body());
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  client2->SendData("eep", true);
+  client2->WaitForResponse();
+  EXPECT_EQ(kFooResponseBody, client2->response_body());
+  EXPECT_EQ("200", client2->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, RequestOverMultiplePackets) {
+  // Send a large enough request to guarantee fragmentation.
+  QuicString huge_request =
+      "/some/path?query=" + QuicString(kMaxPacketSize, '.');
+  AddToCache(huge_request, 200, kBarResponseBody);
+
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest(huge_request));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, MultiplePacketsRandomOrder) {
+  // Send a large enough request to guarantee fragmentation.
+  QuicString huge_request =
+      "/some/path?query=" + QuicString(kMaxPacketSize, '.');
+  AddToCache(huge_request, 200, kBarResponseBody);
+
+  ASSERT_TRUE(Initialize());
+  SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+  SetReorderPercentage(50);
+
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest(huge_request));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, PostMissingBytes) {
+  ASSERT_TRUE(Initialize());
+
+  // Add a content length header with no body.
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  headers["content-length"] = "3";
+
+  // This should be detected as stream fin without complete request,
+  // triggering an error response.
+  client_->SendCustomSynchronousRequest(headers, "");
+  EXPECT_EQ(QuicSimpleServerStream::kErrorResponseBody,
+            client_->response_body());
+  EXPECT_EQ("500", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTest, LargePostNoPacketLoss) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // 1 MB body.
+  QuicString body(1024 * 1024, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  // TODO(ianswett): There should not be packet loss in this test, but on some
+  // platforms the receive buffer overflows.
+  VerifyCleanConnection(true);
+}
+
+TEST_P(EndToEndTest, LargePostNoPacketLoss1sRTT) {
+  ASSERT_TRUE(Initialize());
+  SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(1000));
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // 100 KB body.
+  QuicString body(100 * 1024, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  VerifyCleanConnection(false);
+}
+
+TEST_P(EndToEndTest, LargePostWithPacketLoss) {
+  if (!BothSidesSupportStatelessRejects()) {
+    // Connect with lower fake packet loss than we'd like to test.
+    // Until b/10126687 is fixed, losing handshake packets is pretty
+    // brutal.
+    // TODO(jokulik): Until we support redundant SREJ packets, don't
+    // drop handshake packets for stateless rejects.
+    SetPacketLossPercentage(5);
+  }
+  ASSERT_TRUE(Initialize());
+
+  // Wait for the server SHLO before upping the packet loss.
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  SetPacketLossPercentage(30);
+
+  // 10 KB body.
+  QuicString body(1024 * 10, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  VerifyCleanConnection(true);
+}
+
+// Regression test for b/80090281.
+TEST_P(EndToEndTest, LargePostWithPacketLossAndAlwaysBundleWindowUpdates) {
+  ASSERT_TRUE(Initialize());
+
+  // Wait for the server SHLO before upping the packet loss.
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  // Normally server only bundles a retransmittable frame once every other
+  // kMaxConsecutiveNonRetransmittablePackets ack-only packets. Setting the max
+  // to 0 to reliably reproduce b/80090281.
+  server_thread_->Schedule([this]() {
+    QuicConnectionPeer::SetMaxConsecutiveNumPacketsWithNoRetransmittableFrames(
+        GetServerConnection(), 0);
+  });
+
+  SetPacketLossPercentage(30);
+
+  // 10 KB body.
+  QuicString body(1024 * 10, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  VerifyCleanConnection(true);
+}
+
+TEST_P(EndToEndTest, LargePostWithPacketLossAndBlockedSocket) {
+  if (!BothSidesSupportStatelessRejects()) {
+    // Connect with lower fake packet loss than we'd like to test.  Until
+    // b/10126687 is fixed, losing handshake packets is pretty brutal.
+    // TODO(jokulik): Until we support redundant SREJ packets, don't
+    // drop handshake packets for stateless rejects.
+    SetPacketLossPercentage(5);
+  }
+  ASSERT_TRUE(Initialize());
+
+  // Wait for the server SHLO before upping the packet loss.
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  SetPacketLossPercentage(10);
+  client_writer_->set_fake_blocked_socket_percentage(10);
+
+  // 10 KB body.
+  QuicString body(1024 * 10, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+}
+
+TEST_P(EndToEndTest, LargePostNoPacketLossWithDelayAndReordering) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  // Both of these must be called when the writer is not actively used.
+  SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+  SetReorderPercentage(30);
+
+  // 1 MB body.
+  QuicString body(1024 * 1024, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+}
+
+TEST_P(EndToEndTest, LargePostZeroRTTFailure) {
+  // Send a request and then disconnect. This prepares the client to attempt
+  // a 0-RTT handshake for the next request.
+  ASSERT_TRUE(Initialize());
+
+  QuicString body(20480, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  // In the non-stateless case, the same session is used for both
+  // hellos, so the number of hellos sent on that session is 2.  In
+  // the stateless case, the first client session will be completely
+  // torn down after the reject.  The number of hellos on the latest
+  // session is 1.
+  const int expected_num_hellos_latest_session =
+      (BothSidesSupportStatelessRejects() && !ServerSendsVersionNegotiation())
+          ? 1
+          : 2;
+  EXPECT_EQ(expected_num_hellos_latest_session,
+            client_->client()->client_session()->GetNumSentClientHellos());
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  }
+
+  client_->Disconnect();
+
+  // The 0-RTT handshake should succeed.
+  client_->Connect();
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  ASSERT_TRUE(client_->client()->connected());
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+
+  EXPECT_EQ(1, client_->client()->client_session()->GetNumSentClientHellos());
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+  }
+
+  client_->Disconnect();
+
+  // Restart the server so that the 0-RTT handshake will take 1 RTT.
+  StopServer();
+  server_writer_ = new PacketDroppingTestWriter();
+  StartServer();
+
+  client_->Connect();
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  ASSERT_TRUE(client_->client()->connected());
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  // In the non-stateless case, the same session is used for both
+  // hellos, so the number of hellos sent on that session is 2.  In
+  // the stateless case, the first client session will be completely
+  // torn down after the reject.  The number of hellos sent on the
+  // latest session is 1.
+  EXPECT_EQ(expected_num_hellos_latest_session,
+            client_->client()->client_session()->GetNumSentClientHellos());
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  }
+
+  VerifyCleanConnection(false);
+}
+
+TEST_P(EndToEndTest, SynchronousRequestZeroRTTFailure) {
+  // Send a request and then disconnect. This prepares the client to attempt
+  // a 0-RTT handshake for the next request.
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  // In the non-stateless case, the same session is used for both
+  // hellos, so the number of hellos sent on that session is 2.  In
+  // the stateless case, the first client session will be completely
+  // torn down after the reject.  The number of hellos on that second
+  // latest session is 1.
+  const int expected_num_hellos_latest_session =
+      (BothSidesSupportStatelessRejects() && !ServerSendsVersionNegotiation())
+          ? 1
+          : 2;
+  EXPECT_EQ(expected_num_hellos_latest_session,
+            client_->client()->client_session()->GetNumSentClientHellos());
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  }
+
+  client_->Disconnect();
+
+  // The 0-RTT handshake should succeed.
+  client_->Connect();
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  ASSERT_TRUE(client_->client()->connected());
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+  EXPECT_EQ(1, client_->client()->client_session()->GetNumSentClientHellos());
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+  }
+
+  client_->Disconnect();
+
+  // Restart the server so that the 0-RTT handshake will take 1 RTT.
+  StopServer();
+  server_writer_ = new PacketDroppingTestWriter();
+  StartServer();
+
+  client_->Connect();
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  ASSERT_TRUE(client_->client()->connected());
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  // In the non-stateless case, the same session is used for both
+  // hellos, so the number of hellos sent on that session is 2.  In
+  // the stateless case, the first client session will be completely
+  // torn down after the reject.  The number of hellos sent on the
+  // latest session is 1.
+  EXPECT_EQ(expected_num_hellos_latest_session,
+            client_->client()->client_session()->GetNumSentClientHellos());
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  }
+
+  VerifyCleanConnection(false);
+}
+
+TEST_P(EndToEndTest, LargePostSynchronousRequest) {
+  // Send a request and then disconnect. This prepares the client to attempt
+  // a 0-RTT handshake for the next request.
+  ASSERT_TRUE(Initialize());
+
+  QuicString body(20480, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  // In the non-stateless case, the same session is used for both
+  // hellos, so the number of hellos sent on that session is 2.  In
+  // the stateless case, the first client session will be completely
+  // torn down after the reject.  The number of hellos on the latest
+  // session is 1.
+  const int expected_num_hellos_latest_session =
+      (BothSidesSupportStatelessRejects() && !ServerSendsVersionNegotiation())
+          ? 1
+          : 2;
+  EXPECT_EQ(expected_num_hellos_latest_session,
+            client_->client()->client_session()->GetNumSentClientHellos());
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  }
+
+  client_->Disconnect();
+
+  // The 0-RTT handshake should succeed.
+  client_->Connect();
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  ASSERT_TRUE(client_->client()->connected());
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+
+  EXPECT_EQ(1, client_->client()->client_session()->GetNumSentClientHellos());
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+  }
+
+  client_->Disconnect();
+
+  // Restart the server so that the 0-RTT handshake will take 1 RTT.
+  StopServer();
+  server_writer_ = new PacketDroppingTestWriter();
+  StartServer();
+
+  client_->Connect();
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  ASSERT_TRUE(client_->client()->connected());
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  // In the non-stateless case, the same session is used for both
+  // hellos, so the number of hellos sent on that session is 2.  In
+  // the stateless case, the first client session will be completely
+  // torn down after the reject.  The number of hellos sent on the
+  // latest session is 1.
+  EXPECT_EQ(expected_num_hellos_latest_session,
+            client_->client()->client_session()->GetNumSentClientHellos());
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(3, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  }
+
+  VerifyCleanConnection(false);
+}
+
+TEST_P(EndToEndTest, StatelessRejectWithPacketLoss) {
+  // In this test, we intentionally drop the first packet from the
+  // server, which corresponds with the initial REJ/SREJ response from
+  // the server.
+  server_writer_->set_fake_drop_first_n_packets(1);
+  ASSERT_TRUE(Initialize());
+}
+
+TEST_P(EndToEndTest, SetInitialReceivedConnectionOptions) {
+  QuicTagVector initial_received_options;
+  initial_received_options.push_back(kTBBR);
+  initial_received_options.push_back(kIW10);
+  initial_received_options.push_back(kPRST);
+  EXPECT_TRUE(server_config_.SetInitialReceivedConnectionOptions(
+      initial_received_options));
+
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  EXPECT_FALSE(server_config_.SetInitialReceivedConnectionOptions(
+      initial_received_options));
+
+  // Verify that server's configuration is correct.
+  server_thread_->Pause();
+  EXPECT_TRUE(server_config_.HasReceivedConnectionOptions());
+  EXPECT_TRUE(
+      ContainsQuicTag(server_config_.ReceivedConnectionOptions(), kTBBR));
+  EXPECT_TRUE(
+      ContainsQuicTag(server_config_.ReceivedConnectionOptions(), kIW10));
+  EXPECT_TRUE(
+      ContainsQuicTag(server_config_.ReceivedConnectionOptions(), kPRST));
+}
+
+TEST_P(EndToEndTest, LargePostSmallBandwidthLargeBuffer) {
+  ASSERT_TRUE(Initialize());
+  SetPacketSendDelay(QuicTime::Delta::FromMicroseconds(1));
+  // 256KB per second with a 256KB buffer from server to client.  Wireless
+  // clients commonly have larger buffers, but our max CWND is 200.
+  server_writer_->set_max_bandwidth_and_buffer_size(
+      QuicBandwidth::FromBytesPerSecond(256 * 1024), 256 * 1024);
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // 1 MB body.
+  QuicString body(1024 * 1024, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  // This connection may drop packets, because the buffer is smaller than the
+  // max CWND.
+  VerifyCleanConnection(true);
+}
+
+TEST_P(EndToEndTestWithTls, DoNotSetSendAlarmIfConnectionFlowControlBlocked) {
+  // Regression test for b/14677858.
+  // Test that the resume write alarm is not set in QuicConnection::OnCanWrite
+  // if currently connection level flow control blocked. If set, this results in
+  // an infinite loop in the EpollServer, as the alarm fires and is immediately
+  // rescheduled.
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // Ensure both stream and connection level are flow control blocked by setting
+  // the send window offset to 0.
+  const uint64_t flow_control_window =
+      server_config_.GetInitialStreamFlowControlWindowToSend();
+  QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+  QuicSession* session = client_->client()->client_session();
+  QuicFlowControllerPeer::SetSendWindowOffset(stream->flow_controller(), 0);
+  QuicFlowControllerPeer::SetSendWindowOffset(session->flow_controller(), 0);
+  EXPECT_TRUE(stream->flow_controller()->IsBlocked());
+  EXPECT_TRUE(session->flow_controller()->IsBlocked());
+
+  // Make sure that the stream has data pending so that it will be marked as
+  // write blocked when it receives a stream level WINDOW_UPDATE.
+  stream->WriteOrBufferBody("hello", false);
+
+  // The stream now attempts to write, fails because it is still connection
+  // level flow control blocked, and is added to the write blocked list.
+  QuicWindowUpdateFrame window_update(kInvalidControlFrameId, stream->id(),
+                                      2 * flow_control_window);
+  stream->OnWindowUpdateFrame(window_update);
+
+  // Prior to fixing b/14677858 this call would result in an infinite loop in
+  // Chromium. As a proxy for detecting this, we now check whether the
+  // send alarm is set after OnCanWrite. It should not be, as the
+  // connection is still flow control blocked.
+  session->connection()->OnCanWrite();
+
+  QuicAlarm* send_alarm =
+      QuicConnectionPeer::GetSendAlarm(session->connection());
+  EXPECT_FALSE(send_alarm->IsSet());
+}
+
+// TODO(nharper): Needs to get turned back to EndToEndTestWithTls
+// when we figure out why the test doesn't work on chrome.
+TEST_P(EndToEndTest, InvalidStream) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  QuicString body(kMaxPacketSize, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  // Force the client to write with a stream ID belonging to a nonexistent
+  // server-side stream.
+  QuicSpdySession* session = client_->client()->client_session();
+  QuicSessionPeer::SetNextOutgoingBidirectionalStreamId(
+      session, GetNthServerInitiatedBidirectionalId(0));
+
+  client_->SendCustomSynchronousRequest(headers, body);
+  EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+  EXPECT_EQ(QUIC_INVALID_STREAM_ID, client_->connection_error());
+}
+
+// Test that if the server will close the connection if the client attempts
+// to send a request with overly large headers.
+TEST_P(EndToEndTest, LargeHeaders) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  QuicString body(kMaxPacketSize, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  headers["key1"] = QuicString(15 * 1024, 'a');
+  headers["key2"] = QuicString(15 * 1024, 'a');
+  headers["key3"] = QuicString(15 * 1024, 'a');
+
+  client_->SendCustomSynchronousRequest(headers, body);
+  EXPECT_EQ(QUIC_HEADERS_TOO_LARGE, client_->stream_error());
+  EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+TEST_P(EndToEndTest, EarlyResponseWithQuicStreamNoError) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  QuicString large_body(1024 * 1024, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  // Insert an invalid content_length field in request to trigger an early
+  // response from server.
+  headers["content-length"] = "-3";
+
+  client_->SendCustomSynchronousRequest(headers, large_body);
+  EXPECT_EQ("bad", client_->response_body());
+  EXPECT_EQ("500", client_->response_headers()->find(":status")->second);
+  EXPECT_EQ(QUIC_STREAM_NO_ERROR, client_->stream_error());
+  EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+// TODO(rch): this test seems to cause net_unittests timeouts :|
+TEST_P(EndToEndTestWithTls, QUIC_TEST_DISABLED_IN_CHROME(MultipleTermination)) {
+  ASSERT_TRUE(Initialize());
+
+  // Set the offset so we won't frame.  Otherwise when we pick up termination
+  // before HTTP framing is complete, we send an error and close the stream,
+  // and the second write is picked up as writing on a closed stream.
+  QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+  ASSERT_TRUE(stream != nullptr);
+  QuicStreamPeer::SetStreamBytesWritten(3, stream);
+
+  client_->SendData("bar", true);
+  client_->WaitForWriteToFlush();
+
+  // By default the stream protects itself from writes after terminte is set.
+  // Override this to test the server handling buggy clients.
+  QuicStreamPeer::SetWriteSideClosed(false, client_->GetOrCreateStream());
+
+  EXPECT_QUIC_BUG(client_->SendData("eep", true), "Fin already buffered");
+}
+
+// TODO(nharper): Needs to get turned back to EndToEndTestWithTls
+// when we figure out why the test doesn't work on chrome.
+TEST_P(EndToEndTest, Timeout) {
+  client_config_.SetIdleNetworkTimeout(QuicTime::Delta::FromMicroseconds(500),
+                                       QuicTime::Delta::FromMicroseconds(500));
+  // Note: we do NOT ASSERT_TRUE: we may time out during initial handshake:
+  // that's enough to validate timeout in this case.
+  Initialize();
+  while (client_->client()->connected()) {
+    client_->client()->WaitForEvents();
+  }
+}
+
+TEST_P(EndToEndTestWithTls, MaxIncomingDynamicStreamsLimitRespected) {
+  // Set a limit on maximum number of incoming dynamic streams.
+  // Make sure the limit is respected.
+  const uint32_t kServerMaxIncomingDynamicStreams = 1;
+  server_config_.SetMaxIncomingDynamicStreamsToSend(
+      kServerMaxIncomingDynamicStreams);
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+
+  // Make the client misbehave after negotiation.
+  const int kServerMaxStreams = kMaxStreamsMinimumIncrement + 1;
+  QuicSessionPeer::SetMaxOpenOutgoingStreams(
+      client_->client()->client_session(), kServerMaxStreams + 1);
+
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  headers["content-length"] = "3";
+
+  // The server supports a small number of additional streams beyond the
+  // negotiated limit. Open enough streams to go beyond that limit.
+  for (int i = 0; i < kServerMaxStreams + 1; ++i) {
+    client_->SendMessage(headers, "", /*fin=*/false);
+  }
+  client_->WaitForResponse();
+  if (client_connection->transport_version() != QUIC_VERSION_99) {
+    EXPECT_TRUE(client_->connected());
+    EXPECT_EQ(QUIC_REFUSED_STREAM, client_->stream_error());
+    EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+  } else {
+    // Version 99 disconnects the connection if we exceed the stream limit.
+    EXPECT_FALSE(client_->connected());
+    EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+    EXPECT_EQ(QUIC_INVALID_STREAM_ID, client_->connection_error());
+  }
+}
+
+TEST_P(EndToEndTest, SetIndependentMaxIncomingDynamicStreamsLimits) {
+  // Each endpoint can set max incoming dynamic streams independently.
+  const uint32_t kClientMaxIncomingDynamicStreams = 2;
+  const uint32_t kServerMaxIncomingDynamicStreams = 1;
+  client_config_.SetMaxIncomingDynamicStreamsToSend(
+      kClientMaxIncomingDynamicStreams);
+  server_config_.SetMaxIncomingDynamicStreamsToSend(
+      kServerMaxIncomingDynamicStreams);
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // The client has received the server's limit and vice versa.
+  QuicSpdyClientSession* client_session = client_->client()->client_session();
+  size_t client_max_open_outgoing_bidirectional_streams =
+      client_session->connection()->transport_version() == QUIC_VERSION_99
+          ? QuicSessionPeer::v99_streamid_manager(client_session)
+                ->max_allowed_outgoing_bidirectional_streams()
+          : QuicSessionPeer::GetStreamIdManager(client_session)
+                ->max_open_outgoing_streams();
+  size_t client_max_open_outgoing_unidirectional_streams =
+      client_session->connection()->transport_version() == QUIC_VERSION_99
+          ? QuicSessionPeer::v99_streamid_manager(client_session)
+                ->max_allowed_outgoing_unidirectional_streams()
+          : QuicSessionPeer::GetStreamIdManager(client_session)
+                ->max_open_outgoing_streams();
+  EXPECT_EQ(kServerMaxIncomingDynamicStreams,
+            client_max_open_outgoing_bidirectional_streams);
+  EXPECT_EQ(kServerMaxIncomingDynamicStreams,
+            client_max_open_outgoing_unidirectional_streams);
+  server_thread_->Pause();
+  QuicSession* server_session = GetServerSession();
+  size_t server_max_open_outgoing_bidirectional_streams =
+      server_session->connection()->transport_version() == QUIC_VERSION_99
+          ? QuicSessionPeer::v99_streamid_manager(server_session)
+                ->max_allowed_outgoing_bidirectional_streams()
+          : QuicSessionPeer::GetStreamIdManager(server_session)
+                ->max_open_outgoing_streams();
+  size_t server_max_open_outgoing_unidirectional_streams =
+      server_session->connection()->transport_version() == QUIC_VERSION_99
+          ? QuicSessionPeer::v99_streamid_manager(server_session)
+                ->max_allowed_outgoing_unidirectional_streams()
+          : QuicSessionPeer::GetStreamIdManager(server_session)
+                ->max_open_outgoing_streams();
+  EXPECT_EQ(kClientMaxIncomingDynamicStreams,
+            server_max_open_outgoing_bidirectional_streams);
+  EXPECT_EQ(kClientMaxIncomingDynamicStreams,
+            server_max_open_outgoing_unidirectional_streams);
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, NegotiateCongestionControl) {
+  ASSERT_TRUE(Initialize());
+
+  // For PCC, the underlying implementation may be a stub with a
+  // different name-tag.  Skip the rest of this test.
+  if (GetParam().congestion_control_tag == kTPCC) {
+    return;
+  }
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  CongestionControlType expected_congestion_control_type = kRenoBytes;
+  switch (GetParam().congestion_control_tag) {
+    case kRENO:
+      expected_congestion_control_type = kRenoBytes;
+      break;
+    case kTBBR:
+      expected_congestion_control_type = kBBR;
+      break;
+    case kQBIC:
+      expected_congestion_control_type = kCubicBytes;
+      break;
+    default:
+      QUIC_DLOG(FATAL) << "Unexpected congestion control tag";
+  }
+
+  server_thread_->Pause();
+  EXPECT_EQ(expected_congestion_control_type,
+            QuicSentPacketManagerPeer::GetSendAlgorithm(
+                *GetSentPacketManagerFromFirstServerSession())
+                ->GetCongestionControlType());
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, ClientSuggestsRTT) {
+  // Client suggests initial RTT, verify it is used.
+  const QuicTime::Delta kInitialRTT = QuicTime::Delta::FromMicroseconds(20000);
+  client_config_.SetInitialRoundTripTimeUsToSend(kInitialRTT.ToMicroseconds());
+
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  // Pause the server so we can access the server's internals without races.
+  server_thread_->Pause();
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  ASSERT_EQ(1u, dispatcher->session_map().size());
+  const QuicSentPacketManager& client_sent_packet_manager =
+      client_->client()->client_session()->connection()->sent_packet_manager();
+  const QuicSentPacketManager* server_sent_packet_manager =
+      GetSentPacketManagerFromFirstServerSession();
+
+  EXPECT_EQ(kInitialRTT,
+            client_sent_packet_manager.GetRttStats()->initial_rtt());
+  EXPECT_EQ(kInitialRTT,
+            server_sent_packet_manager->GetRttStats()->initial_rtt());
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, ClientSuggestsIgnoredRTT) {
+  // Client suggests initial RTT, but also specifies NRTT, so it's not used.
+  const QuicTime::Delta kInitialRTT = QuicTime::Delta::FromMicroseconds(20000);
+  client_config_.SetInitialRoundTripTimeUsToSend(kInitialRTT.ToMicroseconds());
+  QuicTagVector options;
+  options.push_back(kNRTT);
+  client_config_.SetConnectionOptionsToSend(options);
+
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  // Pause the server so we can access the server's internals without races.
+  server_thread_->Pause();
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  ASSERT_EQ(1u, dispatcher->session_map().size());
+  const QuicSentPacketManager& client_sent_packet_manager =
+      client_->client()->client_session()->connection()->sent_packet_manager();
+  const QuicSentPacketManager* server_sent_packet_manager =
+      GetSentPacketManagerFromFirstServerSession();
+
+  EXPECT_EQ(kInitialRTT,
+            client_sent_packet_manager.GetRttStats()->initial_rtt());
+  EXPECT_EQ(kInitialRTT,
+            server_sent_packet_manager->GetRttStats()->initial_rtt());
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, MaxInitialRTT) {
+  // Client tries to suggest twice the server's max initial rtt and the server
+  // uses the max.
+  client_config_.SetInitialRoundTripTimeUsToSend(2 *
+                                                 kMaxInitialRoundTripTimeUs);
+
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  // Pause the server so we can access the server's internals without races.
+  server_thread_->Pause();
+  const QuicSentPacketManager& client_sent_packet_manager =
+      client_->client()->client_session()->connection()->sent_packet_manager();
+
+  // Now that acks have been exchanged, the RTT estimate has decreased on the
+  // server and is not infinite on the client.
+  EXPECT_FALSE(
+      client_sent_packet_manager.GetRttStats()->smoothed_rtt().IsInfinite());
+  const RttStats& server_rtt_stats =
+      *GetServerConnection()->sent_packet_manager().GetRttStats();
+  EXPECT_EQ(static_cast<int64_t>(kMaxInitialRoundTripTimeUs),
+            server_rtt_stats.initial_rtt().ToMicroseconds());
+  EXPECT_GE(static_cast<int64_t>(kMaxInitialRoundTripTimeUs),
+            server_rtt_stats.smoothed_rtt().ToMicroseconds());
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, MinInitialRTT) {
+  // Client tries to suggest 0 and the server uses the default.
+  client_config_.SetInitialRoundTripTimeUsToSend(0);
+
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  // Pause the server so we can access the server's internals without races.
+  server_thread_->Pause();
+  const QuicSentPacketManager& client_sent_packet_manager =
+      client_->client()->client_session()->connection()->sent_packet_manager();
+  const QuicSentPacketManager& server_sent_packet_manager =
+      GetServerConnection()->sent_packet_manager();
+
+  // Now that acks have been exchanged, the RTT estimate has decreased on the
+  // server and is not infinite on the client.
+  EXPECT_FALSE(
+      client_sent_packet_manager.GetRttStats()->smoothed_rtt().IsInfinite());
+  // Expect the default rtt of 100ms.
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(100),
+            server_sent_packet_manager.GetRttStats()->initial_rtt());
+  // Ensure the bandwidth is valid.
+  client_sent_packet_manager.BandwidthEstimate();
+  server_sent_packet_manager.BandwidthEstimate();
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, 0ByteConnectionId) {
+  client_config_.SetBytesForConnectionIdToSend(0);
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  QuicPacketHeader* header =
+      QuicConnectionPeer::GetLastHeader(client_connection);
+  EXPECT_EQ(CONNECTION_ID_ABSENT, header->destination_connection_id_included);
+}
+
+TEST_P(EndToEndTestWithTls, 8ByteConnectionId) {
+  client_config_.SetBytesForConnectionIdToSend(8);
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  QuicPacketHeader* header =
+      QuicConnectionPeer::GetLastHeader(client_connection);
+  if (client_connection->transport_version() > QUIC_VERSION_43) {
+    EXPECT_EQ(CONNECTION_ID_ABSENT, header->destination_connection_id_included);
+  } else {
+    EXPECT_EQ(CONNECTION_ID_PRESENT,
+              header->destination_connection_id_included);
+  }
+}
+
+TEST_P(EndToEndTestWithTls, 15ByteConnectionId) {
+  client_config_.SetBytesForConnectionIdToSend(15);
+  ASSERT_TRUE(Initialize());
+
+  // Our server is permissive and allows for out of bounds values.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  QuicPacketHeader* header =
+      QuicConnectionPeer::GetLastHeader(client_connection);
+  if (client_connection->transport_version() > QUIC_VERSION_43) {
+    EXPECT_EQ(CONNECTION_ID_ABSENT, header->destination_connection_id_included);
+  } else {
+    EXPECT_EQ(CONNECTION_ID_PRESENT,
+              header->destination_connection_id_included);
+  }
+}
+
+TEST_P(EndToEndTestWithTls, ResetConnection) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  client_->ResetConnection();
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// TODO(nharper): Needs to get turned back to EndToEndTestWithTls
+// when we figure out why the test doesn't work on chrome.
+TEST_P(EndToEndTest, MaxStreamsUberTest) {
+  if (!BothSidesSupportStatelessRejects()) {
+    // Connect with lower fake packet loss than we'd like to test.  Until
+    // b/10126687 is fixed, losing handshake packets is pretty brutal.
+    // TODO(jokulik): Until we support redundant SREJ packets, don't
+    // drop handshake packets for stateless rejects.
+    SetPacketLossPercentage(1);
+  }
+  ASSERT_TRUE(Initialize());
+  QuicString large_body(10240, 'a');
+  int max_streams = 100;
+
+  AddToCache("/large_response", 200, large_body);
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  SetPacketLossPercentage(10);
+
+  for (int i = 0; i < max_streams; ++i) {
+    EXPECT_LT(0, client_->SendRequest("/large_response"));
+  }
+
+  // WaitForEvents waits 50ms and returns true if there are outstanding
+  // requests.
+  while (client_->client()->WaitForEvents() == true) {
+  }
+}
+
+TEST_P(EndToEndTestWithTls, StreamCancelErrorTest) {
+  ASSERT_TRUE(Initialize());
+  QuicString small_body(256, 'a');
+
+  AddToCache("/small_response", 200, small_body);
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  QuicSession* session = client_->client()->client_session();
+  // Lose the request.
+  SetPacketLossPercentage(100);
+  EXPECT_LT(0, client_->SendRequest("/small_response"));
+  client_->client()->WaitForEvents();
+  // Transmit the cancel, and ensure the connection is torn down properly.
+  SetPacketLossPercentage(0);
+  QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+  session->SendRstStream(stream_id, QUIC_STREAM_CANCELLED, 0);
+
+  // WaitForEvents waits 50ms and returns true if there are outstanding
+  // requests.
+  while (client_->client()->WaitForEvents() == true) {
+  }
+  // It should be completely fine to RST a stream before any data has been
+  // received for that stream.
+  EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+TEST_P(EndToEndTest, ConnectionMigrationClientIPChanged) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Store the client IP address which was used to send the first request.
+  QuicIpAddress old_host =
+      client_->client()->network_helper()->GetLatestClientAddress().host();
+
+  // Migrate socket to the new IP address.
+  QuicIpAddress new_host = TestLoopback(2);
+  EXPECT_NE(old_host, new_host);
+  ASSERT_TRUE(client_->client()->MigrateSocket(new_host));
+
+  // Send a request using the new socket.
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTest, ConnectionMigrationClientPortChanged) {
+  // Tests that the client's port can change during an established QUIC
+  // connection, and that doing so does not result in the connection being
+  // closed by the server.
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Store the client address which was used to send the first request.
+  QuicSocketAddress old_address =
+      client_->client()->network_helper()->GetLatestClientAddress();
+  int old_fd = client_->client()->GetLatestFD();
+
+  // Create a new socket before closing the old one, which will result in a new
+  // ephemeral port.
+  QuicClientPeer::CreateUDPSocketAndBind(client_->client());
+
+  // Stop listening and close the old FD.
+  QuicClientPeer::CleanUpUDPSocket(client_->client(), old_fd);
+
+  // The packet writer needs to be updated to use the new FD.
+  client_->client()->network_helper()->CreateQuicPacketWriter();
+
+  // Change the internal state of the client and connection to use the new port,
+  // this is done because in a real NAT rebinding the client wouldn't see any
+  // port change, and so expects no change to incoming port.
+  // This is kind of ugly, but needed as we are simply swapping out the client
+  // FD rather than any more complex NAT rebinding simulation.
+  int new_port =
+      client_->client()->network_helper()->GetLatestClientAddress().port();
+  QuicClientPeer::SetClientPort(client_->client(), new_port);
+  QuicConnectionPeer::SetSelfAddress(
+      client_->client()->client_session()->connection(),
+      QuicSocketAddress(client_->client()
+                            ->client_session()
+                            ->connection()
+                            ->self_address()
+                            .host(),
+                        new_port));
+
+  // Register the new FD for epoll events.
+  int new_fd = client_->client()->GetLatestFD();
+  QuicEpollServer* eps = client_->epoll_server();
+  eps->RegisterFD(new_fd, client_->client()->epoll_network_helper(),
+                  EPOLLIN | EPOLLOUT | EPOLLET);
+
+  // Send a second request, using the new FD.
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Verify that the client's ephemeral port is different.
+  QuicSocketAddress new_address =
+      client_->client()->network_helper()->GetLatestClientAddress();
+  EXPECT_EQ(old_address.host(), new_address.host());
+  EXPECT_NE(old_address.port(), new_address.port());
+}
+
+TEST_P(EndToEndTest, NegotiatedInitialCongestionWindow) {
+  SetQuicReloadableFlag(quic_unified_iw_options, true);
+  client_extra_copts_.push_back(kIW03);
+
+  ASSERT_TRUE(Initialize());
+
+  // Values are exchanged during crypto handshake, so wait for that to finish.
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+  server_thread_->Pause();
+
+  QuicPacketCount cwnd =
+      GetServerConnection()->sent_packet_manager().initial_congestion_window();
+  EXPECT_EQ(3u, cwnd);
+}
+
+TEST_P(EndToEndTest, DifferentFlowControlWindows) {
+  // Client and server can set different initial flow control receive windows.
+  // These are sent in CHLO/SHLO. Tests that these values are exchanged properly
+  // in the crypto handshake.
+  const uint32_t kClientStreamIFCW = 123456;
+  const uint32_t kClientSessionIFCW = 234567;
+  set_client_initial_stream_flow_control_receive_window(kClientStreamIFCW);
+  set_client_initial_session_flow_control_receive_window(kClientSessionIFCW);
+
+  uint32_t kServerStreamIFCW = 32 * 1024;
+  uint32_t kServerSessionIFCW = 48 * 1024;
+  set_server_initial_stream_flow_control_receive_window(kServerStreamIFCW);
+  set_server_initial_session_flow_control_receive_window(kServerSessionIFCW);
+
+  ASSERT_TRUE(Initialize());
+
+  // Values are exchanged during crypto handshake, so wait for that to finish.
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  // Open a data stream to make sure the stream level flow control is updated.
+  QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+  stream->WriteOrBufferBody("hello", false);
+
+  // Client should have the right values for server's receive window.
+  EXPECT_EQ(kServerStreamIFCW,
+            client_->client()
+                ->client_session()
+                ->config()
+                ->ReceivedInitialStreamFlowControlWindowBytes());
+  EXPECT_EQ(kServerSessionIFCW,
+            client_->client()
+                ->client_session()
+                ->config()
+                ->ReceivedInitialSessionFlowControlWindowBytes());
+  EXPECT_EQ(kServerStreamIFCW, QuicFlowControllerPeer::SendWindowOffset(
+                                   stream->flow_controller()));
+  EXPECT_EQ(kServerSessionIFCW,
+            QuicFlowControllerPeer::SendWindowOffset(
+                client_->client()->client_session()->flow_controller()));
+
+  // Server should have the right values for client's receive window.
+  server_thread_->Pause();
+  QuicSession* session = GetServerSession();
+  EXPECT_EQ(kClientStreamIFCW,
+            session->config()->ReceivedInitialStreamFlowControlWindowBytes());
+  EXPECT_EQ(kClientSessionIFCW,
+            session->config()->ReceivedInitialSessionFlowControlWindowBytes());
+  EXPECT_EQ(kClientSessionIFCW, QuicFlowControllerPeer::SendWindowOffset(
+                                    session->flow_controller()));
+  server_thread_->Resume();
+}
+
+// Test negotiation of IFWA connection option.
+TEST_P(EndToEndTest, NegotiatedServerInitialFlowControlWindow) {
+  const uint32_t kClientStreamIFCW = 123456;
+  const uint32_t kClientSessionIFCW = 234567;
+  set_client_initial_stream_flow_control_receive_window(kClientStreamIFCW);
+  set_client_initial_session_flow_control_receive_window(kClientSessionIFCW);
+
+  uint32_t kServerStreamIFCW = 32 * 1024;
+  uint32_t kServerSessionIFCW = 48 * 1024;
+  set_server_initial_stream_flow_control_receive_window(kServerStreamIFCW);
+  set_server_initial_session_flow_control_receive_window(kServerSessionIFCW);
+
+  // Bump the window.
+  const uint32_t kExpectedStreamIFCW = 1024 * 1024;
+  const uint32_t kExpectedSessionIFCW = 1.5 * 1024 * 1024;
+  client_extra_copts_.push_back(kIFWA);
+
+  ASSERT_TRUE(Initialize());
+
+  // Values are exchanged during crypto handshake, so wait for that to finish.
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  // Open a data stream to make sure the stream level flow control is updated.
+  QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+  stream->WriteOrBufferBody("hello", false);
+
+  // Client should have the right values for server's receive window.
+  EXPECT_EQ(kExpectedStreamIFCW,
+            client_->client()
+                ->client_session()
+                ->config()
+                ->ReceivedInitialStreamFlowControlWindowBytes());
+  EXPECT_EQ(kExpectedSessionIFCW,
+            client_->client()
+                ->client_session()
+                ->config()
+                ->ReceivedInitialSessionFlowControlWindowBytes());
+  EXPECT_EQ(kExpectedStreamIFCW, QuicFlowControllerPeer::SendWindowOffset(
+                                     stream->flow_controller()));
+  EXPECT_EQ(kExpectedSessionIFCW,
+            QuicFlowControllerPeer::SendWindowOffset(
+                client_->client()->client_session()->flow_controller()));
+}
+
+TEST_P(EndToEndTest, HeadersAndCryptoStreamsNoConnectionFlowControl) {
+  // The special headers and crypto streams should be subject to per-stream flow
+  // control limits, but should not be subject to connection level flow control
+  const uint32_t kStreamIFCW = 32 * 1024;
+  const uint32_t kSessionIFCW = 48 * 1024;
+  set_client_initial_stream_flow_control_receive_window(kStreamIFCW);
+  set_client_initial_session_flow_control_receive_window(kSessionIFCW);
+  set_server_initial_stream_flow_control_receive_window(kStreamIFCW);
+  set_server_initial_session_flow_control_receive_window(kSessionIFCW);
+
+  ASSERT_TRUE(Initialize());
+
+  // Wait for crypto handshake to finish. This should have contributed to the
+  // crypto stream flow control window, but not affected the session flow
+  // control window.
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  QuicCryptoStream* crypto_stream = QuicSessionPeer::GetMutableCryptoStream(
+      client_->client()->client_session());
+  // In v47 and later, the crypto handshake (sent in CRYPTO frames) is not
+  // subject to flow control.
+  if (client_->client()->client_session()->connection()->transport_version() <
+      QUIC_VERSION_47) {
+    EXPECT_LT(QuicFlowControllerPeer::SendWindowSize(
+                  crypto_stream->flow_controller()),
+              kStreamIFCW);
+  }
+  EXPECT_EQ(kSessionIFCW,
+            QuicFlowControllerPeer::SendWindowSize(
+                client_->client()->client_session()->flow_controller()));
+
+  // Send a request with no body, and verify that the connection level window
+  // has not been affected.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+  QuicHeadersStream* headers_stream = QuicSpdySessionPeer::GetHeadersStream(
+      client_->client()->client_session());
+  EXPECT_LT(
+      QuicFlowControllerPeer::SendWindowSize(headers_stream->flow_controller()),
+      kStreamIFCW);
+  EXPECT_EQ(kSessionIFCW,
+            QuicFlowControllerPeer::SendWindowSize(
+                client_->client()->client_session()->flow_controller()));
+
+  // Server should be in a similar state: connection flow control window should
+  // not have any bytes marked as received.
+  server_thread_->Pause();
+  QuicSession* session = GetServerSession();
+  QuicFlowController* server_connection_flow_controller =
+      session->flow_controller();
+  EXPECT_EQ(kSessionIFCW, QuicFlowControllerPeer::ReceiveWindowSize(
+                              server_connection_flow_controller));
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, FlowControlsSynced) {
+  set_smaller_flow_control_receive_window();
+
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  server_thread_->WaitForCryptoHandshakeConfirmed();
+
+  server_thread_->Pause();
+  QuicSpdySession* const client_session = client_->client()->client_session();
+  auto* server_session = static_cast<QuicSpdySession*>(GetServerSession());
+  ExpectFlowControlsSynced(client_session->flow_controller(),
+                           server_session->flow_controller());
+  ExpectFlowControlsSynced(
+      QuicSessionPeer::GetMutableCryptoStream(client_session)
+          ->flow_controller(),
+      QuicSessionPeer::GetMutableCryptoStream(server_session)
+          ->flow_controller());
+  SpdyFramer spdy_framer(SpdyFramer::ENABLE_COMPRESSION);
+  SpdySettingsIR settings_frame;
+  settings_frame.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE,
+                            kDefaultMaxUncompressedHeaderSize);
+  SpdySerializedFrame frame(spdy_framer.SerializeFrame(settings_frame));
+  QuicFlowController* client_header_stream_flow_controller =
+      QuicSpdySessionPeer::GetHeadersStream(client_session)->flow_controller();
+  QuicFlowController* server_header_stream_flow_controller =
+      QuicSpdySessionPeer::GetHeadersStream(server_session)->flow_controller();
+  // Both client and server are sending this SETTINGS frame, and the send
+  // window is consumed. But because of timing issue, the server may send or
+  // not send the frame, and the client may send/ not send / receive / not
+  // receive the frame.
+  // TODO(fayang): Rewrite this part because it is hacky.
+  QuicByteCount win_difference1 = QuicFlowControllerPeer::ReceiveWindowSize(
+                                      server_header_stream_flow_controller) -
+                                  QuicFlowControllerPeer::SendWindowSize(
+                                      client_header_stream_flow_controller);
+  QuicByteCount win_difference2 = QuicFlowControllerPeer::ReceiveWindowSize(
+                                      client_header_stream_flow_controller) -
+                                  QuicFlowControllerPeer::SendWindowSize(
+                                      server_header_stream_flow_controller);
+  EXPECT_TRUE(win_difference1 == 0 || win_difference1 == frame.size());
+  EXPECT_TRUE(win_difference2 == 0 || win_difference2 == frame.size());
+
+  // Client *may* have received the SETTINGs frame.
+  // TODO(fayang): Rewrite this part because it is hacky.
+  float ratio1 = static_cast<float>(QuicFlowControllerPeer::ReceiveWindowSize(
+                     client_session->flow_controller())) /
+                 QuicFlowControllerPeer::ReceiveWindowSize(
+                     QuicSpdySessionPeer::GetHeadersStream(client_session)
+                         ->flow_controller());
+  float ratio2 = static_cast<float>(QuicFlowControllerPeer::ReceiveWindowSize(
+                     client_session->flow_controller())) /
+                 (QuicFlowControllerPeer::ReceiveWindowSize(
+                      QuicSpdySessionPeer::GetHeadersStream(client_session)
+                          ->flow_controller()) +
+                  frame.size());
+  EXPECT_TRUE(ratio1 == kSessionToStreamRatio ||
+              ratio2 == kSessionToStreamRatio);
+
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTestWithTls, RequestWithNoBodyWillNeverSendStreamFrameWithFIN) {
+  // A stream created on receipt of a simple request with no body will never get
+  // a stream frame with a FIN. Verify that we don't keep track of the stream in
+  // the locally closed streams map: it will never be removed if so.
+  ASSERT_TRUE(Initialize());
+
+  // Send a simple headers only request, and receive response.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Now verify that the server is not waiting for a final FIN or RST.
+  server_thread_->Pause();
+  QuicSession* session = GetServerSession();
+  EXPECT_EQ(
+      0u,
+      QuicSessionPeer::GetLocallyClosedStreamsHighestOffset(session).size());
+  server_thread_->Resume();
+}
+
+// A TestAckListener verifies that its OnAckNotification method has been
+// called exactly once on destruction.
+class TestAckListener : public QuicAckListenerInterface {
+ public:
+  explicit TestAckListener(int bytes_to_ack) : bytes_to_ack_(bytes_to_ack) {}
+
+  void OnPacketAcked(int acked_bytes,
+                     QuicTime::Delta /*delta_largest_observed*/) override {
+    ASSERT_LE(acked_bytes, bytes_to_ack_);
+    bytes_to_ack_ -= acked_bytes;
+  }
+
+  void OnPacketRetransmitted(int /*retransmitted_bytes*/) override {}
+
+  bool has_been_notified() const { return bytes_to_ack_ == 0; }
+
+ protected:
+  // Object is ref counted.
+  ~TestAckListener() override { EXPECT_EQ(0, bytes_to_ack_); }
+
+ private:
+  int bytes_to_ack_;
+};
+
+class TestResponseListener : public QuicSpdyClientBase::ResponseListener {
+ public:
+  void OnCompleteResponse(QuicStreamId id,
+                          const SpdyHeaderBlock& response_headers,
+                          const QuicString& response_body) override {
+    QUIC_DVLOG(1) << "response for stream " << id << " "
+                  << response_headers.DebugString() << "\n"
+                  << response_body;
+  }
+};
+
+TEST_P(EndToEndTest, AckNotifierWithPacketLossAndBlockedSocket) {
+  // Verify that even in the presence of packet loss and occasionally blocked
+  // socket,  an AckNotifierDelegate will get informed that the data it is
+  // interested in has been ACKed. This tests end-to-end ACK notification, and
+  // demonstrates that retransmissions do not break this functionality.
+  if (!BothSidesSupportStatelessRejects()) {
+    // TODO(jokulik): Until we support redundant SREJ packets, don't
+    // drop handshake packets for stateless rejects.
+    SetPacketLossPercentage(5);
+  }
+  ASSERT_TRUE(Initialize());
+
+  // Wait for the server SHLO before upping the packet loss.
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  SetPacketLossPercentage(30);
+  client_writer_->set_fake_blocked_socket_percentage(10);
+
+  // Create a POST request and send the headers only.
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  client_->SendMessage(headers, "", /*fin=*/false);
+
+  // Test the AckNotifier's ability to track multiple packets by making the
+  // request body exceed the size of a single packet.
+  QuicString request_string =
+      "a request body bigger than one packet" + QuicString(kMaxPacketSize, '.');
+
+  // The TestAckListener will cause a failure if not notified.
+  QuicReferenceCountedPointer<TestAckListener> ack_listener(
+      new TestAckListener(request_string.length()));
+
+  // Send the request, and register the delegate for ACKs.
+  client_->SendData(request_string, true, ack_listener);
+  client_->WaitForResponse();
+  EXPECT_EQ(kFooResponseBody, client_->response_body());
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Send another request to flush out any pending ACKs on the server.
+  client_->SendSynchronousRequest("/bar");
+
+  // Make sure the delegate does get the notification it expects.
+  while (!ack_listener->has_been_notified()) {
+    // Waits for up to 50 ms.
+    client_->client()->WaitForEvents();
+  }
+}
+
+// Send a public reset from the server.
+TEST_P(EndToEndTestWithTls, ServerSendPublicReset) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  if (SupportsIetfQuicWithTls(client_connection->version())) {
+    // TLS handshake does not support stateless reset token yet.
+    return;
+  }
+  QuicUint128 stateless_reset_token = 0;
+  if (client_connection->version().handshake_protocol == PROTOCOL_QUIC_CRYPTO) {
+    QuicConfig* config = client_->client()->session()->config();
+    EXPECT_TRUE(config->HasReceivedStatelessResetToken());
+    stateless_reset_token = config->ReceivedStatelessResetToken();
+  }
+
+  // Send the public reset.
+  QuicConnectionId connection_id = client_connection->connection_id();
+  QuicPublicResetPacket header;
+  header.connection_id = connection_id;
+  QuicFramer framer(server_supported_versions_, QuicTime::Zero(),
+                    Perspective::IS_SERVER, kQuicDefaultConnectionIdLength);
+  std::unique_ptr<QuicEncryptedPacket> packet;
+  if (client_connection->transport_version() > QUIC_VERSION_43) {
+    packet = framer.BuildIetfStatelessResetPacket(connection_id,
+                                                  stateless_reset_token);
+  } else {
+    packet = framer.BuildPublicResetPacket(header);
+  }
+  // We must pause the server's thread in order to call WritePacket without
+  // race conditions.
+  server_thread_->Pause();
+  server_writer_->WritePacket(
+      packet->data(), packet->length(), server_address_.host(),
+      client_->client()->network_helper()->GetLatestClientAddress(), nullptr);
+  server_thread_->Resume();
+
+  // The request should fail.
+  EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+  EXPECT_TRUE(client_->response_headers()->empty());
+  EXPECT_EQ(QUIC_PUBLIC_RESET, client_->connection_error());
+}
+
+// Send a public reset from the server for a different connection ID.
+// It should be ignored.
+TEST_P(EndToEndTestWithTls, ServerSendPublicResetWithDifferentConnectionId) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  if (SupportsIetfQuicWithTls(client_connection->version())) {
+    // TLS handshake does not support stateless reset token yet.
+    return;
+  }
+  QuicUint128 stateless_reset_token = 0;
+  if (client_connection->version().handshake_protocol == PROTOCOL_QUIC_CRYPTO) {
+    QuicConfig* config = client_->client()->session()->config();
+    EXPECT_TRUE(config->HasReceivedStatelessResetToken());
+    stateless_reset_token = config->ReceivedStatelessResetToken();
+  }
+  // Send the public reset.
+  QuicConnectionId incorrect_connection_id = TestConnectionId(
+      TestConnectionIdToUInt64(client_connection->connection_id()) + 1);
+  QuicPublicResetPacket header;
+  header.connection_id = incorrect_connection_id;
+  QuicFramer framer(server_supported_versions_, QuicTime::Zero(),
+                    Perspective::IS_SERVER, kQuicDefaultConnectionIdLength);
+  std::unique_ptr<QuicEncryptedPacket> packet;
+  testing::NiceMock<MockQuicConnectionDebugVisitor> visitor;
+  client_->client()->client_session()->connection()->set_debug_visitor(
+      &visitor);
+  if (client_connection->transport_version() > QUIC_VERSION_43) {
+    packet = framer.BuildIetfStatelessResetPacket(incorrect_connection_id,
+                                                  stateless_reset_token);
+    EXPECT_CALL(visitor, OnIncorrectConnectionId(incorrect_connection_id))
+        .Times(0);
+  } else {
+    packet = framer.BuildPublicResetPacket(header);
+    EXPECT_CALL(visitor, OnIncorrectConnectionId(incorrect_connection_id))
+        .Times(1);
+  }
+  // We must pause the server's thread in order to call WritePacket without
+  // race conditions.
+  server_thread_->Pause();
+  server_writer_->WritePacket(
+      packet->data(), packet->length(), server_address_.host(),
+      client_->client()->network_helper()->GetLatestClientAddress(), nullptr);
+  server_thread_->Resume();
+
+  if (client_connection->transport_version() > QUIC_VERSION_43) {
+    // The request should fail. IETF stateless reset does not include connection
+    // ID.
+    EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+    EXPECT_TRUE(client_->response_headers()->empty());
+    EXPECT_EQ(QUIC_PUBLIC_RESET, client_->connection_error());
+    return;
+  }
+  // The connection should be unaffected.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  client_->client()->client_session()->connection()->set_debug_visitor(nullptr);
+}
+
+// Send a public reset from the client for a different connection ID.
+// It should be ignored.
+TEST_P(EndToEndTestWithTls, ClientSendPublicResetWithDifferentConnectionId) {
+  ASSERT_TRUE(Initialize());
+
+  // Send the public reset.
+  QuicConnectionId incorrect_connection_id = TestConnectionId(
+      TestConnectionIdToUInt64(
+          client_->client()->client_session()->connection()->connection_id()) +
+      1);
+  QuicPublicResetPacket header;
+  header.connection_id = incorrect_connection_id;
+  QuicFramer framer(server_supported_versions_, QuicTime::Zero(),
+                    Perspective::IS_CLIENT, kQuicDefaultConnectionIdLength);
+  std::unique_ptr<QuicEncryptedPacket> packet(
+      framer.BuildPublicResetPacket(header));
+  client_writer_->WritePacket(
+      packet->data(), packet->length(),
+      client_->client()->network_helper()->GetLatestClientAddress().host(),
+      server_address_, nullptr);
+
+  // The connection should be unaffected.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// Send a version negotiation packet from the server for a different
+// connection ID.  It should be ignored.
+TEST_P(EndToEndTestWithTls,
+       ServerSendVersionNegotiationWithDifferentConnectionId) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // Send the version negotiation packet.
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  QuicConnectionId incorrect_connection_id = TestConnectionId(
+      TestConnectionIdToUInt64(client_connection->connection_id()) + 1);
+  std::unique_ptr<QuicEncryptedPacket> packet(
+      QuicFramer::BuildVersionNegotiationPacket(
+          incorrect_connection_id,
+          client_connection->transport_version() > QUIC_VERSION_43,
+          server_supported_versions_));
+  testing::NiceMock<MockQuicConnectionDebugVisitor> visitor;
+  client_connection->set_debug_visitor(&visitor);
+  EXPECT_CALL(visitor, OnIncorrectConnectionId(incorrect_connection_id))
+      .Times(1);
+  // We must pause the server's thread in order to call WritePacket without
+  // race conditions.
+  server_thread_->Pause();
+  server_writer_->WritePacket(
+      packet->data(), packet->length(), server_address_.host(),
+      client_->client()->network_helper()->GetLatestClientAddress(), nullptr);
+  server_thread_->Resume();
+
+  // The connection should be unaffected.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  client_connection->set_debug_visitor(nullptr);
+}
+
+// A bad header shouldn't tear down the connection, because the receiver can't
+// tell the connection ID.
+TEST_P(EndToEndTestWithTls, BadPacketHeaderTruncated) {
+  ASSERT_TRUE(Initialize());
+
+  // Start the connection.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Packet with invalid public flags.
+  char packet[] = {// public flags (8 byte connection_id)
+                   0x3C,
+                   // truncated connection ID
+                   0x11};
+  client_writer_->WritePacket(
+      &packet[0], sizeof(packet),
+      client_->client()->network_helper()->GetLatestClientAddress().host(),
+      server_address_, nullptr);
+  // Give the server time to process the packet.
+  QuicSleep(QuicTime::Delta::FromMilliseconds(100));
+  // Pause the server so we can access the server's internals without races.
+  server_thread_->Pause();
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  EXPECT_EQ(QUIC_INVALID_PACKET_HEADER,
+            QuicDispatcherPeer::GetAndClearLastError(dispatcher));
+  server_thread_->Resume();
+
+  // The connection should not be terminated.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// A bad header shouldn't tear down the connection, because the receiver can't
+// tell the connection ID.
+TEST_P(EndToEndTestWithTls, BadPacketHeaderFlags) {
+  ASSERT_TRUE(Initialize());
+
+  // Start the connection.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Packet with invalid public flags.
+  char packet[] = {
+      // invalid public flags
+      0xFF,
+      // connection_id
+      0x10,
+      0x32,
+      0x54,
+      0x76,
+      0x98,
+      0xBA,
+      0xDC,
+      0xFE,
+      // packet sequence number
+      0xBC,
+      0x9A,
+      0x78,
+      0x56,
+      0x34,
+      0x12,
+      // private flags
+      0x00,
+  };
+  client_writer_->WritePacket(
+      &packet[0], sizeof(packet),
+      client_->client()->network_helper()->GetLatestClientAddress().host(),
+      server_address_, nullptr);
+  // Give the server time to process the packet.
+  QuicSleep(QuicTime::Delta::FromMilliseconds(100));
+  // Pause the server so we can access the server's internals without races.
+  server_thread_->Pause();
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  EXPECT_EQ(QUIC_INVALID_PACKET_HEADER,
+            QuicDispatcherPeer::GetAndClearLastError(dispatcher));
+  server_thread_->Resume();
+
+  // The connection should not be terminated.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// Send a packet from the client with bad encrypted data.  The server should not
+// tear down the connection.
+TEST_P(EndToEndTestWithTls, BadEncryptedData) {
+  ASSERT_TRUE(Initialize());
+
+  // Start the connection.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
+      client_->client()->client_session()->connection()->connection_id(),
+      EmptyQuicConnectionId(), false, false, 1, "At least 20 characters.",
+      CONNECTION_ID_PRESENT, CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER));
+  // Damage the encrypted data.
+  QuicString damaged_packet(packet->data(), packet->length());
+  damaged_packet[30] ^= 0x01;
+  QUIC_DLOG(INFO) << "Sending bad packet.";
+  client_writer_->WritePacket(
+      damaged_packet.data(), damaged_packet.length(),
+      client_->client()->network_helper()->GetLatestClientAddress().host(),
+      server_address_, nullptr);
+  // Give the server time to process the packet.
+  QuicSleep(QuicTime::Delta::FromMilliseconds(100));
+  // This error is sent to the connection's OnError (which ignores it), so the
+  // dispatcher doesn't see it.
+  // Pause the server so we can access the server's internals without races.
+  server_thread_->Pause();
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  EXPECT_EQ(QUIC_NO_ERROR,
+            QuicDispatcherPeer::GetAndClearLastError(dispatcher));
+  server_thread_->Resume();
+
+  // The connection should not be terminated.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls, CanceledStreamDoesNotBecomeZombie) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  // Lose the request.
+  SetPacketLossPercentage(100);
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  client_->SendMessage(headers, "test_body", /*fin=*/false);
+  QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+
+  // Cancel the stream.
+  stream->Reset(QUIC_STREAM_CANCELLED);
+  QuicSession* session = client_->client()->client_session();
+  // Verify canceled stream does not become zombie.
+  EXPECT_TRUE(QuicSessionPeer::zombie_streams(session).empty());
+  EXPECT_EQ(1u, QuicSessionPeer::closed_streams(session).size());
+}
+
+// A test stream that gives |response_body_| as an error response body.
+class ServerStreamWithErrorResponseBody : public QuicSimpleServerStream {
+ public:
+  ServerStreamWithErrorResponseBody(
+      QuicStreamId id,
+      QuicSpdySession* session,
+      QuicSimpleServerBackend* quic_simple_server_backend,
+      QuicString response_body)
+      : QuicSimpleServerStream(id,
+                               session,
+                               BIDIRECTIONAL,
+                               quic_simple_server_backend),
+        response_body_(std::move(response_body)) {}
+
+  ~ServerStreamWithErrorResponseBody() override = default;
+
+ protected:
+  void SendErrorResponse() override {
+    QUIC_DLOG(INFO) << "Sending error response for stream " << id();
+    SpdyHeaderBlock headers;
+    headers[":status"] = "500";
+    headers["content-length"] =
+        QuicTextUtils::Uint64ToString(response_body_.size());
+    // This method must call CloseReadSide to cause the test case, StopReading
+    // is not sufficient.
+    QuicStreamPeer::CloseReadSide(this);
+    SendHeadersAndBody(std::move(headers), response_body_);
+  }
+
+  QuicString response_body_;
+};
+
+class StreamWithErrorFactory : public QuicTestServer::StreamFactory {
+ public:
+  explicit StreamWithErrorFactory(QuicString response_body)
+      : response_body_(std::move(response_body)) {}
+
+  ~StreamWithErrorFactory() override = default;
+
+  QuicSimpleServerStream* CreateStream(
+      QuicStreamId id,
+      QuicSpdySession* session,
+      QuicSimpleServerBackend* quic_simple_server_backend) override {
+    return new ServerStreamWithErrorResponseBody(
+        id, session, quic_simple_server_backend, response_body_);
+  }
+
+ private:
+  QuicString response_body_;
+};
+
+// A test server stream that drops all received body.
+class ServerStreamThatDropsBody : public QuicSimpleServerStream {
+ public:
+  ServerStreamThatDropsBody(QuicStreamId id,
+                            QuicSpdySession* session,
+                            QuicSimpleServerBackend* quic_simple_server_backend)
+      : QuicSimpleServerStream(id,
+                               session,
+                               BIDIRECTIONAL,
+                               quic_simple_server_backend) {}
+
+  ~ServerStreamThatDropsBody() override = default;
+
+ protected:
+  void OnBodyAvailable() override {
+    while (HasBytesToRead()) {
+      struct iovec iov;
+      if (GetReadableRegions(&iov, 1) == 0) {
+        // No more data to read.
+        break;
+      }
+      QUIC_DVLOG(1) << "Processed " << iov.iov_len << " bytes for stream "
+                    << id();
+      MarkConsumed(iov.iov_len);
+    }
+
+    if (!sequencer()->IsClosed()) {
+      sequencer()->SetUnblocked();
+      return;
+    }
+
+    // If the sequencer is closed, then all the body, including the fin, has
+    // been consumed.
+    OnFinRead();
+
+    if (write_side_closed() || fin_buffered()) {
+      return;
+    }
+
+    SendResponse();
+  }
+};
+
+class ServerStreamThatDropsBodyFactory : public QuicTestServer::StreamFactory {
+ public:
+  ServerStreamThatDropsBodyFactory() = default;
+
+  ~ServerStreamThatDropsBodyFactory() override = default;
+
+  QuicSimpleServerStream* CreateStream(
+      QuicStreamId id,
+      QuicSpdySession* session,
+      QuicSimpleServerBackend* quic_simple_server_backend) override {
+    return new ServerStreamThatDropsBody(id, session,
+                                         quic_simple_server_backend);
+  }
+};
+
+// A test server stream that sends response with body size greater than 4GB.
+class ServerStreamThatSendsHugeResponse : public QuicSimpleServerStream {
+ public:
+  ServerStreamThatSendsHugeResponse(
+      QuicStreamId id,
+      QuicSpdySession* session,
+      QuicSimpleServerBackend* quic_simple_server_backend,
+      int64_t body_bytes)
+      : QuicSimpleServerStream(id,
+                               session,
+                               BIDIRECTIONAL,
+                               quic_simple_server_backend),
+        body_bytes_(body_bytes) {}
+
+  ~ServerStreamThatSendsHugeResponse() override = default;
+
+ protected:
+  void SendResponse() override {
+    QuicBackendResponse response;
+    QuicString body(body_bytes_, 'a');
+    response.set_body(body);
+    SendHeadersAndBodyAndTrailers(response.headers().Clone(), response.body(),
+                                  response.trailers().Clone());
+  }
+
+ private:
+  // Use a explicit int64_t rather than size_t to simulate a 64-bit server
+  // talking to a 32-bit client.
+  int64_t body_bytes_;
+};
+
+class ServerStreamThatSendsHugeResponseFactory
+    : public QuicTestServer::StreamFactory {
+ public:
+  explicit ServerStreamThatSendsHugeResponseFactory(int64_t body_bytes)
+      : body_bytes_(body_bytes) {}
+
+  ~ServerStreamThatSendsHugeResponseFactory() override = default;
+
+  QuicSimpleServerStream* CreateStream(
+      QuicStreamId id,
+      QuicSpdySession* session,
+      QuicSimpleServerBackend* quic_simple_server_backend) override {
+    return new ServerStreamThatSendsHugeResponse(
+        id, session, quic_simple_server_backend, body_bytes_);
+  }
+
+  int64_t body_bytes_;
+};
+
+TEST_P(EndToEndTest, EarlyResponseFinRecording) {
+  set_smaller_flow_control_receive_window();
+
+  // Verify that an incoming FIN is recorded in a stream object even if the read
+  // side has been closed.  This prevents an entry from being made in
+  // locally_close_streams_highest_offset_ (which will never be deleted).
+  // To set up the test condition, the server must do the following in order:
+  // start sending the response and call CloseReadSide
+  // receive the FIN of the request
+  // send the FIN of the response
+
+  // The response body must be larger than the flow control window so the server
+  // must receive a window update from the client before it can finish sending
+  // it.
+  uint32_t response_body_size =
+      2 * client_config_.GetInitialStreamFlowControlWindowToSend();
+  QuicString response_body(response_body_size, 'a');
+
+  StreamWithErrorFactory stream_factory(response_body);
+  SetSpdyStreamFactory(&stream_factory);
+
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // A POST that gets an early error response, after the headers are received
+  // and before the body is received, due to invalid content-length.
+  // Set an invalid content-length, so the request will receive an early 500
+  // response.
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/garbage";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  headers["content-length"] = "-1";
+
+  // The body must be large enough that the FIN will be in a different packet
+  // than the end of the headers, but short enough to not require a flow control
+  // update.  This allows headers processing to trigger the error response
+  // before the request FIN is processed but receive the request FIN before the
+  // response is sent completely.
+  const uint32_t kRequestBodySize = kMaxPacketSize + 10;
+  QuicString request_body(kRequestBodySize, 'a');
+
+  // Send the request.
+  client_->SendMessage(headers, request_body);
+  client_->WaitForResponse();
+  EXPECT_EQ("500", client_->response_headers()->find(":status")->second);
+
+  // Pause the server so we can access the server's internals without races.
+  server_thread_->Pause();
+
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  QuicDispatcher::SessionMap const& map =
+      QuicDispatcherPeer::session_map(dispatcher);
+  auto it = map.begin();
+  EXPECT_TRUE(it != map.end());
+  QuicSession* server_session = it->second.get();
+
+  // The stream is not waiting for the arrival of the peer's final offset.
+  EXPECT_EQ(
+      0u, QuicSessionPeer::GetLocallyClosedStreamsHighestOffset(server_session)
+              .size());
+
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTestWithTls, Trailers) {
+  // Test sending and receiving HTTP/2 Trailers (trailing HEADERS frames).
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // Set reordering to ensure that Trailers arriving before body is ok.
+  SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+  SetReorderPercentage(30);
+
+  // Add a response with headers, body, and trailers.
+  const QuicString kBody = "body content";
+
+  SpdyHeaderBlock headers;
+  headers[":status"] = "200";
+  headers[":version"] = "HTTP/1.1";
+  headers["content-length"] = QuicTextUtils::Uint64ToString(kBody.size());
+
+  SpdyHeaderBlock trailers;
+  trailers["some-trailing-header"] = "trailing-header-value";
+
+  memory_cache_backend_.AddResponse(server_hostname_, "/trailer_url",
+                                    std::move(headers), kBody,
+                                    trailers.Clone());
+
+  EXPECT_EQ(kBody, client_->SendSynchronousRequest("/trailer_url"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+  EXPECT_EQ(trailers, client_->response_trailers());
+}
+
+class EndToEndTestServerPush : public EndToEndTest {
+ protected:
+  const size_t kNumMaxStreams = 10;
+
+  EndToEndTestServerPush() : EndToEndTest() {
+    client_config_.SetMaxIncomingDynamicStreamsToSend(kNumMaxStreams);
+    server_config_.SetMaxIncomingDynamicStreamsToSend(kNumMaxStreams);
+    support_server_push_ = true;
+  }
+
+  // Add a request with its response and |num_resources| push resources into
+  // cache.
+  // If |resource_size| == 0, response body of push resources use default string
+  // concatenating with resource url. Otherwise, generate a string of
+  // |resource_size| as body.
+  void AddRequestAndResponseWithServerPush(QuicString host,
+                                           QuicString path,
+                                           QuicString response_body,
+                                           QuicString* push_urls,
+                                           const size_t num_resources,
+                                           const size_t resource_size) {
+    bool use_large_response = resource_size != 0;
+    QuicString large_resource;
+    if (use_large_response) {
+      // Generate a response common body larger than flow control window for
+      // push response.
+      large_resource = QuicString(resource_size, 'a');
+    }
+    std::list<QuicBackendResponse::ServerPushInfo> push_resources;
+    for (size_t i = 0; i < num_resources; ++i) {
+      QuicString url = push_urls[i];
+      QuicUrl resource_url(url);
+      QuicString body =
+          use_large_response
+              ? large_resource
+              : QuicStrCat("This is server push response body for ", url);
+      SpdyHeaderBlock response_headers;
+      response_headers[":version"] = "HTTP/1.1";
+      response_headers[":status"] = "200";
+      response_headers["content-length"] =
+          QuicTextUtils::Uint64ToString(body.size());
+      push_resources.push_back(QuicBackendResponse::ServerPushInfo(
+          resource_url, std::move(response_headers), kV3LowestPriority, body));
+    }
+
+    memory_cache_backend_.AddSimpleResponseWithServerPushResources(
+        host, path, 200, response_body, push_resources);
+  }
+};
+
+// Run all server push end to end tests with all supported versions.
+INSTANTIATE_TEST_SUITE_P(EndToEndTestsServerPush,
+                         EndToEndTestServerPush,
+                         ::testing::ValuesIn(GetTestParams(false, false)));
+
+TEST_P(EndToEndTestServerPush, ServerPush) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // Set reordering to ensure that body arriving before PUSH_PROMISE is ok.
+  SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+  SetReorderPercentage(30);
+
+  // Add a response with headers, body, and push resources.
+  const QuicString kBody = "body content";
+  size_t kNumResources = 4;
+  QuicString push_urls[] = {"https://example.com/font.woff",
+                            "https://example.com/script.js",
+                            "https://fonts.example.com/font.woff",
+                            "https://example.com/logo-hires.jpg"};
+  AddRequestAndResponseWithServerPush("example.com", "/push_example", kBody,
+                                      push_urls, kNumResources, 0);
+
+  client_->client()->set_response_listener(
+      std::unique_ptr<QuicSpdyClientBase::ResponseListener>(
+          new TestResponseListener));
+
+  QUIC_DVLOG(1) << "send request for /push_example";
+  EXPECT_EQ(kBody, client_->SendSynchronousRequest(
+                       "https://example.com/push_example"));
+  QuicHeadersStream* headers_stream = QuicSpdySessionPeer::GetHeadersStream(
+      client_->client()->client_session());
+  QuicStreamSequencer* sequencer = QuicStreamPeer::sequencer(headers_stream);
+  // Headers stream's sequencer buffer shouldn't be released because server push
+  // hasn't finished yet.
+  EXPECT_TRUE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+
+  for (const QuicString& url : push_urls) {
+    QUIC_DVLOG(1) << "send request for pushed stream on url " << url;
+    QuicString expected_body =
+        QuicStrCat("This is server push response body for ", url);
+    QuicString response_body = client_->SendSynchronousRequest(url);
+    QUIC_DVLOG(1) << "response body " << response_body;
+    EXPECT_EQ(expected_body, response_body);
+  }
+  EXPECT_FALSE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+}
+
+TEST_P(EndToEndTestServerPush, ServerPushUnderLimit) {
+  // Tests that sending a request which has 4 push resources will trigger server
+  // to push those 4 resources and client can handle pushed resources and match
+  // them with requests later.
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // Set reordering to ensure that body arriving before PUSH_PROMISE is ok.
+  SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+  SetReorderPercentage(30);
+
+  // Add a response with headers, body, and push resources.
+  const QuicString kBody = "body content";
+  size_t const kNumResources = 4;
+  QuicString push_urls[] = {
+      "https://example.com/font.woff",
+      "https://example.com/script.js",
+      "https://fonts.example.com/font.woff",
+      "https://example.com/logo-hires.jpg",
+  };
+  AddRequestAndResponseWithServerPush("example.com", "/push_example", kBody,
+                                      push_urls, kNumResources, 0);
+  client_->client()->set_response_listener(
+      std::unique_ptr<QuicSpdyClientBase::ResponseListener>(
+          new TestResponseListener));
+
+  // Send the first request: this will trigger the server to send all the push
+  // resources associated with this request, and these will be cached by the
+  // client.
+  EXPECT_EQ(kBody, client_->SendSynchronousRequest(
+                       "https://example.com/push_example"));
+
+  for (const QuicString& url : push_urls) {
+    // Sending subsequent requesets will not actually send anything on the wire,
+    // as the responses are already in the client's cache.
+    QUIC_DVLOG(1) << "send request for pushed stream on url " << url;
+    QuicString expected_body =
+        QuicStrCat("This is server push response body for ", url);
+    QuicString response_body = client_->SendSynchronousRequest(url);
+    QUIC_DVLOG(1) << "response body " << response_body;
+    EXPECT_EQ(expected_body, response_body);
+  }
+  // Expect only original request has been sent and push responses have been
+  // received as normal response.
+  EXPECT_EQ(1u, client_->num_requests());
+  EXPECT_EQ(1u + kNumResources, client_->num_responses());
+}
+
+TEST_P(EndToEndTestServerPush, ServerPushOverLimitNonBlocking) {
+  // Tests that when streams are not blocked by flow control or congestion
+  // control, pushing even more resources than max number of open outgoing
+  // streams should still work because all response streams get closed
+  // immediately after pushing resources.
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // Set reordering to ensure that body arriving before PUSH_PROMISE is ok.
+  SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+  SetReorderPercentage(30);
+
+  // Add a response with headers, body, and push resources.
+  const QuicString kBody = "body content";
+
+  // One more resource than max number of outgoing stream of this session.
+  const size_t kNumResources = 1 + kNumMaxStreams;  // 11.
+  QuicString push_urls[11];
+  for (size_t i = 0; i < kNumResources; ++i) {
+    push_urls[i] = QuicStrCat("https://example.com/push_resources", i);
+  }
+  AddRequestAndResponseWithServerPush("example.com", "/push_example", kBody,
+                                      push_urls, kNumResources, 0);
+  client_->client()->set_response_listener(
+      std::unique_ptr<QuicSpdyClientBase::ResponseListener>(
+          new TestResponseListener));
+
+  // Send the first request: this will trigger the server to send all the push
+  // resources associated with this request, and these will be cached by the
+  // client.
+  EXPECT_EQ(kBody, client_->SendSynchronousRequest(
+                       "https://example.com/push_example"));
+
+  for (const QuicString& url : push_urls) {
+    // Sending subsequent requesets will not actually send anything on the wire,
+    // as the responses are already in the client's cache.
+    EXPECT_EQ(QuicStrCat("This is server push response body for ", url),
+              client_->SendSynchronousRequest(url));
+  }
+
+  // Only 1 request should have been sent.
+  EXPECT_EQ(1u, client_->num_requests());
+  // The responses to the original request and all the promised resources
+  // should have been received.
+  EXPECT_EQ(12u, client_->num_responses());
+}
+
+TEST_P(EndToEndTestServerPush, ServerPushOverLimitWithBlocking) {
+  // Tests that when server tries to send more large resources(large enough to
+  // be blocked by flow control window or congestion control window) than max
+  // open outgoing streams , server can open upto max number of outgoing
+  // streams for them, and the rest will be queued up.
+
+  // Reset flow control windows.
+  size_t kFlowControlWnd = 20 * 1024;  // 20KB.
+  // Response body is larger than 1 flow controlblock window.
+  size_t kBodySize = kFlowControlWnd * 2;
+  set_client_initial_stream_flow_control_receive_window(kFlowControlWnd);
+  // Make sure conntection level flow control window is large enough not to
+  // block data being sent out though they will be blocked by stream level one.
+  set_client_initial_session_flow_control_receive_window(
+      kBodySize * kNumMaxStreams + 1024);
+
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  // Set reordering to ensure that body arriving before PUSH_PROMISE is ok.
+  SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2));
+  SetReorderPercentage(30);
+
+  // Add a response with headers, body, and push resources.
+  const QuicString kBody = "body content";
+
+  const size_t kNumResources = kNumMaxStreams + 1;
+  QuicString push_urls[11];
+  for (size_t i = 0; i < kNumResources; ++i) {
+    push_urls[i] = QuicStrCat("http://example.com/push_resources", i);
+  }
+  AddRequestAndResponseWithServerPush("example.com", "/push_example", kBody,
+                                      push_urls, kNumResources, kBodySize);
+
+  client_->client()->set_response_listener(
+      std::unique_ptr<QuicSpdyClientBase::ResponseListener>(
+          new TestResponseListener));
+
+  client_->SendRequest("https://example.com/push_example");
+
+  // Pause after the first response arrives.
+  while (!client_->response_complete()) {
+    // Because of priority, the first response arrived should be to original
+    // request.
+    client_->WaitForResponse();
+  }
+
+  // Check server session to see if it has max number of outgoing streams opened
+  // though more resources need to be pushed.
+  server_thread_->Pause();
+  EXPECT_EQ(kNumMaxStreams, GetServerSession()->GetNumOpenOutgoingStreams());
+  server_thread_->Resume();
+
+  EXPECT_EQ(1u, client_->num_requests());
+  EXPECT_EQ(1u, client_->num_responses());
+  EXPECT_EQ(kBody, client_->response_body());
+
+  // "Send" request for a promised resources will not really send out it because
+  // its response is being pushed(but blocked). And the following ack and
+  // flow control behavior of SendSynchronousRequests()
+  // will unblock the stream to finish receiving response.
+  client_->SendSynchronousRequest(push_urls[0]);
+  EXPECT_EQ(1u, client_->num_requests());
+  EXPECT_EQ(2u, client_->num_responses());
+
+  // Do same thing for the rest 10 resources.
+  for (size_t i = 1; i < kNumResources; ++i) {
+    client_->SendSynchronousRequest(push_urls[i]);
+  }
+
+  // Because of server push, client gets all pushed resources without actually
+  // sending requests for them.
+  EXPECT_EQ(1u, client_->num_requests());
+  // Including response to original request, 12 responses in total were
+  // received.
+  EXPECT_EQ(12u, client_->num_responses());
+}
+
+// TODO(fayang): this test seems to cause net_unittests timeouts :|
+TEST_P(EndToEndTest, DISABLED_TestHugePostWithPacketLoss) {
+  // This test tests a huge post with introduced packet loss from client to
+  // server and body size greater than 4GB, making sure QUIC code does not break
+  // for 32-bit builds.
+  ServerStreamThatDropsBodyFactory stream_factory;
+  SetSpdyStreamFactory(&stream_factory);
+  ASSERT_TRUE(Initialize());
+  // Set client's epoll server's time out to 0 to make this test be finished
+  // within a short time.
+  client_->epoll_server()->set_timeout_in_us(0);
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  SetPacketLossPercentage(1);
+  // To avoid storing the whole request body in memory, use a loop to repeatedly
+  // send body size of kSizeBytes until the whole request body size is reached.
+  const int kSizeBytes = 128 * 1024;
+  // Request body size is 4G plus one more kSizeBytes.
+  int64_t request_body_size_bytes = pow(2, 32) + kSizeBytes;
+  ASSERT_LT(INT64_C(4294967296), request_body_size_bytes);
+  QuicString body(kSizeBytes, 'a');
+
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  headers["content-length"] =
+      QuicTextUtils::Uint64ToString(request_body_size_bytes);
+
+  client_->SendMessage(headers, "", /*fin=*/false);
+
+  for (int i = 0; i < request_body_size_bytes / kSizeBytes; ++i) {
+    bool fin = (i == request_body_size_bytes - 1);
+    client_->SendData(QuicString(body.data(), kSizeBytes), fin);
+    client_->client()->WaitForEvents();
+  }
+  VerifyCleanConnection(true);
+}
+
+// TODO(fayang): this test seems to cause net_unittests timeouts :|
+TEST_P(EndToEndTest, DISABLED_TestHugeResponseWithPacketLoss) {
+  // This test tests a huge response with introduced loss from server to client
+  // and body size greater than 4GB, making sure QUIC code does not break for
+  // 32-bit builds.
+  const int kSizeBytes = 128 * 1024;
+  int64_t response_body_size_bytes = pow(2, 32) + kSizeBytes;
+  ASSERT_LT(4294967296, response_body_size_bytes);
+  ServerStreamThatSendsHugeResponseFactory stream_factory(
+      response_body_size_bytes);
+  SetSpdyStreamFactory(&stream_factory);
+
+  StartServer();
+
+  // Use a quic client that drops received body.
+  QuicTestClient* client =
+      new QuicTestClient(server_address_, server_hostname_, client_config_,
+                         client_supported_versions_);
+  client->client()->set_drop_response_body(true);
+  client->UseWriter(client_writer_);
+  client->Connect();
+  client_.reset(client);
+  static QuicEpollEvent event(EPOLLOUT);
+  client_writer_->Initialize(
+      QuicConnectionPeer::GetHelper(
+          client_->client()->client_session()->connection()),
+      QuicConnectionPeer::GetAlarmFactory(
+          client_->client()->client_session()->connection()),
+      QuicMakeUnique<ClientDelegate>(client_->client()));
+  initialized_ = true;
+  ASSERT_TRUE(client_->client()->connected());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  SetPacketLossPercentage(1);
+  client_->SendRequest("/huge_response");
+  client_->WaitForResponse();
+  // TODO(fayang): Fix this test to work with stateless rejects.
+  if (!BothSidesSupportStatelessRejects()) {
+    VerifyCleanConnection(true);
+  }
+}
+
+// Regression test for b/111515567
+TEST_P(EndToEndTest, AgreeOnStopWaiting) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  server_thread_->Pause();
+  QuicConnection* server_connection = GetServerConnection();
+  // Verify client and server connections agree on the value of
+  // no_stop_waiting_frames.
+  EXPECT_EQ(QuicConnectionPeer::GetNoStopWaitingFrames(client_connection),
+            QuicConnectionPeer::GetNoStopWaitingFrames(server_connection));
+  server_thread_->Resume();
+}
+
+// Regression test for b/111515567
+TEST_P(EndToEndTest, AgreeOnStopWaitingWithNoStopWaitingOption) {
+  QuicTagVector options;
+  options.push_back(kNSTP);
+  client_config_.SetConnectionOptionsToSend(options);
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  server_thread_->Pause();
+  QuicConnection* server_connection = GetServerConnection();
+  // Verify client and server connections agree on the value of
+  // no_stop_waiting_frames.
+  EXPECT_EQ(QuicConnectionPeer::GetNoStopWaitingFrames(client_connection),
+            QuicConnectionPeer::GetNoStopWaitingFrames(server_connection));
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, ReleaseHeadersStreamBufferWhenIdle) {
+  // Tests that when client side has no active request and no waiting
+  // PUSH_PROMISE, its headers stream's sequencer buffer should be released.
+  ASSERT_TRUE(Initialize());
+  client_->SendSynchronousRequest("/foo");
+  QuicHeadersStream* headers_stream = QuicSpdySessionPeer::GetHeadersStream(
+      client_->client()->client_session());
+  QuicStreamSequencer* sequencer = QuicStreamPeer::sequencer(headers_stream);
+  EXPECT_FALSE(QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(sequencer));
+}
+
+TEST_P(EndToEndTest, WayTooLongRequestHeaders) {
+  ASSERT_TRUE(Initialize());
+  SpdyHeaderBlock headers;
+  headers[":method"] = "GET";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  headers["key"] = QuicString(64 * 1024, 'a');
+
+  client_->SendMessage(headers, "");
+  client_->WaitForResponse();
+  EXPECT_EQ(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
+            client_->connection_error());
+}
+
+class WindowUpdateObserver : public QuicConnectionDebugVisitor {
+ public:
+  WindowUpdateObserver() : num_window_update_frames_(0), num_ping_frames_(0) {}
+
+  size_t num_window_update_frames() const { return num_window_update_frames_; }
+
+  size_t num_ping_frames() const { return num_ping_frames_; }
+
+  void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame,
+                           const QuicTime& receive_time) override {
+    ++num_window_update_frames_;
+  }
+
+  void OnPingFrame(const QuicPingFrame& frame) override { ++num_ping_frames_; }
+
+ private:
+  size_t num_window_update_frames_;
+  size_t num_ping_frames_;
+};
+
+TEST_P(EndToEndTest, WindowUpdateInAck) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  WindowUpdateObserver observer;
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  client_connection->set_debug_visitor(&observer);
+  // 100KB body.
+  QuicString body(100 * 1024, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  EXPECT_EQ(kFooResponseBody,
+            client_->SendCustomSynchronousRequest(headers, body));
+  client_->Disconnect();
+  EXPECT_LT(0u, observer.num_window_update_frames());
+  EXPECT_EQ(0u, observer.num_ping_frames());
+}
+
+TEST_P(EndToEndTest, SendStatelessResetTokenInShlo) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  QuicConfig* config = client_->client()->session()->config();
+  EXPECT_TRUE(config->HasReceivedStatelessResetToken());
+  EXPECT_EQ(QuicUtils::GenerateStatelessResetToken(
+                client_->client()->session()->connection()->connection_id()),
+            config->ReceivedStatelessResetToken());
+  client_->Disconnect();
+}
+
+// Regression test for b/116200989.
+TEST_P(EndToEndTest,
+       SendStatelessResetIfServerConnectionClosedLocallyDuringHandshake) {
+  connect_to_server_on_initialize_ = false;
+  ASSERT_TRUE(Initialize());
+
+  server_thread_->Pause();
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  ASSERT_EQ(0u, dispatcher->session_map().size());
+  // Note: this writer will only used by the server connection, not the time
+  // wait list.
+  QuicDispatcherPeer::UseWriter(
+      dispatcher,
+      // This cause the first server-sent packet, a.k.a REJ, to fail.
+      new BadPacketWriter(/*packet_causing_write_error=*/0, EPERM));
+  server_thread_->Resume();
+
+  client_.reset(CreateQuicClient(client_writer_));
+  EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+
+  if (client_->client()->client_session()->connection()->transport_version() >
+      QUIC_VERSION_43) {
+    EXPECT_EQ(QUIC_HANDSHAKE_FAILED, client_->connection_error());
+  } else {
+    EXPECT_EQ(QUIC_PUBLIC_RESET, client_->connection_error());
+  }
+}
+
+// Regression test for b/116200989.
+TEST_P(EndToEndTest,
+       SendStatelessResetIfServerConnectionClosedLocallyAfterHandshake) {
+  // Prevent the connection from expiring in the time wait list.
+  FLAGS_quic_time_wait_list_seconds = 10000;
+  connect_to_server_on_initialize_ = false;
+  ASSERT_TRUE(Initialize());
+
+  // big_response_body is 64K, which is about 48 full-sized packets.
+  const size_t kBigResponseBodySize = 65536;
+  QuicData big_response_body(new char[kBigResponseBodySize](),
+                             kBigResponseBodySize, /*owns_buffer=*/true);
+  AddToCache("/big_response", 200, big_response_body.AsStringPiece());
+
+  server_thread_->Pause();
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  ASSERT_EQ(0u, dispatcher->session_map().size());
+  QuicDispatcherPeer::UseWriter(
+      dispatcher,
+      // This will cause an server write error with EPERM, while sending the
+      // response for /big_response.
+      new BadPacketWriter(/*packet_causing_write_error=*/20, EPERM));
+  server_thread_->Resume();
+
+  client_.reset(CreateQuicClient(client_writer_));
+
+  // First, a /foo request with small response should succeed.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Second, a /big_response request with big response should fail.
+  EXPECT_LT(client_->SendSynchronousRequest("/big_response").length(),
+            kBigResponseBodySize);
+  EXPECT_EQ(QUIC_PUBLIC_RESET, client_->connection_error());
+}
+
+// Regression test of b/70782529.
+TEST_P(EndToEndTest, DoNotCrashOnPacketWriteError) {
+  ASSERT_TRUE(Initialize());
+  BadPacketWriter* bad_writer =
+      new BadPacketWriter(/*packet_causing_write_error=*/5,
+                          /*error_code=*/90);
+  std::unique_ptr<QuicTestClient> client(CreateQuicClient(bad_writer));
+
+  // 1 MB body.
+  QuicString body(1024 * 1024, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  client->SendCustomSynchronousRequest(headers, body);
+}
+
+// Regression test for b/71711996. This test sends a connectivity probing packet
+// as its last sent packet, and makes sure the server's ACK of that packet does
+// not cause the client to fail.
+TEST_P(EndToEndTest, LastPacketSentIsConnectivityProbing) {
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+
+  // Wait for the client's ACK (of the response) to be received by the server.
+  client_->WaitForDelayedAcks();
+
+  // We are sending a connectivity probing packet from an unchanged client
+  // address, so the server will not respond to us with a connectivity probing
+  // packet, however the server should send an ack-only packet to us.
+  client_->SendConnectivityProbing();
+
+  // Wait for the server's last ACK to be received by the client.
+  client_->WaitForDelayedAcks();
+}
+
+TEST_P(EndToEndTest, PreSharedKey) {
+  client_config_.set_max_time_before_crypto_handshake(
+      QuicTime::Delta::FromSeconds(1));
+  client_config_.set_max_idle_time_before_crypto_handshake(
+      QuicTime::Delta::FromSeconds(1));
+  pre_shared_key_client_ = "foobar";
+  pre_shared_key_server_ = "foobar";
+  ASSERT_TRUE(Initialize());
+
+  ASSERT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+// TODO: reenable once we have a way to make this run faster.
+TEST_P(EndToEndTest, QUIC_TEST_DISABLED_IN_CHROME(PreSharedKeyMismatch)) {
+  client_config_.set_max_time_before_crypto_handshake(
+      QuicTime::Delta::FromSeconds(1));
+  client_config_.set_max_idle_time_before_crypto_handshake(
+      QuicTime::Delta::FromSeconds(1));
+  pre_shared_key_client_ = "foo";
+  pre_shared_key_server_ = "bar";
+  // One of two things happens when Initialize() returns:
+  // 1. Crypto handshake has completed, and it is unsuccessful. Initialize()
+  //    returns false.
+  // 2. Crypto handshake has not completed, Initialize() returns true. The call
+  //    to WaitForCryptoHandshakeConfirmed() will wait for the handshake and
+  //    return whether it is successful.
+  ASSERT_FALSE(Initialize() &&
+               client_->client()->WaitForCryptoHandshakeConfirmed());
+  EXPECT_EQ(QUIC_HANDSHAKE_TIMEOUT, client_->connection_error());
+}
+
+// TODO: reenable once we have a way to make this run faster.
+TEST_P(EndToEndTest, QUIC_TEST_DISABLED_IN_CHROME(PreSharedKeyNoClient)) {
+  client_config_.set_max_time_before_crypto_handshake(
+      QuicTime::Delta::FromSeconds(1));
+  client_config_.set_max_idle_time_before_crypto_handshake(
+      QuicTime::Delta::FromSeconds(1));
+  pre_shared_key_server_ = "foobar";
+  ASSERT_FALSE(Initialize() &&
+               client_->client()->WaitForCryptoHandshakeConfirmed());
+  EXPECT_EQ(QUIC_HANDSHAKE_TIMEOUT, client_->connection_error());
+}
+
+// TODO: reenable once we have a way to make this run faster.
+TEST_P(EndToEndTest, QUIC_TEST_DISABLED_IN_CHROME(PreSharedKeyNoServer)) {
+  client_config_.set_max_time_before_crypto_handshake(
+      QuicTime::Delta::FromSeconds(1));
+  client_config_.set_max_idle_time_before_crypto_handshake(
+      QuicTime::Delta::FromSeconds(1));
+  pre_shared_key_client_ = "foobar";
+  ASSERT_FALSE(Initialize() &&
+               client_->client()->WaitForCryptoHandshakeConfirmed());
+  EXPECT_EQ(QUIC_HANDSHAKE_TIMEOUT, client_->connection_error());
+}
+
+TEST_P(EndToEndTest, RequestAndStreamRstInOnePacket) {
+  // Regression test for b/80234898.
+  ASSERT_TRUE(Initialize());
+
+  // INCOMPLETE_RESPONSE will cause the server to not to send the trailer
+  // (and the FIN) after the response body.
+  QuicString response_body(1305, 'a');
+  SpdyHeaderBlock response_headers;
+  response_headers[":status"] = QuicTextUtils::Uint64ToString(200);
+  response_headers["content-length"] =
+      QuicTextUtils::Uint64ToString(response_body.length());
+  memory_cache_backend_.AddSpecialResponse(
+      server_hostname_, "/test_url", std::move(response_headers), response_body,
+      QuicBackendResponse::INCOMPLETE_RESPONSE);
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  client_->WaitForDelayedAcks();
+
+  QuicSession* session = client_->client()->client_session();
+  const QuicPacketCount packets_sent_before =
+      session->connection()->GetStats().packets_sent;
+
+  client_->SendRequestAndRstTogether("/test_url");
+
+  // Expect exactly one packet is sent from the block above.
+  ASSERT_EQ(packets_sent_before + 1,
+            session->connection()->GetStats().packets_sent);
+
+  // Wait for the connection to become idle.
+  client_->WaitForDelayedAcks();
+
+  // The real expectation is the test does not crash or timeout.
+  EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+TEST_P(EndToEndTest, ResetStreamOnTtlExpires) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  if (!client_->client()->client_session()->session_decides_what_to_write()) {
+    return;
+  }
+  SetPacketLossPercentage(30);
+
+  QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+  // Set a TTL which expires immediately.
+  stream->MaybeSetTtl(QuicTime::Delta::FromMicroseconds(1));
+
+  // 1 MB body.
+  QuicString body(1024 * 1024, 'a');
+  stream->WriteOrBufferBody(body, true);
+  client_->WaitForResponse();
+  EXPECT_EQ(QUIC_STREAM_TTL_EXPIRED, client_->stream_error());
+}
+
+TEST_P(EndToEndTest, SendMessages) {
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  QuicSession* client_session = client_->client()->client_session();
+  QuicConnection* client_connection = client_session->connection();
+  if (client_connection->transport_version() <= QUIC_VERSION_44) {
+    return;
+  }
+
+  SetPacketLossPercentage(30);
+  ASSERT_GT(kMaxPacketSize, client_session->GetLargestMessagePayload());
+  ASSERT_LT(0, client_session->GetLargestMessagePayload());
+
+  QuicString message_string(kMaxPacketSize, 'a');
+  QuicStringPiece message_buffer(message_string);
+  QuicRandom* random =
+      QuicConnectionPeer::GetHelper(client_connection)->GetRandomGenerator();
+  QuicMemSliceStorage storage(nullptr, 0, nullptr, 0);
+  {
+    QuicConnection::ScopedPacketFlusher flusher(
+        client_session->connection(), QuicConnection::SEND_ACK_IF_PENDING);
+    // Verify the largest message gets successfully sent.
+    EXPECT_EQ(MessageResult(MESSAGE_STATUS_SUCCESS, 1),
+              client_session->SendMessage(MakeSpan(
+                  client_session->connection()
+                      ->helper()
+                      ->GetStreamSendBufferAllocator(),
+                  QuicStringPiece(message_buffer.data(),
+                                  client_session->GetLargestMessagePayload()),
+                  &storage)));
+    // Send more messages with size (0, largest_payload] until connection is
+    // write blocked.
+    const int kTestMaxNumberOfMessages = 100;
+    for (size_t i = 2; i <= kTestMaxNumberOfMessages; ++i) {
+      size_t message_length =
+          random->RandUint64() % client_session->GetLargestMessagePayload() + 1;
+      MessageResult result = client_session->SendMessage(MakeSpan(
+          client_session->connection()
+              ->helper()
+              ->GetStreamSendBufferAllocator(),
+          QuicStringPiece(message_buffer.data(), message_length), &storage));
+      if (result.status == MESSAGE_STATUS_BLOCKED) {
+        // Connection is write blocked.
+        break;
+      }
+      EXPECT_EQ(MessageResult(MESSAGE_STATUS_SUCCESS, i), result);
+    }
+  }
+
+  client_->WaitForDelayedAcks();
+  EXPECT_EQ(
+      MESSAGE_STATUS_TOO_LARGE,
+      client_session
+          ->SendMessage(MakeSpan(
+              client_session->connection()
+                  ->helper()
+                  ->GetStreamSendBufferAllocator(),
+              QuicStringPiece(message_buffer.data(),
+                              client_session->GetLargestMessagePayload() + 1),
+              &storage))
+          .status);
+  EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+}
+
+class EndToEndPacketReorderingTest : public EndToEndTest {
+ public:
+  void CreateClientWithWriter() override {
+    QUIC_LOG(ERROR) << "create client with reorder_writer_";
+    reorder_writer_ = new PacketReorderingWriter();
+    client_.reset(EndToEndTest::CreateQuicClient(reorder_writer_));
+  }
+
+  void SetUp() override {
+    // Don't initialize client writer in base class.
+    server_writer_ = new PacketDroppingTestWriter();
+  }
+
+ protected:
+  PacketReorderingWriter* reorder_writer_;
+};
+
+INSTANTIATE_TEST_SUITE_P(EndToEndPacketReorderingTests,
+                         EndToEndPacketReorderingTest,
+                         testing::ValuesIn(GetTestParams(false, false)));
+
+TEST_P(EndToEndPacketReorderingTest, ReorderedConnectivityProbing) {
+  ASSERT_TRUE(Initialize());
+
+  // Finish one request to make sure handshake established.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+  // Wait for the connection to become idle, to make sure the packet gets
+  // delayed is the connectivity probing packet.
+  client_->WaitForDelayedAcks();
+
+  QuicSocketAddress old_addr =
+      client_->client()->network_helper()->GetLatestClientAddress();
+
+  // Migrate socket to the new IP address.
+  QuicIpAddress new_host = TestLoopback(2);
+  EXPECT_NE(old_addr.host(), new_host);
+  ASSERT_TRUE(client_->client()->MigrateSocket(new_host));
+
+  // Write a connectivity probing after the next /foo request.
+  reorder_writer_->SetDelay(1);
+  client_->SendConnectivityProbing();
+
+  ASSERT_TRUE(client_->MigrateSocketWithSpecifiedPort(old_addr.host(),
+                                                      old_addr.port()));
+
+  // The (delayed) connectivity probing will be sent after this request.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+  // Send yet another request after the connectivity probing, when this request
+  // returns, the probing is guaranteed to have been received by the server, and
+  // the server's response to probing is guaranteed to have been received by the
+  // client.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+  server_thread_->Pause();
+  QuicConnection* server_connection = GetServerConnection();
+  EXPECT_EQ(1u,
+            server_connection->GetStats().num_connectivity_probing_received);
+  server_thread_->Resume();
+
+  QuicConnection* client_connection =
+      client_->client()->client_session()->connection();
+  EXPECT_EQ(1u,
+            client_connection->GetStats().num_connectivity_probing_received);
+}
+
+TEST_P(EndToEndPacketReorderingTest, Buffer0RttRequest) {
+  ASSERT_TRUE(Initialize());
+  // Finish one request to make sure handshake established.
+  client_->SendSynchronousRequest("/foo");
+  // Disconnect for next 0-rtt request.
+  client_->Disconnect();
+
+  // Client get valid STK now. Do a 0-rtt request.
+  // Buffer a CHLO till another packets sent out.
+  reorder_writer_->SetDelay(1);
+  // Only send out a CHLO.
+  client_->client()->Initialize();
+  client_->client()->StartConnect();
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  ASSERT_TRUE(client_->client()->connected());
+
+  // Send a request before handshake finishes.
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/bar";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  client_->SendMessage(headers, "");
+  client_->WaitForResponse();
+  EXPECT_EQ(kBarResponseBody, client_->response_body());
+  QuicConnectionStats client_stats =
+      client_->client()->client_session()->connection()->GetStats();
+  EXPECT_EQ(0u, client_stats.packets_lost);
+  if (ServerSendsVersionNegotiation()) {
+    EXPECT_EQ(2, client_->client()->GetNumSentClientHellos());
+  } else {
+    EXPECT_EQ(1, client_->client()->GetNumSentClientHellos());
+  }
+}
+
+// Test that STOP_SENDING makes it to the other side. Set up a client & server,
+// create a stream (do not close it), and then send a STOP_SENDING from one
+// side. The other side should get a call to QuicStream::OnStopSending.
+// (aside, test cribbed from RequestAndStreamRstInOnePacket)
+TEST_P(EndToEndTest, SimpleStopSendingTest) {
+  const uint16_t kStopSendingTestCode = 123;
+  ASSERT_TRUE(Initialize());
+  if (negotiated_version_.transport_version != QUIC_VERSION_99) {
+    return;
+  }
+  QuicSession* client_session = client_->client()->client_session();
+  ASSERT_NE(nullptr, client_session);
+  QuicConnection* client_connection = client_session->connection();
+  ASSERT_NE(nullptr, client_connection);
+
+  // STOP_SENDING will cause the server to not to send the trailer
+  // (and the FIN) after the response body. Instead, it sends a STOP_SENDING
+  // frame for the stream.
+  QuicString response_body(1305, 'a');
+  SpdyHeaderBlock response_headers;
+  response_headers[":status"] = QuicTextUtils::Uint64ToString(200);
+  response_headers["content-length"] =
+      QuicTextUtils::Uint64ToString(response_body.length());
+  memory_cache_backend_.AddStopSendingResponse(
+      server_hostname_, "/test_url", std::move(response_headers), response_body,
+      kStopSendingTestCode);
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  client_->WaitForDelayedAcks();
+
+  QuicSession* session = client_->client()->client_session();
+  const QuicPacketCount packets_sent_before =
+      session->connection()->GetStats().packets_sent;
+
+  QuicStreamId stream_id = session->next_outgoing_bidirectional_stream_id();
+  client_->SendRequest("/test_url");
+
+  // Expect exactly one packet is sent from the block above.
+  ASSERT_EQ(packets_sent_before + 1,
+            session->connection()->GetStats().packets_sent);
+
+  // Wait for the connection to become idle.
+  client_->WaitForDelayedAcks();
+
+  // The real expectation is the test does not crash or timeout.
+  EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
+  // And that the stop-sending code is received.
+  QuicSimpleClientStream* client_stream =
+      static_cast<QuicSimpleClientStream*>(client_->latest_created_stream());
+  ASSERT_NE(nullptr, client_stream);
+  // Make sure we have the correct stream
+  EXPECT_EQ(stream_id, client_stream->id());
+  EXPECT_EQ(kStopSendingTestCode, client_stream->last_stop_sending_code());
+}
+
+TEST_P(EndToEndTest, SimpleStopSendingRstStreamTest) {
+  ASSERT_TRUE(Initialize());
+
+  // Send a request without a fin, to keep the stream open
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+  client_->SendMessage(headers, "", /*fin=*/false);
+  // Stream should be open
+  ASSERT_NE(nullptr, client_->latest_created_stream());
+  EXPECT_FALSE(
+      QuicStreamPeer::write_side_closed(client_->latest_created_stream()));
+  EXPECT_FALSE(
+      QuicStreamPeer::read_side_closed(client_->latest_created_stream()));
+
+  // Send a RST_STREAM+STOP_SENDING on the stream
+  // Code is not important.
+  client_->latest_created_stream()->Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+  client_->WaitForResponse();
+
+  // Stream should be gone.
+  ASSERT_EQ(nullptr, client_->latest_created_stream());
+}
+
+class BadShloPacketWriter : public QuicPacketWriterWrapper {
+ public:
+  BadShloPacketWriter() : error_returned_(false) {}
+  ~BadShloPacketWriter() override {}
+
+  WriteResult WritePacket(const char* buffer,
+                          size_t buf_len,
+                          const QuicIpAddress& self_address,
+                          const QuicSocketAddress& peer_address,
+                          quic::PerPacketOptions* options) override {
+    const WriteResult result = QuicPacketWriterWrapper::WritePacket(
+        buffer, buf_len, self_address, peer_address, options);
+    const uint8_t type_byte = buffer[0];
+    if (!error_returned_ && (type_byte & FLAGS_LONG_HEADER) &&
+        (((type_byte & 0x30) >> 4) == 1 || (type_byte & 0x7F) == 0x7C)) {
+      QUIC_DVLOG(1) << "Return write error for ZERO_RTT_PACKET";
+      error_returned_ = true;
+      return WriteResult(WRITE_STATUS_ERROR, QUIC_EMSGSIZE);
+    }
+    return result;
+  }
+
+ private:
+  bool error_returned_;
+};
+
+TEST_P(EndToEndTest, ZeroRttProtectedConnectionClose) {
+  // This test ensures ZERO_RTT_PROTECTED connection close could close a client
+  // which has switched to forward secure.
+  connect_to_server_on_initialize_ =
+      negotiated_version_.transport_version <= QUIC_VERSION_43;
+  ASSERT_TRUE(Initialize());
+  if (negotiated_version_.transport_version <= QUIC_VERSION_43) {
+    // Only runs for IETF QUIC header.
+    return;
+  }
+  server_thread_->Pause();
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  ASSERT_EQ(0u, dispatcher->session_map().size());
+  // Note: this writer will only used by the server connection, not the time
+  // wait list.
+  QuicDispatcherPeer::UseWriter(
+      dispatcher,
+      // This causes the first server sent ZERO_RTT_PROTECTED packet (i.e.,
+      // SHLO) to be sent, but WRITE_ERROR is returned. Such that a
+      // ZERO_RTT_PROTECTED connection close would be sent to a client with
+      // encryption level FORWARD_SECURE.
+      new BadShloPacketWriter());
+  server_thread_->Resume();
+
+  client_.reset(CreateQuicClient(client_writer_));
+  EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+  // Verify ZERO_RTT_PROTECTED connection close is successfully processed by
+  // client which switches to FORWARD_SECURE.
+  EXPECT_EQ(QUIC_PACKET_WRITE_ERROR, client_->connection_error());
+}
+
+class BadShloPacketWriter2 : public QuicPacketWriterWrapper {
+ public:
+  BadShloPacketWriter2() : error_returned_(false) {}
+  ~BadShloPacketWriter2() override {}
+
+  WriteResult WritePacket(const char* buffer,
+                          size_t buf_len,
+                          const QuicIpAddress& self_address,
+                          const QuicSocketAddress& peer_address,
+                          quic::PerPacketOptions* options) override {
+    const uint8_t type_byte = buffer[0];
+    if ((type_byte & FLAGS_LONG_HEADER) &&
+        (((type_byte & 0x30) >> 4) == 1 || (type_byte & 0x7F) == 0x7C)) {
+      QUIC_DVLOG(1) << "Dropping ZERO_RTT_PACKET packet";
+      return WriteResult(WRITE_STATUS_OK, buf_len);
+    }
+    if (!error_returned_ && !(type_byte & FLAGS_LONG_HEADER)) {
+      QUIC_DVLOG(1) << "Return write error for short header packet";
+      error_returned_ = true;
+      return WriteResult(WRITE_STATUS_ERROR, QUIC_EMSGSIZE);
+    }
+    return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address,
+                                                peer_address, options);
+  }
+
+ private:
+  bool error_returned_;
+};
+
+TEST_P(EndToEndTest, ForwardSecureConnectionClose) {
+  // This test ensures ZERO_RTT_PROTECTED connection close is sent to a client
+  // which has ZERO_RTT_PROTECTED encryption level.
+  SetQuicReloadableFlag(quic_fix_termination_packets, true);
+  connect_to_server_on_initialize_ =
+      negotiated_version_.transport_version <= QUIC_VERSION_43;
+  ASSERT_TRUE(Initialize());
+  if (negotiated_version_.transport_version <= QUIC_VERSION_43) {
+    // Only runs for IETF QUIC header.
+    return;
+  }
+  server_thread_->Pause();
+  QuicDispatcher* dispatcher =
+      QuicServerPeer::GetDispatcher(server_thread_->server());
+  ASSERT_EQ(0u, dispatcher->session_map().size());
+  // Note: this writer will only used by the server connection, not the time
+  // wait list.
+  QuicDispatcherPeer::UseWriter(
+      dispatcher,
+      // This causes the all server sent ZERO_RTT_PROTECTED packets to be
+      // dropped, and first short header packet causes write error.
+      new BadShloPacketWriter2());
+  server_thread_->Resume();
+  client_.reset(CreateQuicClient(client_writer_));
+  EXPECT_EQ("", client_->SendSynchronousRequest("/foo"));
+  // Verify ZERO_RTT_PROTECTED connection close is successfully processed by
+  // client.
+  EXPECT_EQ(QUIC_PACKET_WRITE_ERROR, client_->connection_error());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
new file mode 100644
index 0000000..e697821
--- /dev/null
+++ b/quic/core/http/http_decoder.cc
@@ -0,0 +1,411 @@
+// 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/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_reader.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fallthrough.h"
+
+namespace quic {
+
+namespace {
+
+// Create a mask that sets the last |num_bits| to 1 and the rest to 0.
+inline uint8_t GetMaskFromNumBits(uint8_t num_bits) {
+  return (1u << num_bits) - 1;
+}
+
+// Extract |num_bits| from |flags| offset by |offset|.
+uint8_t ExtractBits(uint8_t flags, uint8_t num_bits, uint8_t offset) {
+  return (flags >> offset) & GetMaskFromNumBits(num_bits);
+}
+
+// Length of the type field of HTTP/3 frames.
+static const QuicByteCount kFrameTypeLength = 1;
+
+}  // namespace
+
+HttpDecoder::HttpDecoder()
+    : visitor_(nullptr),
+      state_(STATE_READING_FRAME_LENGTH),
+      current_frame_type_(0),
+      current_length_field_size_(0),
+      remaining_length_field_length_(0),
+      current_frame_length_(0),
+      remaining_frame_length_(0),
+      error_(QUIC_NO_ERROR),
+      error_detail_(""),
+      has_payload_(false) {}
+
+HttpDecoder::~HttpDecoder() {}
+
+QuicByteCount HttpDecoder::ProcessInput(const char* data, QuicByteCount len) {
+  has_payload_ = false;
+  QuicDataReader reader(data, len);
+  while (error_ == QUIC_NO_ERROR && reader.BytesRemaining() != 0) {
+    switch (state_) {
+      case STATE_READING_FRAME_LENGTH:
+        ReadFrameLength(&reader);
+        break;
+      case STATE_READING_FRAME_TYPE:
+        ReadFrameType(&reader);
+        break;
+      case STATE_READING_FRAME_PAYLOAD:
+        ReadFramePayload(&reader);
+        break;
+      case STATE_ERROR:
+        break;
+      default:
+        QUIC_BUG << "Invalid state: " << state_;
+    }
+  }
+
+  if (error_ != QUIC_NO_ERROR) {
+    return 0;
+  }
+
+  return len - reader.BytesRemaining();
+}
+
+void HttpDecoder::ReadFrameLength(QuicDataReader* reader) {
+  DCHECK_NE(0u, reader->BytesRemaining());
+  BufferFrameLength(reader);
+  if (remaining_length_field_length_ != 0) {
+    return;
+  }
+  QuicDataReader length_reader(length_buffer_.data(),
+                               current_length_field_size_);
+  if (!length_reader.ReadVarInt62(&current_frame_length_)) {
+    RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame length");
+    visitor_->OnError(this);
+    return;
+  }
+
+  state_ = STATE_READING_FRAME_TYPE;
+  remaining_frame_length_ = current_frame_length_;
+}
+
+void HttpDecoder::ReadFrameType(QuicDataReader* reader) {
+  DCHECK_NE(0u, reader->BytesRemaining());
+  if (!reader->ReadUInt8(&current_frame_type_)) {
+    RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame type");
+    return;
+  }
+
+  state_ = STATE_READING_FRAME_PAYLOAD;
+}
+
+void HttpDecoder::ReadFramePayload(QuicDataReader* reader) {
+  DCHECK_NE(0u, reader->BytesRemaining());
+  switch (current_frame_type_) {
+    case 0x0: {  // DATA
+      if (current_frame_length_ == remaining_frame_length_) {
+        visitor_->OnDataFrameStart(
+            Http3FrameLengths(current_length_field_size_ + kFrameTypeLength,
+                              current_frame_length_));
+      }
+      QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+          remaining_frame_length_, reader->BytesRemaining());
+      QuicStringPiece payload;
+      if (!reader->ReadStringPiece(&payload, bytes_to_read)) {
+        RaiseError(QUIC_INTERNAL_ERROR, "Unable to read data");
+        return;
+      }
+      has_payload_ = true;
+      visitor_->OnDataFramePayload(payload);
+      remaining_frame_length_ -= payload.length();
+      if (remaining_frame_length_ == 0) {
+        state_ = STATE_READING_FRAME_LENGTH;
+        current_length_field_size_ = 0;
+        visitor_->OnDataFrameEnd();
+      }
+      return;
+    }
+    case 0x1: {  // HEADERS
+      if (current_frame_length_ == remaining_frame_length_) {
+        visitor_->OnHeadersFrameStart();
+      }
+      QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+          remaining_frame_length_, reader->BytesRemaining());
+      QuicStringPiece payload;
+      if (!reader->ReadStringPiece(&payload, bytes_to_read)) {
+        RaiseError(QUIC_INTERNAL_ERROR, "Unable to read data");
+        return;
+      }
+      visitor_->OnHeadersFramePayload(payload);
+      remaining_frame_length_ -= payload.length();
+      if (remaining_frame_length_ == 0) {
+        state_ = STATE_READING_FRAME_LENGTH;
+        current_length_field_size_ = 0;
+        visitor_->OnHeadersFrameEnd(current_frame_length_);
+      }
+      return;
+    }
+    case 0x2: {  // PRIORITY
+      // TODO(rch): avoid buffering if the entire frame is present, and
+      // instead parse directly out of |reader|.
+      BufferFramePayload(reader);
+      if (remaining_frame_length_ == 0) {
+        PriorityFrame frame;
+        QuicDataReader reader(buffer_.data(), current_frame_length_);
+        if (!ParsePriorityFrame(&reader, &frame)) {
+          return;
+        }
+        visitor_->OnPriorityFrame(frame);
+        state_ = STATE_READING_FRAME_LENGTH;
+        current_length_field_size_ = 0;
+      }
+      return;
+    }
+    case 0x3: {  // CANCEL_PUSH
+      // TODO(rch): Handle partial delivery.
+      BufferFramePayload(reader);
+      if (remaining_frame_length_ == 0) {
+        CancelPushFrame frame;
+        QuicDataReader reader(buffer_.data(), current_frame_length_);
+        if (!reader.ReadVarInt62(&frame.push_id)) {
+          RaiseError(QUIC_INTERNAL_ERROR, "Unable to read push_id");
+          return;
+        }
+        visitor_->OnCancelPushFrame(frame);
+        state_ = STATE_READING_FRAME_LENGTH;
+        current_length_field_size_ = 0;
+      }
+      return;
+    }
+    case 0x4: {  // SETTINGS
+      // TODO(rch): Handle overly large SETTINGS frames. Either:
+      // 1. Impose a limit on SETTINGS frame size, and close the connection if
+      //    exceeded
+      // 2. Implement a streaming parsing mode.
+      BufferFramePayload(reader);
+      if (remaining_frame_length_ == 0) {
+        SettingsFrame frame;
+        QuicDataReader reader(buffer_.data(), current_frame_length_);
+        if (!ParseSettingsFrame(&reader, &frame)) {
+          return;
+        }
+        visitor_->OnSettingsFrame(frame);
+        state_ = STATE_READING_FRAME_LENGTH;
+        current_length_field_size_ = 0;
+      }
+      return;
+    }
+    case 0x5: {  // PUSH_PROMISE
+      if (current_frame_length_ == remaining_frame_length_) {
+        QuicByteCount bytes_remaining = reader->BytesRemaining();
+        PushId push_id;
+        // TODO(rch): Handle partial delivery of this field.
+        if (!reader->ReadVarInt62(&push_id)) {
+          RaiseError(QUIC_INTERNAL_ERROR, "Unable to read push_id");
+          return;
+        }
+        remaining_frame_length_ -= bytes_remaining - reader->BytesRemaining();
+        visitor_->OnPushPromiseFrameStart(push_id);
+      }
+      QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+          remaining_frame_length_, reader->BytesRemaining());
+      if (bytes_to_read == 0) {
+        return;
+      }
+      QuicStringPiece payload;
+      if (!reader->ReadStringPiece(&payload, bytes_to_read)) {
+        RaiseError(QUIC_INTERNAL_ERROR, "Unable to read data");
+        return;
+      }
+      visitor_->OnPushPromiseFramePayload(payload);
+      remaining_frame_length_ -= payload.length();
+      if (remaining_frame_length_ == 0) {
+        state_ = STATE_READING_FRAME_LENGTH;
+        current_length_field_size_ = 0;
+        visitor_->OnPushPromiseFrameEnd();
+      }
+      return;
+    }
+    case 0x7: {  // GOAWAY
+      BufferFramePayload(reader);
+      if (remaining_frame_length_ == 0) {
+        GoAwayFrame frame;
+        QuicDataReader reader(buffer_.data(), current_frame_length_);
+        uint64_t stream_id;
+        if (!reader.ReadVarInt62(&stream_id)) {
+          RaiseError(QUIC_INTERNAL_ERROR, "Unable to read GOAWAY stream_id");
+          return;
+        }
+        frame.stream_id = stream_id;
+        visitor_->OnGoAwayFrame(frame);
+        state_ = STATE_READING_FRAME_LENGTH;
+        current_length_field_size_ = 0;
+      }
+      return;
+    }
+
+    case 0xD: {  // MAX_PUSH_ID
+      // TODO(rch): Handle partial delivery.
+      BufferFramePayload(reader);
+      if (remaining_frame_length_ == 0) {
+        QuicDataReader reader(buffer_.data(), current_frame_length_);
+        MaxPushIdFrame frame;
+        if (!reader.ReadVarInt62(&frame.push_id)) {
+          RaiseError(QUIC_INTERNAL_ERROR, "Unable to read push_id");
+          return;
+        }
+        visitor_->OnMaxPushIdFrame(frame);
+        state_ = STATE_READING_FRAME_LENGTH;
+        current_length_field_size_ = 0;
+      }
+      return;
+    }
+
+    case 0xE: {  // DUPLICATE_PUSH
+      BufferFramePayload(reader);
+      if (remaining_frame_length_ != 0) {
+        return;
+      }
+      QuicDataReader reader(buffer_.data(), current_frame_length_);
+      DuplicatePushFrame frame;
+      if (!reader.ReadVarInt62(&frame.push_id)) {
+        RaiseError(QUIC_INTERNAL_ERROR, "Unable to read push_id");
+        return;
+      }
+      visitor_->OnDuplicatePushFrame(frame);
+      state_ = STATE_READING_FRAME_LENGTH;
+      current_length_field_size_ = 0;
+      return;
+    }
+    // Reserved frame types.
+    // TODO(rch): Since these are actually the same behavior as the
+    // default, we probably don't need to special case them here?
+    case 0xB:
+      QUIC_FALLTHROUGH_INTENDED;
+    case 0xB + 0x1F:
+      QUIC_FALLTHROUGH_INTENDED;
+    case 0xB + 0x1F * 2:
+      QUIC_FALLTHROUGH_INTENDED;
+    case 0xB + 0x1F * 3:
+      QUIC_FALLTHROUGH_INTENDED;
+    case 0xB + 0x1F * 4:
+      QUIC_FALLTHROUGH_INTENDED;
+    case 0xB + 0x1F * 5:
+      QUIC_FALLTHROUGH_INTENDED;
+    case 0xB + 0x1F * 6:
+      QUIC_FALLTHROUGH_INTENDED;
+    case 0xB + 0x1F * 7:
+      QUIC_FALLTHROUGH_INTENDED;
+    default:
+      DiscardFramePayload(reader);
+  }
+}
+
+void HttpDecoder::DiscardFramePayload(QuicDataReader* reader) {
+  QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+      remaining_frame_length_, reader->BytesRemaining());
+  QuicStringPiece payload;
+  if (!reader->ReadStringPiece(&payload, bytes_to_read)) {
+    RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame payload");
+    return;
+  }
+  remaining_frame_length_ -= payload.length();
+  if (remaining_frame_length_ == 0) {
+    state_ = STATE_READING_FRAME_LENGTH;
+    current_length_field_size_ = 0;
+  }
+}
+
+void HttpDecoder::BufferFramePayload(QuicDataReader* reader) {
+  if (current_frame_length_ == remaining_frame_length_) {
+    buffer_.erase(buffer_.size());
+    buffer_.reserve(current_frame_length_);
+  }
+  QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+      remaining_frame_length_, reader->BytesRemaining());
+  if (!reader->ReadBytes(
+          &(buffer_[0]) + current_frame_length_ - remaining_frame_length_,
+          bytes_to_read)) {
+    RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame payload");
+    return;
+  }
+  remaining_frame_length_ -= bytes_to_read;
+}
+
+void HttpDecoder::BufferFrameLength(QuicDataReader* reader) {
+  if (current_length_field_size_ == 0) {
+    current_length_field_size_ = reader->PeekVarInt62Length();
+    if (current_length_field_size_ == 0) {
+      RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame length");
+      visitor_->OnError(this);
+      return;
+    }
+    remaining_length_field_length_ = current_length_field_size_;
+  }
+  if (current_length_field_size_ == remaining_length_field_length_) {
+    length_buffer_.erase(length_buffer_.size());
+    length_buffer_.reserve(current_length_field_size_);
+  }
+  QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+      remaining_length_field_length_, reader->BytesRemaining());
+  if (!reader->ReadBytes(&(length_buffer_[0]) + current_length_field_size_ -
+                             remaining_length_field_length_,
+                         bytes_to_read)) {
+    RaiseError(QUIC_INTERNAL_ERROR, "Unable to read frame length");
+    visitor_->OnError(this);
+    return;
+  }
+  remaining_length_field_length_ -= bytes_to_read;
+}
+
+void HttpDecoder::RaiseError(QuicErrorCode error, QuicString error_detail) {
+  state_ = STATE_ERROR;
+  error_ = error;
+  error_detail_ = std::move(error_detail);
+}
+
+bool HttpDecoder::ParsePriorityFrame(QuicDataReader* reader,
+                                     PriorityFrame* frame) {
+  uint8_t flags;
+  if (!reader->ReadUInt8(&flags)) {
+    RaiseError(QUIC_INTERNAL_ERROR, "Unable to read priority frame flags");
+    return false;
+  }
+
+  frame->prioritized_type =
+      static_cast<PriorityElementType>(ExtractBits(flags, 2, 6));
+  frame->dependency_type =
+      static_cast<PriorityElementType>(ExtractBits(flags, 2, 4));
+  frame->exclusive = flags % 2 == 1;
+  if (!reader->ReadVarInt62(&frame->prioritized_element_id)) {
+    RaiseError(QUIC_INTERNAL_ERROR, "Unable to read prioritized_element_id");
+    return false;
+  }
+  if (!reader->ReadVarInt62(&frame->element_dependency_id)) {
+    RaiseError(QUIC_INTERNAL_ERROR, "Unable to read element_dependency_id");
+    return false;
+  }
+  if (!reader->ReadUInt8(&frame->weight)) {
+    RaiseError(QUIC_INTERNAL_ERROR, "Unable to read priority frame weight");
+    return false;
+  }
+  return true;
+}
+
+bool HttpDecoder::ParseSettingsFrame(QuicDataReader* reader,
+                                     SettingsFrame* frame) {
+  while (!reader->IsDoneReading()) {
+    uint16_t id;
+    if (!reader->ReadUInt16(&id)) {
+      RaiseError(QUIC_INTERNAL_ERROR,
+                 "Unable to read settings frame identifier");
+      return false;
+    }
+    uint64_t content;
+    if (!reader->ReadVarInt62(&content)) {
+      RaiseError(QUIC_INTERNAL_ERROR, "Unable to read settings frame content");
+      return false;
+    }
+    frame->values[id] = content;
+  }
+  return true;
+}
+
+}  // namespace quic
diff --git a/quic/core/http/http_decoder.h b/quic/core/http/http_decoder.h
new file mode 100644
index 0000000..910d189
--- /dev/null
+++ b/quic/core/http/http_decoder.h
@@ -0,0 +1,185 @@
+// 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_CORE_HTTP_HTTP_DECODER_H_
+#define QUICHE_QUIC_CORE_HTTP_HTTP_DECODER_H_
+
+#include <cstddef>
+
+#include "net/third_party/quiche/src/quic/core/http/http_frames.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_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+class QuicDataReader;
+
+// Struct that stores meta data of a data frame.
+// |header_length| stores number of bytes header occupies.
+// |payload_length| stores number of bytes payload occupies.
+struct QUIC_EXPORT_PRIVATE Http3FrameLengths {
+  Http3FrameLengths(QuicByteCount header, QuicByteCount payload)
+      : header_length(header), payload_length(payload) {}
+
+  bool operator==(const Http3FrameLengths& other) const {
+    return (header_length == other.header_length) &&
+           (payload_length == other.payload_length);
+  }
+
+  QuicByteCount header_length;
+  QuicByteCount payload_length;
+};
+
+// A class for decoding the HTTP frames that are exchanged in an HTTP over QUIC
+// session.
+class QUIC_EXPORT_PRIVATE HttpDecoder {
+ public:
+  class QUIC_EXPORT_PRIVATE Visitor {
+   public:
+    virtual ~Visitor() {}
+
+    // Called if an error is detected.
+    virtual void OnError(HttpDecoder* decoder) = 0;
+
+    // Called when a PRIORITY frame has been successfully parsed.
+    virtual void OnPriorityFrame(const PriorityFrame& frame) = 0;
+
+    // Called when a CANCEL_PUSH frame has been successfully parsed.
+    virtual void OnCancelPushFrame(const CancelPushFrame& frame) = 0;
+
+    // Called when a MAX_PUSH_ID frame has been successfully parsed.
+    virtual void OnMaxPushIdFrame(const MaxPushIdFrame& frame) = 0;
+
+    // Called when a GOAWAY frame has been successfully parsed.
+    virtual void OnGoAwayFrame(const GoAwayFrame& frame) = 0;
+
+    // Called when a SETTINGS frame has been successfully parsed.
+    virtual void OnSettingsFrame(const SettingsFrame& frame) = 0;
+
+    // Called when a DUPLICATE_PUSH frame has been successfully parsed.
+    virtual void OnDuplicatePushFrame(const DuplicatePushFrame& frame) = 0;
+
+    // Called when a DATA frame has been received, |frame_lengths| will be
+    // passed to inform header length and payload length of the frame.
+    virtual void OnDataFrameStart(Http3FrameLengths frame_length) = 0;
+    // Called when the payload of a DATA frame has read. May be called
+    // multiple times for a single frame.
+    virtual void OnDataFramePayload(QuicStringPiece payload) = 0;
+    // Called when a DATA frame has been completely processed.
+    virtual void OnDataFrameEnd() = 0;
+
+    // Called when a HEADERS frame has been recevied.
+    virtual void OnHeadersFrameStart() = 0;
+    // Called when the payload of a HEADERS frame has read. May be called
+    // multiple times for a single frame.
+    virtual void OnHeadersFramePayload(QuicStringPiece payload) = 0;
+    // Called when a HEADERS frame has been completely processed.
+    // |frame_len| is the length of the HEADERS frame payload.
+    virtual void OnHeadersFrameEnd(QuicByteCount frame_len) = 0;
+
+    // Called when a PUSH_PROMISE frame has been recevied for |push_id|.
+    virtual void OnPushPromiseFrameStart(PushId push_id) = 0;
+    // Called when the payload of a PUSH_PROMISE frame has read. May be called
+    // multiple times for a single frame.
+    virtual void OnPushPromiseFramePayload(QuicStringPiece payload) = 0;
+    // Called when a PUSH_PROMISE frame has been completely processed.
+    virtual void OnPushPromiseFrameEnd() = 0;
+
+    // TODO(rch): Consider adding methods like:
+    // OnUnknownFrame{Start,Payload,End}()
+    // to allow callers to handle unknown frames.
+  };
+
+  HttpDecoder();
+
+  ~HttpDecoder();
+
+  // Set callbacks to be called from the decoder.  A visitor must be set, or
+  // else the decoder will crash.  It is acceptable for the visitor to do
+  // nothing.  If this is called multiple times, only the last visitor
+  // will be used.  |visitor| will be owned by the caller.
+  void set_visitor(Visitor* visitor) { visitor_ = visitor; }
+
+  // Processes the input and invokes the visitor for any frames.
+  // Returns the number of bytes consumed, or 0 if there was an error, in which
+  // case error() should be consulted.
+  QuicByteCount ProcessInput(const char* data, QuicByteCount len);
+
+  bool has_payload() { return has_payload_; }
+
+  QuicErrorCode error() const { return error_; }
+  const QuicString& error_detail() const { return error_detail_; }
+
+ private:
+  // Represents the current state of the parsing state machine.
+  enum HttpDecoderState {
+    STATE_READING_FRAME_LENGTH,
+    STATE_READING_FRAME_TYPE,
+    STATE_READING_FRAME_PAYLOAD,
+    STATE_ERROR
+  };
+
+  // Reads the length of a frame from |reader|. Sets error_ and error_detail_
+  // if there are any errors.
+  void ReadFrameLength(QuicDataReader* reader);
+
+  // Reads the type of a frame from |reader|. Sets error_ and error_detail_
+  // if there are any errors.
+  void ReadFrameType(QuicDataReader* reader);
+
+  // Reads the payload of the current frame from |reader| and processes it,
+  // possibly buffering the data or invoking the visitor.
+  void ReadFramePayload(QuicDataReader* reader);
+
+  // Discards any remaining frame payload from |reader|.
+  void DiscardFramePayload(QuicDataReader* reader);
+
+  // Buffers any remaining frame payload from |reader| into |buffer_|.
+  void BufferFramePayload(QuicDataReader* reader);
+
+  // Buffers any remaining frame length field from |reader| into
+  // |length_buffer_|
+  void BufferFrameLength(QuicDataReader* reader);
+
+  // Sets |error_| and |error_detail_| accordingly.
+  void RaiseError(QuicErrorCode error, QuicString error_detail);
+
+  // Parses the payload of a PRIORITY frame from |reader| into |frame|.
+  bool ParsePriorityFrame(QuicDataReader* reader, PriorityFrame* frame);
+
+  // Parses the payload of a SETTINGS frame from |reader| into |frame|.
+  bool ParseSettingsFrame(QuicDataReader* reader, SettingsFrame* frame);
+
+  // Visitor to invoke when messages are parsed.
+  Visitor* visitor_;  // Unowned.
+  // Current state of the parsing.
+  HttpDecoderState state_;
+  // Type of the frame currently being parsed.
+  uint8_t current_frame_type_;
+  // Size of the frame's length field.
+  QuicByteCount current_length_field_size_;
+  // Remaining length that's needed for the frame's length field.
+  QuicByteCount remaining_length_field_length_;
+  // Length of the payload of the frame currently being parsed.
+  QuicByteCount current_frame_length_;
+  // Remaining payload bytes to be parsed.
+  QuicByteCount remaining_frame_length_;
+  // Last error.
+  QuicErrorCode error_;
+  // The issue which caused |error_|
+  QuicString error_detail_;
+  // True if the call to ProcessInput() generates any payload. Flushed every
+  // time ProcessInput() is called.
+  bool has_payload_;
+  // Remaining unparsed data.
+  QuicString buffer_;
+  // Remaining unparsed length field data.
+  QuicString length_buffer_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_HTTP_DECODER_H_
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc
new file mode 100644
index 0000000..c6d7d9f
--- /dev/null
+++ b/quic/core/http/http_decoder_test.cc
@@ -0,0 +1,409 @@
+// 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/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+
+using testing::InSequence;
+
+namespace quic {
+
+class MockVisitor : public HttpDecoder::Visitor {
+ public:
+  virtual ~MockVisitor() = default;
+
+  // Called if an error is detected.
+  MOCK_METHOD1(OnError, void(HttpDecoder* decoder));
+
+  MOCK_METHOD1(OnPriorityFrame, void(const PriorityFrame& frame));
+  MOCK_METHOD1(OnCancelPushFrame, void(const CancelPushFrame& frame));
+  MOCK_METHOD1(OnMaxPushIdFrame, void(const MaxPushIdFrame& frame));
+  MOCK_METHOD1(OnGoAwayFrame, void(const GoAwayFrame& frame));
+  MOCK_METHOD1(OnSettingsFrame, void(const SettingsFrame& frame));
+  MOCK_METHOD1(OnDuplicatePushFrame, void(const DuplicatePushFrame& frame));
+
+  MOCK_METHOD1(OnDataFrameStart, void(Http3FrameLengths frame_lengths));
+  MOCK_METHOD1(OnDataFramePayload, void(QuicStringPiece payload));
+  MOCK_METHOD0(OnDataFrameEnd, void());
+
+  MOCK_METHOD0(OnHeadersFrameStart, void());
+  MOCK_METHOD1(OnHeadersFramePayload, void(QuicStringPiece payload));
+  MOCK_METHOD1(OnHeadersFrameEnd, void(QuicByteCount frame_len));
+
+  MOCK_METHOD1(OnPushPromiseFrameStart, void(PushId push_id));
+  MOCK_METHOD1(OnPushPromiseFramePayload, void(QuicStringPiece payload));
+  MOCK_METHOD0(OnPushPromiseFrameEnd, void());
+};
+
+class HttpDecoderTest : public QuicTest {
+ public:
+  HttpDecoderTest() { decoder_.set_visitor(&visitor_); }
+  HttpDecoder decoder_;
+  testing::StrictMock<MockVisitor> visitor_;
+};
+
+TEST_F(HttpDecoderTest, InitialState) {
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, ReservedFramesNoPayload) {
+  for (int n = 0; n < 8; ++n) {
+    const uint8_t type = 0xB + 0x1F * n;
+    char input[] = {// length
+                    0x00,
+                    // type
+                    type};
+
+    EXPECT_EQ(2u, decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input))) << n;
+    EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+    ASSERT_EQ("", decoder_.error_detail());
+  }
+}
+
+TEST_F(HttpDecoderTest, ReservedFramesSmallPayload) {
+  for (int n = 0; n < 8; ++n) {
+    const uint8_t type = 0xB + 0x1F * n;
+    const uint8_t payload_size = 50;
+    char input[payload_size + 2] = {// length
+                                    payload_size,
+                                    // type
+                                    type};
+
+    EXPECT_EQ(QUIC_ARRAYSIZE(input),
+              decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)))
+        << n;
+    EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+    ASSERT_EQ("", decoder_.error_detail());
+  }
+}
+
+TEST_F(HttpDecoderTest, ReservedFramesLargePayload) {
+  for (int n = 0; n < 8; ++n) {
+    const uint8_t type = 0xB + 0x1F * n;
+    const QuicByteCount payload_size = 256;
+    char input[payload_size + 3] = {// length
+                                    0x40 + 0x01, 0x00,
+                                    // type
+                                    type};
+
+    EXPECT_EQ(QUIC_ARRAYSIZE(input),
+              decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)))
+        << n;
+    EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+    ASSERT_EQ("", decoder_.error_detail());
+  }
+}
+
+TEST_F(HttpDecoderTest, CancelPush) {
+  char input[] = {// length
+                  0x1,
+                  // type (CANCEL_PUSH)
+                  0x03,
+                  // Push Id
+                  0x01};
+
+  // Process the full frame.
+  EXPECT_CALL(visitor_, OnCancelPushFrame(CancelPushFrame({1})));
+  EXPECT_EQ(QUIC_ARRAYSIZE(input),
+            decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incremently.
+  EXPECT_CALL(visitor_, OnCancelPushFrame(CancelPushFrame({1})));
+  for (char c : input) {
+    EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+  }
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, PushPromiseFrame) {
+  char input[] = {// length
+                  0x8,
+                  // type (PUSH_PROMISE)
+                  0x05,
+                  // Push Id
+                  0x01,
+                  // Header Block
+                  'H', 'e', 'a', 'd', 'e', 'r', 's'};
+
+  // Process the full frame.
+  InSequence s;
+  EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1));
+  EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("Headers")));
+  EXPECT_CALL(visitor_, OnPushPromiseFrameEnd());
+  EXPECT_EQ(QUIC_ARRAYSIZE(input),
+            decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incremently.
+  EXPECT_CALL(visitor_, OnPushPromiseFrameStart(1));
+  EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("H")));
+  EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("e")));
+  EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("a")));
+  EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("d")));
+  EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("e")));
+  EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("r")));
+  EXPECT_CALL(visitor_, OnPushPromiseFramePayload(QuicStringPiece("s")));
+  EXPECT_CALL(visitor_, OnPushPromiseFrameEnd());
+  for (char c : input) {
+    EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+  }
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, MaxPushId) {
+  char input[] = {// length
+                  0x1,
+                  // type (MAX_PUSH_ID)
+                  0x0D,
+                  // Push Id
+                  0x01};
+
+  // Process the full frame.
+  EXPECT_CALL(visitor_, OnMaxPushIdFrame(MaxPushIdFrame({1})));
+  EXPECT_EQ(QUIC_ARRAYSIZE(input),
+            decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incremently.
+  EXPECT_CALL(visitor_, OnMaxPushIdFrame(MaxPushIdFrame({1})));
+  for (char c : input) {
+    EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+  }
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, DuplicatePush) {
+  char input[] = {// length
+                  0x1,
+                  // type (DUPLICATE_PUSH)
+                  0x0E,
+                  // Push Id
+                  0x01};
+  // Process the full frame.
+  EXPECT_CALL(visitor_, OnDuplicatePushFrame(DuplicatePushFrame({1})));
+  EXPECT_EQ(QUIC_ARRAYSIZE(input),
+            decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incremently.
+  EXPECT_CALL(visitor_, OnDuplicatePushFrame(DuplicatePushFrame({1})));
+  for (char c : input) {
+    EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+  }
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, PriorityFrame) {
+  char input[] = {// length
+                  0x4,
+                  // type (PRIORITY)
+                  0x2,
+                  // request stream, request stream, exclusive
+                  0x01,
+                  // prioritized_element_id
+                  0x03,
+                  // element_dependency_id
+                  0x04,
+                  // weight
+                  0xFF};
+
+  PriorityFrame frame;
+  frame.prioritized_type = REQUEST_STREAM;
+  frame.dependency_type = REQUEST_STREAM;
+  frame.exclusive = true;
+  frame.prioritized_element_id = 0x03;
+  frame.element_dependency_id = 0x04;
+  frame.weight = 0xFF;
+
+  // Process the full frame.
+  EXPECT_CALL(visitor_, OnPriorityFrame(frame));
+  EXPECT_EQ(QUIC_ARRAYSIZE(input),
+            decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  /*
+  // Process the frame incremently.
+  EXPECT_CALL(visitor_, OnPriorityFrame(frame));
+  for (char c : input) {
+    EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+  }
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+  */
+}
+
+TEST_F(HttpDecoderTest, SettingsFrame) {
+  // clang-format off
+  char input[] = {
+      // length
+      0x06,
+      // type (SETTINGS)
+      0x04,
+      // identifier (SETTINGS_NUM_PLACEHOLDERS)
+      0x00,
+      0x03,
+      // content
+      0x02,
+      // identifier (SETTINGS_MAX_HEADER_LIST_SIZE)
+      0x00,
+      0x06,
+      // content
+      0x05,
+  };
+  // clang-format on
+
+  SettingsFrame frame;
+  frame.values[3] = 2;
+  frame.values[6] = 5;
+
+  // Process the full frame.
+  EXPECT_CALL(visitor_, OnSettingsFrame(frame));
+  EXPECT_EQ(QUIC_ARRAYSIZE(input),
+            decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incremently.
+  EXPECT_CALL(visitor_, OnSettingsFrame(frame));
+  for (char c : input) {
+    EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+  }
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, DataFrame) {
+  char input[] = {// length
+                  0x05,
+                  // type (DATA)
+                  0x00,
+                  // data
+                  'D', 'a', 't', 'a', '!'};
+
+  // Process the full frame.
+  InSequence s;
+  EXPECT_CALL(visitor_, OnDataFrameStart(Http3FrameLengths(2, 5)));
+  EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("Data!")));
+  EXPECT_CALL(visitor_, OnDataFrameEnd());
+  EXPECT_EQ(QUIC_ARRAYSIZE(input),
+            decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incremently.
+  EXPECT_CALL(visitor_, OnDataFrameStart(Http3FrameLengths(2, 5)));
+  EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("D")));
+  EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("a")));
+  EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("t")));
+  EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("a")));
+  EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece("!")));
+  EXPECT_CALL(visitor_, OnDataFrameEnd());
+  for (char c : input) {
+    EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+  }
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, FrameHeaderPartialDelivery) {
+  // A large input that will occupy more than 1 byte in the length field.
+  QuicString input(2048, 'x');
+  HttpEncoder encoder;
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder.SerializeDataFrameHeader(input.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  // Partially send only 1 byte of the header to process.
+  EXPECT_EQ(1u, decoder_.ProcessInput(header.data(), 1));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Send the rest of the header.
+  EXPECT_EQ(header_length - 1,
+            decoder_.ProcessInput(header.data() + 1, header_length - 1));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Send data.
+  EXPECT_CALL(visitor_, OnDataFrameStart(Http3FrameLengths(3, 2048)));
+  EXPECT_CALL(visitor_, OnDataFramePayload(QuicStringPiece(input)));
+  EXPECT_CALL(visitor_, OnDataFrameEnd());
+  EXPECT_EQ(2048u, decoder_.ProcessInput(input.data(), 2048));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, GoAway) {
+  char input[] = {// length
+                  0x1,
+                  // type (GOAWAY)
+                  0x07,
+                  // StreamId
+                  0x01};
+
+  // Process the full frame.
+  EXPECT_CALL(visitor_, OnGoAwayFrame(GoAwayFrame({1})));
+  EXPECT_EQ(QUIC_ARRAYSIZE(input),
+            decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incremently.
+  EXPECT_CALL(visitor_, OnGoAwayFrame(GoAwayFrame({1})));
+  for (char c : input) {
+    EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+  }
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, HeadersFrame) {
+  char input[] = {// length
+                  0x07,
+                  // type (HEADERS)
+                  0x01,
+                  // headers
+                  'H', 'e', 'a', 'd', 'e', 'r', 's'};
+
+  // Process the full frame.
+  InSequence s;
+  EXPECT_CALL(visitor_, OnHeadersFrameStart());
+  EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("Headers")));
+  EXPECT_CALL(visitor_, OnHeadersFrameEnd(7));
+  EXPECT_EQ(QUIC_ARRAYSIZE(input),
+            decoder_.ProcessInput(input, QUIC_ARRAYSIZE(input)));
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+
+  // Process the frame incremently.
+  EXPECT_CALL(visitor_, OnHeadersFrameStart());
+  EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("H")));
+  EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("e")));
+  EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("a")));
+  EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("d")));
+  EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("e")));
+  EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("r")));
+  EXPECT_CALL(visitor_, OnHeadersFramePayload(QuicStringPiece("s")));
+  EXPECT_CALL(visitor_, OnHeadersFrameEnd(7));
+  for (char c : input) {
+    EXPECT_EQ(1u, decoder_.ProcessInput(&c, 1));
+  }
+  EXPECT_EQ(QUIC_NO_ERROR, decoder_.error());
+  EXPECT_EQ("", decoder_.error_detail());
+}
+
+}  // namespace quic
diff --git a/quic/core/http/http_encoder.cc b/quic/core/http/http_encoder.cc
new file mode 100644
index 0000000..d2d1699
--- /dev/null
+++ b/quic/core/http/http_encoder.cc
@@ -0,0 +1,257 @@
+// 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/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fallthrough.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+namespace {
+
+// Set the first byte of a PRIORITY frame according to its fields.
+uint8_t SetPriorityFields(uint8_t num,
+                          PriorityElementType type,
+                          bool prioritized) {
+  switch (type) {
+    case REQUEST_STREAM:
+      return num;
+    case PUSH_STREAM:
+      if (prioritized) {
+        return num | (1 << 6);
+      }
+      return num | (1 << 4);
+    case PLACEHOLDER:
+      if (prioritized) {
+        return num | (1 << 7);
+      }
+      return num | (1 << 5);
+    case ROOT_OF_TREE:
+      if (prioritized) {
+        num = num | (1 << 6);
+        return num | (1 << 7);
+      }
+      num = num | (1 << 4);
+      return num | (1 << 5);
+    default:
+      QUIC_NOTREACHED();
+      return num;
+  }
+}
+
+// Length of the type field of a frame.
+static const size_t kFrameTypeLength = 1;
+// Length of the weight field of a priority frame.
+static const size_t kPriorityWeightLength = 1;
+// Length of a priority frame's first byte.
+static const size_t kPriorityFirstByteLength = 1;
+// Length of a key in the map of a settings frame.
+static const size_t kSettingsMapKeyLength = 2;
+
+}  // namespace
+
+HttpEncoder::HttpEncoder() {}
+
+HttpEncoder::~HttpEncoder() {}
+
+QuicByteCount HttpEncoder::SerializeDataFrameHeader(
+    QuicByteCount payload_length,
+    std::unique_ptr<char[]>* output) {
+  DCHECK_NE(0u, payload_length);
+  QuicByteCount header_length =
+      QuicDataWriter::GetVarInt62Len(payload_length) + kFrameTypeLength;
+
+  output->reset(new char[header_length]);
+  QuicDataWriter writer(header_length, output->get());
+
+  if (WriteFrameHeader(payload_length, HttpFrameType::DATA, &writer)) {
+    return header_length;
+  }
+  return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeHeadersFrameHeader(
+    QuicByteCount payload_length,
+    std::unique_ptr<char[]>* output) {
+  DCHECK_NE(0u, payload_length);
+  QuicByteCount header_length =
+      QuicDataWriter::GetVarInt62Len(payload_length) + kFrameTypeLength;
+
+  output->reset(new char[header_length]);
+  QuicDataWriter writer(header_length, output->get());
+
+  if (WriteFrameHeader(payload_length, HttpFrameType::HEADERS, &writer)) {
+    return header_length;
+  }
+  return 0;
+}
+
+QuicByteCount HttpEncoder::SerializePriorityFrame(
+    const PriorityFrame& priority,
+    std::unique_ptr<char[]>* output) {
+  QuicByteCount payload_length =
+      kPriorityFirstByteLength +
+      QuicDataWriter::GetVarInt62Len(priority.prioritized_element_id) +
+      QuicDataWriter::GetVarInt62Len(priority.element_dependency_id) +
+      kPriorityWeightLength;
+  QuicByteCount total_length = GetTotalLength(payload_length);
+
+  output->reset(new char[total_length]);
+  QuicDataWriter writer(total_length, output->get());
+
+  if (!WriteFrameHeader(payload_length, HttpFrameType::PRIORITY, &writer)) {
+    return 0;
+  }
+
+  // Set the first byte of the payload.
+  uint8_t bits = 0;
+  bits = SetPriorityFields(bits, priority.prioritized_type, true);
+  bits = SetPriorityFields(bits, priority.dependency_type, false);
+  if (priority.exclusive) {
+    bits |= 1;
+  }
+
+  if (writer.WriteUInt8(bits) &&
+      writer.WriteVarInt62(priority.prioritized_element_id) &&
+      writer.WriteVarInt62(priority.element_dependency_id) &&
+      writer.WriteUInt8(priority.weight)) {
+    return total_length;
+  }
+  return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeCancelPushFrame(
+    const CancelPushFrame& cancel_push,
+    std::unique_ptr<char[]>* output) {
+  QuicByteCount payload_length =
+      QuicDataWriter::GetVarInt62Len(cancel_push.push_id);
+  QuicByteCount total_length = GetTotalLength(payload_length);
+
+  output->reset(new char[total_length]);
+  QuicDataWriter writer(total_length, output->get());
+
+  if (WriteFrameHeader(payload_length, HttpFrameType::CANCEL_PUSH, &writer) &&
+      writer.WriteVarInt62(cancel_push.push_id)) {
+    return total_length;
+  }
+  return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeSettingsFrame(
+    const SettingsFrame& settings,
+    std::unique_ptr<char[]>* output) {
+  // Calculate the key sizes.
+  QuicByteCount payload_length = settings.values.size() * kSettingsMapKeyLength;
+  // Calculate the value sizes.
+  for (auto it = settings.values.begin(); it != settings.values.end(); ++it) {
+    payload_length += QuicDataWriter::GetVarInt62Len(it->second);
+  }
+
+  QuicByteCount total_length = GetTotalLength(payload_length);
+
+  output->reset(new char[total_length]);
+  QuicDataWriter writer(total_length, output->get());
+
+  if (!WriteFrameHeader(payload_length, HttpFrameType::SETTINGS, &writer)) {
+    return 0;
+  }
+
+  for (auto it = settings.values.begin(); it != settings.values.end(); ++it) {
+    if (!writer.WriteUInt16(it->first) || !writer.WriteVarInt62(it->second)) {
+      return 0;
+    }
+  }
+
+  return total_length;
+}
+
+QuicByteCount HttpEncoder::SerializePushPromiseFrameWithOnlyPushId(
+    const PushPromiseFrame& push_promise,
+    std::unique_ptr<char[]>* output) {
+  QuicByteCount payload_length =
+      QuicDataWriter::GetVarInt62Len(push_promise.push_id) +
+      push_promise.headers.length();
+  // GetTotalLength() is not used because headers will not be serialized.
+  QuicByteCount total_length =
+      QuicDataWriter::GetVarInt62Len(payload_length) + kFrameTypeLength +
+      QuicDataWriter::GetVarInt62Len(push_promise.push_id);
+
+  output->reset(new char[total_length]);
+  QuicDataWriter writer(total_length, output->get());
+
+  if (WriteFrameHeader(payload_length, HttpFrameType::PUSH_PROMISE, &writer) &&
+      writer.WriteVarInt62(push_promise.push_id)) {
+    return total_length;
+  }
+  return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeGoAwayFrame(
+    const GoAwayFrame& goaway,
+    std::unique_ptr<char[]>* output) {
+  QuicByteCount payload_length =
+      QuicDataWriter::GetVarInt62Len(goaway.stream_id);
+  QuicByteCount total_length = GetTotalLength(payload_length);
+
+  output->reset(new char[total_length]);
+  QuicDataWriter writer(total_length, output->get());
+
+  if (WriteFrameHeader(payload_length, HttpFrameType::GOAWAY, &writer) &&
+      writer.WriteVarInt62(goaway.stream_id)) {
+    return total_length;
+  }
+  return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeMaxPushIdFrame(
+    const MaxPushIdFrame& max_push_id,
+    std::unique_ptr<char[]>* output) {
+  QuicByteCount payload_length =
+      QuicDataWriter::GetVarInt62Len(max_push_id.push_id);
+  QuicByteCount total_length = GetTotalLength(payload_length);
+
+  output->reset(new char[total_length]);
+  QuicDataWriter writer(total_length, output->get());
+
+  if (WriteFrameHeader(payload_length, HttpFrameType::MAX_PUSH_ID, &writer) &&
+      writer.WriteVarInt62(max_push_id.push_id)) {
+    return total_length;
+  }
+  return 0;
+}
+
+QuicByteCount HttpEncoder::SerializeDuplicatePushFrame(
+    const DuplicatePushFrame& duplicate_push,
+    std::unique_ptr<char[]>* output) {
+  QuicByteCount payload_length =
+      QuicDataWriter::GetVarInt62Len(duplicate_push.push_id);
+  QuicByteCount total_length = GetTotalLength(payload_length);
+
+  output->reset(new char[total_length]);
+  QuicDataWriter writer(total_length, output->get());
+
+  if (WriteFrameHeader(payload_length, HttpFrameType::DUPLICATE_PUSH,
+                       &writer) &&
+      writer.WriteVarInt62(duplicate_push.push_id)) {
+    return total_length;
+  }
+  return 0;
+}
+
+bool HttpEncoder::WriteFrameHeader(QuicByteCount length,
+                                   HttpFrameType type,
+                                   QuicDataWriter* writer) {
+  return writer->WriteVarInt62(length) &&
+         writer->WriteUInt8(static_cast<uint8_t>(type));
+}
+
+QuicByteCount HttpEncoder::GetTotalLength(QuicByteCount payload_length) {
+  return QuicDataWriter::GetVarInt62Len(payload_length) + kFrameTypeLength +
+         payload_length;
+}
+
+}  // namespace quic
diff --git a/quic/core/http/http_encoder.h b/quic/core/http/http_encoder.h
new file mode 100644
index 0000000..f04e6e4
--- /dev/null
+++ b/quic/core/http/http_encoder.h
@@ -0,0 +1,85 @@
+// 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_CORE_HTTP_HTTP_ENCODER_H_
+#define QUICHE_QUIC_CORE_HTTP_HTTP_ENCODER_H_
+
+#include <cstddef>
+
+#include "net/third_party/quiche/src/quic/core/http/http_frames.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+class QuicDataWriter;
+
+// A class for encoding the HTTP frames that are exchanged in an HTTP over QUIC
+// session.
+class QUIC_EXPORT_PRIVATE HttpEncoder {
+ public:
+  HttpEncoder();
+
+  ~HttpEncoder();
+
+  // Serializes a DATA frame header into a new buffer stored in |output|.
+  // Returns the length of the buffer on success, or 0 otherwise.
+  QuicByteCount SerializeDataFrameHeader(QuicByteCount payload_length,
+                                         std::unique_ptr<char[]>* output);
+
+  // Serializes a HEADERS frame header into a new buffer stored in |output|.
+  // Returns the length of the buffer on success, or 0 otherwise.
+  QuicByteCount SerializeHeadersFrameHeader(QuicByteCount payload_length,
+                                            std::unique_ptr<char[]>* output);
+
+  // Serializes a PRIORITY frame into a new buffer stored in |output|.
+  // Returns the length of the buffer on success, or 0 otherwise.
+  QuicByteCount SerializePriorityFrame(const PriorityFrame& priority,
+                                       std::unique_ptr<char[]>* output);
+
+  // Serializes a CANCEL_PUSH frame into a new buffer stored in |output|.
+  // Returns the length of the buffer on success, or 0 otherwise.
+  QuicByteCount SerializeCancelPushFrame(const CancelPushFrame& cancel_push,
+                                         std::unique_ptr<char[]>* output);
+
+  // Serializes a SETTINGS frame into a new buffer stored in |output|.
+  // Returns the length of the buffer on success, or 0 otherwise.
+  QuicByteCount SerializeSettingsFrame(const SettingsFrame& settings,
+                                       std::unique_ptr<char[]>* output);
+
+  // Serializes the header and push_id of a PUSH_PROMISE frame into a new buffer
+  // stored in |output|. Returns the length of the buffer on success, or 0
+  // otherwise.
+  QuicByteCount SerializePushPromiseFrameWithOnlyPushId(
+      const PushPromiseFrame& push_promise,
+      std::unique_ptr<char[]>* output);
+
+  // Serializes a GOAWAY frame into a new buffer stored in |output|.
+  // Returns the length of the buffer on success, or 0 otherwise.
+  QuicByteCount SerializeGoAwayFrame(const GoAwayFrame& goaway,
+                                     std::unique_ptr<char[]>* output);
+
+  // Serializes a MAX_PUSH frame into a new buffer stored in |output|.
+  // Returns the length of the buffer on success, or 0 otherwise.
+  QuicByteCount SerializeMaxPushIdFrame(const MaxPushIdFrame& max_push_id,
+                                        std::unique_ptr<char[]>* output);
+
+  // Serialize a DUPLICATE_PUSH frame into a new buffer stored in |output|.
+  // Returns the length of the buffer on success, or 0 otherwise.
+  QuicByteCount SerializeDuplicatePushFrame(
+      const DuplicatePushFrame& duplicate_push,
+      std::unique_ptr<char[]>* output);
+
+ private:
+  bool WriteFrameHeader(QuicByteCount length,
+                        HttpFrameType type,
+                        QuicDataWriter* writer);
+
+  QuicByteCount GetTotalLength(QuicByteCount payload_length);
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_HTTP_ENCODER_H_
diff --git a/quic/core/http/http_encoder_test.cc b/quic/core/http/http_encoder_test.cc
new file mode 100644
index 0000000..a484aed
--- /dev/null
+++ b/quic/core/http/http_encoder_test.cc
@@ -0,0 +1,185 @@
+// 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/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.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/test_tools/quic_test_utils.h"
+
+namespace quic {
+namespace test {
+
+class HttpEncoderTest : public QuicTest {
+ public:
+  HttpEncoderTest() {}
+  HttpEncoder encoder_;
+};
+
+TEST_F(HttpEncoderTest, SerializeDataFrameHeader) {
+  std::unique_ptr<char[]> buffer;
+  uint64_t length =
+      encoder_.SerializeDataFrameHeader(/* payload_length = */ 5, &buffer);
+  char output[] = {// length
+                   0x05,
+                   // type (DATA)
+                   0x00};
+  EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+  CompareCharArraysWithHexError("DATA", buffer.get(), length, output,
+                                QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeHeadersFrameHeader) {
+  std::unique_ptr<char[]> buffer;
+  uint64_t length =
+      encoder_.SerializeHeadersFrameHeader(/* payload_length = */ 7, &buffer);
+  char output[] = {// length
+                   0x07,
+                   // type (HEADERS)
+                   0x01};
+  EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+  CompareCharArraysWithHexError("HEADERS", buffer.get(), length, output,
+                                QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializePriorityFrame) {
+  PriorityFrame priority;
+  priority.prioritized_type = REQUEST_STREAM;
+  priority.dependency_type = REQUEST_STREAM;
+  priority.exclusive = true;
+  priority.prioritized_element_id = 0x03;
+  priority.element_dependency_id = 0x04;
+  priority.weight = 0xFF;
+  char output[] = {// length
+                   0x4,
+                   // type (PRIORITY)
+                   0x2,
+                   // request stream, request stream, exclusive
+                   0x01,
+                   // prioritized_element_id
+                   0x03,
+                   // element_dependency_id
+                   0x04,
+                   // weight
+                   0xFF};
+
+  std::unique_ptr<char[]> buffer;
+  uint64_t length = encoder_.SerializePriorityFrame(priority, &buffer);
+  EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+  CompareCharArraysWithHexError("PRIORITY", buffer.get(), length, output,
+                                QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeCancelPushFrame) {
+  CancelPushFrame cancel_push;
+  cancel_push.push_id = 0x01;
+  char output[] = {// length
+                   0x1,
+                   // type (CANCEL_PUSH)
+                   0x03,
+                   // Push Id
+                   0x01};
+  std::unique_ptr<char[]> buffer;
+  uint64_t length = encoder_.SerializeCancelPushFrame(cancel_push, &buffer);
+  EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+  CompareCharArraysWithHexError("CANCEL_PUSH", buffer.get(), length, output,
+                                QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeSettingsFrame) {
+  SettingsFrame settings;
+  settings.values[3] = 2;
+  settings.values[6] = 5;
+  char output[] = {
+      // length
+      0x06,
+      // type (SETTINGS)
+      0x04,
+      // identifier (SETTINGS_NUM_PLACEHOLDERS)
+      0x00,
+      0x03,
+      // content
+      0x02,
+      // identifier (SETTINGS_MAX_HEADER_LIST_SIZE)
+      0x00,
+      0x06,
+      // content
+      0x05,
+  };
+  std::unique_ptr<char[]> buffer;
+  uint64_t length = encoder_.SerializeSettingsFrame(settings, &buffer);
+  EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+  CompareCharArraysWithHexError("SETTINGS", buffer.get(), length, output,
+                                QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializePushPromiseFrameWithOnlyPushId) {
+  PushPromiseFrame push_promise;
+  push_promise.push_id = 0x01;
+  push_promise.headers = "Headers";
+  char output[] = {// length
+                   0x8,
+                   // type (PUSH_PROMISE)
+                   0x05,
+                   // Push Id
+                   0x01};
+  std::unique_ptr<char[]> buffer;
+  uint64_t length =
+      encoder_.SerializePushPromiseFrameWithOnlyPushId(push_promise, &buffer);
+  EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+  CompareCharArraysWithHexError("PUSH_PROMISE", buffer.get(), length, output,
+                                QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeGoAwayFrame) {
+  GoAwayFrame goaway;
+  goaway.stream_id = 0x1;
+  char output[] = {// length
+                   0x1,
+                   // type (GOAWAY)
+                   0x07,
+                   // StreamId
+                   0x01};
+  std::unique_ptr<char[]> buffer;
+  uint64_t length = encoder_.SerializeGoAwayFrame(goaway, &buffer);
+  EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+  CompareCharArraysWithHexError("GOAWAY", buffer.get(), length, output,
+                                QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeMaxPushIdFrame) {
+  MaxPushIdFrame max_push_id;
+  max_push_id.push_id = 0x1;
+  char output[] = {// length
+                   0x1,
+                   // type (MAX_PUSH_ID)
+                   0x0D,
+                   // Push Id
+                   0x01};
+  std::unique_ptr<char[]> buffer;
+  uint64_t length = encoder_.SerializeMaxPushIdFrame(max_push_id, &buffer);
+  EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+  CompareCharArraysWithHexError("MAX_PUSH_ID", buffer.get(), length, output,
+                                QUIC_ARRAYSIZE(output));
+}
+
+TEST_F(HttpEncoderTest, SerializeDuplicatePushFrame) {
+  DuplicatePushFrame duplicate_push;
+  duplicate_push.push_id = 0x1;
+  char output[] = {// length
+                   0x1,
+                   // type (DUPLICATE_PUSH)
+                   0x0E,
+                   // Push Id
+                   0x01};
+  std::unique_ptr<char[]> buffer;
+  uint64_t length =
+      encoder_.SerializeDuplicatePushFrame(duplicate_push, &buffer);
+  EXPECT_EQ(QUIC_ARRAYSIZE(output), length);
+  CompareCharArraysWithHexError("DUPLICATE_PUSH", buffer.get(), length, output,
+                                QUIC_ARRAYSIZE(output));
+}
+
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/http_frames.h b/quic/core/http/http_frames.h
new file mode 100644
index 0000000..ea11557
--- /dev/null
+++ b/quic/core/http/http_frames.h
@@ -0,0 +1,156 @@
+// 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_CORE_HTTP_HTTP_FRAMES_H_
+#define QUICHE_QUIC_CORE_HTTP_HTTP_FRAMES_H_
+
+#include <map>
+
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+enum class HttpFrameType : uint8_t {
+  DATA = 0x0,
+  HEADERS = 0x1,
+  PRIORITY = 0X2,
+  CANCEL_PUSH = 0X3,
+  SETTINGS = 0x4,
+  PUSH_PROMISE = 0x5,
+  GOAWAY = 0x7,
+  MAX_PUSH_ID = 0xD,
+  DUPLICATE_PUSH = 0xE
+};
+
+// 4.2.1.  DATA
+//
+//   DATA frames (type=0x0) convey arbitrary, variable-length sequences of
+//   octets associated with an HTTP request or response payload.
+struct DataFrame {
+  QuicStringPiece data;
+};
+
+// 4.2.2.  HEADERS
+//
+//   The HEADERS frame (type=0x1) is used to carry a header block,
+//   compressed using QPACK.
+struct HeadersFrame {
+  QuicStringPiece headers;
+};
+
+// 4.2.3.  PRIORITY
+//
+//   The PRIORITY (type=0x02) frame specifies the sender-advised priority
+//   of a stream
+enum PriorityElementType {
+  REQUEST_STREAM = 0,
+  PUSH_STREAM = 1,
+  PLACEHOLDER = 2,
+  ROOT_OF_TREE = 3
+};
+
+struct PriorityFrame {
+  PriorityElementType prioritized_type;
+  PriorityElementType dependency_type;
+  bool exclusive;
+  uint64_t prioritized_element_id;
+  uint64_t element_dependency_id;
+  uint8_t weight;
+
+  bool operator==(const PriorityFrame& rhs) const {
+    return prioritized_type == rhs.prioritized_type &&
+           dependency_type == rhs.dependency_type &&
+           exclusive == rhs.exclusive &&
+           prioritized_element_id == rhs.prioritized_element_id &&
+           element_dependency_id == rhs.element_dependency_id &&
+           weight == rhs.weight;
+  }
+};
+
+// 4.2.4.  CANCEL_PUSH
+//
+//   The CANCEL_PUSH frame (type=0x3) is used to request cancellation of
+//   server push prior to the push stream being created.
+using PushId = uint64_t;
+
+struct CancelPushFrame {
+  PushId push_id;
+
+  bool operator==(const CancelPushFrame& rhs) const {
+    return push_id == rhs.push_id;
+  }
+};
+
+// 4.2.5.  SETTINGS
+//
+//   The SETTINGS frame (type=0x4) conveys configuration parameters that
+//   affect how endpoints communicate, such as preferences and constraints
+//   on peer behavior
+
+using SettingsId = uint16_t;
+using SettingsMap = std::map<SettingsId, uint64_t>;
+
+struct SettingsFrame {
+  SettingsMap values;
+
+  bool operator==(const SettingsFrame& rhs) const {
+    return values == rhs.values;
+  }
+};
+
+// 4.2.6.  PUSH_PROMISE
+//
+//   The PUSH_PROMISE frame (type=0x05) is used to carry a request header
+//   set from server to client, as in HTTP/2.
+struct PushPromiseFrame {
+  PushId push_id;
+  QuicStringPiece headers;
+
+  bool operator==(const PushPromiseFrame& rhs) const {
+    return push_id == rhs.push_id && headers == rhs.headers;
+  }
+};
+
+// 4.2.7.  GOAWAY
+//
+//   The GOAWAY frame (type=0x7) is used to initiate graceful shutdown of
+//   a connection by a server.
+struct GoAwayFrame {
+  QuicStreamId stream_id;
+
+  bool operator==(const GoAwayFrame& rhs) const {
+    return stream_id == rhs.stream_id;
+  }
+};
+
+// 4.2.8.  MAX_PUSH_ID
+//
+//   The MAX_PUSH_ID frame (type=0xD) is used by clients to control the
+//   number of server pushes that the server can initiate.
+struct MaxPushIdFrame {
+  PushId push_id;
+
+  bool operator==(const MaxPushIdFrame& rhs) const {
+    return push_id == rhs.push_id;
+  }
+};
+
+// 4.2.9.  DUPLICATE_PUSH
+//
+//  The DUPLICATE_PUSH frame (type=0xE) is used by servers to indicate
+//  that an existing pushed resource is related to multiple client
+//  requests.
+struct DuplicatePushFrame {
+  PushId push_id;
+
+  bool operator==(const DuplicatePushFrame& rhs) const {
+    return push_id == rhs.push_id;
+  }
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_HTTP_FRAMES_H_
diff --git a/quic/core/http/quic_client_promised_info.cc b/quic/core/http/quic_client_promised_info.cc
new file mode 100644
index 0000000..abfc4b4
--- /dev/null
+++ b/quic/core/http/quic_client_promised_info.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h"
+
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.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.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+QuicClientPromisedInfo::QuicClientPromisedInfo(
+    QuicSpdyClientSessionBase* session,
+    QuicStreamId id,
+    QuicString url)
+    : session_(session),
+      id_(id),
+      url_(std::move(url)),
+      client_request_delegate_(nullptr) {}
+
+QuicClientPromisedInfo::~QuicClientPromisedInfo() {}
+
+void QuicClientPromisedInfo::CleanupAlarm::OnAlarm() {
+  QUIC_DVLOG(1) << "self GC alarm for stream " << promised_->id_;
+  promised_->session()->OnPushStreamTimedOut(promised_->id_);
+  promised_->Reset(QUIC_PUSH_STREAM_TIMED_OUT);
+}
+
+void QuicClientPromisedInfo::Init() {
+  cleanup_alarm_.reset(session_->connection()->alarm_factory()->CreateAlarm(
+      new QuicClientPromisedInfo::CleanupAlarm(this)));
+  cleanup_alarm_->Set(
+      session_->connection()->helper()->GetClock()->ApproximateNow() +
+      QuicTime::Delta::FromSeconds(kPushPromiseTimeoutSecs));
+}
+
+bool QuicClientPromisedInfo::OnPromiseHeaders(const SpdyHeaderBlock& headers) {
+  // RFC7540, Section 8.2, requests MUST be safe [RFC7231], Section
+  // 4.2.1.  GET and HEAD are the methods that are safe and required.
+  SpdyHeaderBlock::const_iterator it = headers.find(spdy::kHttp2MethodHeader);
+  if (it == headers.end()) {
+    QUIC_DVLOG(1) << "Promise for stream " << id_ << " has no method";
+    Reset(QUIC_INVALID_PROMISE_METHOD);
+    return false;
+  }
+  if (!(it->second == "GET" || it->second == "HEAD")) {
+    QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid method "
+                  << it->second;
+    Reset(QUIC_INVALID_PROMISE_METHOD);
+    return false;
+  }
+  if (!SpdyUtils::PromisedUrlIsValid(headers)) {
+    QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid URL "
+                  << url_;
+    Reset(QUIC_INVALID_PROMISE_URL);
+    return false;
+  }
+  if (!session_->IsAuthorized(
+          SpdyUtils::GetPromisedHostNameFromHeaders(headers))) {
+    Reset(QUIC_UNAUTHORIZED_PROMISE_URL);
+    return false;
+  }
+  request_headers_ = headers.Clone();
+  return true;
+}
+
+void QuicClientPromisedInfo::OnResponseHeaders(const SpdyHeaderBlock& headers) {
+  response_headers_ = QuicMakeUnique<SpdyHeaderBlock>(headers.Clone());
+  if (client_request_delegate_) {
+    // We already have a client request waiting.
+    FinalValidation();
+  }
+}
+
+void QuicClientPromisedInfo::Reset(QuicRstStreamErrorCode error_code) {
+  QuicClientPushPromiseIndex::Delegate* delegate = client_request_delegate_;
+  session_->ResetPromised(id_, error_code);
+  session_->DeletePromised(this);
+  if (delegate) {
+    delegate->OnRendezvousResult(nullptr);
+  }
+}
+
+QuicAsyncStatus QuicClientPromisedInfo::FinalValidation() {
+  if (!client_request_delegate_->CheckVary(
+          client_request_headers_, request_headers_, *response_headers_)) {
+    Reset(QUIC_PROMISE_VARY_MISMATCH);
+    return QUIC_FAILURE;
+  }
+  QuicSpdyStream* stream = session_->GetPromisedStream(id_);
+  if (!stream) {
+    // This shouldn't be possible, as |ClientRequest| guards against
+    // closed stream for the synchronous case.  And in the
+    // asynchronous case, a RST can only be caught by |OnAlarm()|.
+    QUIC_BUG << "missing promised stream" << id_;
+  }
+  QuicClientPushPromiseIndex::Delegate* delegate = client_request_delegate_;
+  session_->DeletePromised(this);
+  // Stream can start draining now
+  if (delegate) {
+    delegate->OnRendezvousResult(stream);
+  }
+  return QUIC_SUCCESS;
+}
+
+QuicAsyncStatus QuicClientPromisedInfo::HandleClientRequest(
+    const SpdyHeaderBlock& request_headers,
+    QuicClientPushPromiseIndex::Delegate* delegate) {
+  if (session_->IsClosedStream(id_)) {
+    // There was a RST on the response stream.
+    session_->DeletePromised(this);
+    return QUIC_FAILURE;
+  }
+
+  if (is_validating()) {
+    // The push promise has already been matched to another request though
+    // pending for validation. Returns QUIC_FAILURE to the caller as it couldn't
+    // match a new request any more. This will not affect the validation of the
+    // other request.
+    return QUIC_FAILURE;
+  }
+
+  client_request_delegate_ = delegate;
+  client_request_headers_ = request_headers.Clone();
+  if (response_headers_ == nullptr) {
+    return QUIC_PENDING;
+  }
+  return FinalValidation();
+}
+
+void QuicClientPromisedInfo::Cancel() {
+  // Don't fire OnRendezvousResult() for client initiated cancel.
+  client_request_delegate_ = nullptr;
+  Reset(QUIC_STREAM_CANCELLED);
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_client_promised_info.h b/quic/core/http/quic_client_promised_info.h
new file mode 100644
index 0000000..917c9f7
--- /dev/null
+++ b/quic/core/http/quic_client_promised_info.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PROMISED_INFO_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PROMISED_INFO_H_
+
+#include <cstddef>
+
+#include "net/third_party/quiche/src/quic/core/quic_alarm.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+namespace test {
+class QuicClientPromisedInfoPeer;
+}  // namespace test
+
+// QuicClientPromisedInfo tracks the client state of a server push
+// stream from the time a PUSH_PROMISE is received until rendezvous
+// between the promised response and the corresponding client request
+// is complete.
+class QUIC_EXPORT_PRIVATE QuicClientPromisedInfo
+    : public QuicClientPushPromiseIndex::TryHandle {
+ public:
+  // Interface to QuicSpdyClientStream
+  QuicClientPromisedInfo(QuicSpdyClientSessionBase* session,
+                         QuicStreamId id,
+                         QuicString url);
+  QuicClientPromisedInfo(const QuicClientPromisedInfo&) = delete;
+  QuicClientPromisedInfo& operator=(const QuicClientPromisedInfo&) = delete;
+  virtual ~QuicClientPromisedInfo();
+
+  void Init();
+
+  // Validate promise headers etc. Returns true if headers are valid.
+  bool OnPromiseHeaders(const spdy::SpdyHeaderBlock& headers);
+
+  // Store response, possibly proceed with final validation.
+  void OnResponseHeaders(const spdy::SpdyHeaderBlock& headers);
+
+  // Rendezvous between this promised stream and a client request that
+  // has a matching URL.
+  virtual QuicAsyncStatus HandleClientRequest(
+      const spdy::SpdyHeaderBlock& headers,
+      QuicClientPushPromiseIndex::Delegate* delegate);
+
+  void Cancel() override;
+
+  void Reset(QuicRstStreamErrorCode error_code);
+
+  // Client requests are initially associated to promises by matching
+  // URL in the client request against the URL in the promise headers,
+  // uing the |promised_by_url| map.  The push can be cross-origin, so
+  // the client should validate that the session is authoritative for
+  // the promised URL.  If not, it should call |RejectUnauthorized|.
+  QuicSpdyClientSessionBase* session() { return session_; }
+
+  // If the promised response contains Vary header, then the fields
+  // specified by Vary must match between the client request header
+  // and the promise headers (see https://crbug.com//554220).  Vary
+  // validation requires the response headers (for the actual Vary
+  // field list), the promise headers (taking the role of the "cached"
+  // request), and the client request headers.
+  spdy::SpdyHeaderBlock* request_headers() { return &request_headers_; }
+
+  spdy::SpdyHeaderBlock* response_headers() { return response_headers_.get(); }
+
+  // After validation, client will use this to access the pushed stream.
+
+  QuicStreamId id() const { return id_; }
+
+  const QuicString url() const { return url_; }
+
+  // Return true if there's a request pending matching this push promise.
+  bool is_validating() const { return client_request_delegate_ != nullptr; }
+
+ private:
+  friend class test::QuicClientPromisedInfoPeer;
+
+  class CleanupAlarm : public QuicAlarm::Delegate {
+   public:
+    explicit CleanupAlarm(QuicClientPromisedInfo* promised)
+        : promised_(promised) {}
+
+    void OnAlarm() override;
+
+    QuicClientPromisedInfo* promised_;
+  };
+
+  QuicAsyncStatus FinalValidation();
+
+  QuicSpdyClientSessionBase* session_;
+  QuicStreamId id_;
+  QuicString url_;
+  spdy::SpdyHeaderBlock request_headers_;
+  std::unique_ptr<spdy::SpdyHeaderBlock> response_headers_;
+  spdy::SpdyHeaderBlock client_request_headers_;
+  QuicClientPushPromiseIndex::Delegate* client_request_delegate_;
+
+  // The promise will commit suicide eventually if it is not claimed by a GET
+  // first.
+  std::unique_ptr<QuicAlarm> cleanup_alarm_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PROMISED_INFO_H_
diff --git a/quic/core/http/quic_client_promised_info_test.cc b/quic/core/http/quic_client_promised_info_test.cc
new file mode 100644
index 0000000..2a7b1b0
--- /dev/null
+++ b/quic/core/http/quic_client_promised_info_test.cc
@@ -0,0 +1,355 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h"
+
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.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/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_client_promised_info_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+using spdy::SpdyHeaderBlock;
+using testing::_;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class MockQuicSpdyClientSession : public QuicSpdyClientSession {
+ public:
+  explicit MockQuicSpdyClientSession(
+      const ParsedQuicVersionVector& supported_versions,
+      QuicConnection* connection,
+      QuicClientPushPromiseIndex* push_promise_index)
+      : QuicSpdyClientSession(DefaultQuicConfig(),
+                              supported_versions,
+                              connection,
+                              QuicServerId("example.com", 443, false),
+                              &crypto_config_,
+                              push_promise_index),
+        crypto_config_(crypto_test_utils::ProofVerifierForTesting(),
+                       TlsClientHandshaker::CreateSslCtx()),
+        authorized_(true) {}
+  MockQuicSpdyClientSession(const MockQuicSpdyClientSession&) = delete;
+  MockQuicSpdyClientSession& operator=(const MockQuicSpdyClientSession&) =
+      delete;
+  ~MockQuicSpdyClientSession() override {}
+
+  bool IsAuthorized(const QuicString& authority) override {
+    return authorized_;
+  }
+
+  void set_authorized(bool authorized) { authorized_ = authorized; }
+
+  MOCK_METHOD1(CloseStream, void(QuicStreamId stream_id));
+
+ private:
+  QuicCryptoClientConfig crypto_config_;
+
+  bool authorized_;
+};
+
+class QuicClientPromisedInfoTest : public QuicTest {
+ public:
+  class StreamVisitor;
+
+  QuicClientPromisedInfoTest()
+      : connection_(new StrictMock<MockQuicConnection>(&helper_,
+                                                       &alarm_factory_,
+                                                       Perspective::IS_CLIENT)),
+        session_(connection_->supported_versions(),
+                 connection_,
+                 &push_promise_index_),
+        body_("hello world"),
+        promise_id_(
+            QuicUtils::GetInvalidStreamId(connection_->transport_version())) {
+    connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    session_.Initialize();
+
+    headers_[":status"] = "200";
+    headers_["content-length"] = "11";
+
+    stream_ = QuicMakeUnique<QuicSpdyClientStream>(
+        GetNthClientInitiatedBidirectionalStreamId(
+            connection_->transport_version(), 0),
+        &session_, BIDIRECTIONAL);
+    stream_visitor_ = QuicMakeUnique<StreamVisitor>();
+    stream_->set_visitor(stream_visitor_.get());
+
+    push_promise_[":path"] = "/bar";
+    push_promise_[":authority"] = "www.google.com";
+    push_promise_[":version"] = "HTTP/1.1";
+    push_promise_[":method"] = "GET";
+    push_promise_[":scheme"] = "https";
+
+    promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
+
+    client_request_ = push_promise_.Clone();
+    promise_id_ = GetNthServerInitiatedUnidirectionalStreamId(
+        connection_->transport_version(), 0);
+  }
+
+  class StreamVisitor : public QuicSpdyClientStream::Visitor {
+    void OnClose(QuicSpdyStream* stream) override {
+      QUIC_DVLOG(1) << "stream " << stream->id();
+    }
+  };
+
+  void ReceivePromise(QuicStreamId id) {
+    auto headers = AsHeaderList(push_promise_);
+    stream_->OnPromiseHeaderList(id, headers.uncompressed_header_bytes(),
+                                 headers);
+  }
+
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnection>* connection_;
+  QuicClientPushPromiseIndex push_promise_index_;
+
+  MockQuicSpdyClientSession session_;
+  std::unique_ptr<QuicSpdyClientStream> stream_;
+  std::unique_ptr<StreamVisitor> stream_visitor_;
+  std::unique_ptr<QuicSpdyClientStream> promised_stream_;
+  SpdyHeaderBlock headers_;
+  QuicString body_;
+  SpdyHeaderBlock push_promise_;
+  QuicStreamId promise_id_;
+  QuicString promise_url_;
+  SpdyHeaderBlock client_request_;
+};
+
+TEST_F(QuicClientPromisedInfoTest, PushPromise) {
+  ReceivePromise(promise_id_);
+
+  // Verify that the promise is in the unclaimed streams map.
+  EXPECT_NE(session_.GetPromisedById(promise_id_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseCleanupAlarm) {
+  ReceivePromise(promise_id_);
+
+  // Verify that the promise is in the unclaimed streams map.
+  QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+  ASSERT_NE(promised, nullptr);
+
+  // Fire the alarm that will cancel the promised stream.
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promise_id_, QUIC_PUSH_STREAM_TIMED_OUT));
+  alarm_factory_.FireAlarm(QuicClientPromisedInfoPeer::GetAlarm(promised));
+
+  // Verify that the promise is gone after the alarm fires.
+  EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+  EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseInvalidMethod) {
+  // Promise with an unsafe method
+  push_promise_[":method"] = "PUT";
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promise_id_, QUIC_INVALID_PROMISE_METHOD));
+  ReceivePromise(promise_id_);
+
+  // Verify that the promise headers were ignored
+  EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+  EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseMissingMethod) {
+  // Promise with a missing method
+  push_promise_.erase(":method");
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promise_id_, QUIC_INVALID_PROMISE_METHOD));
+  ReceivePromise(promise_id_);
+
+  // Verify that the promise headers were ignored
+  EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+  EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseInvalidUrl) {
+  // Remove required header field to make URL invalid
+  push_promise_.erase(":authority");
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promise_id_, QUIC_INVALID_PROMISE_URL));
+  ReceivePromise(promise_id_);
+
+  // Verify that the promise headers were ignored
+  EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+  EXPECT_EQ(session_.GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseUnauthorizedUrl) {
+  session_.set_authorized(false);
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promise_id_, QUIC_UNAUTHORIZED_PROMISE_URL));
+
+  ReceivePromise(promise_id_);
+
+  QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+  ASSERT_EQ(promised, nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseMismatch) {
+  ReceivePromise(promise_id_);
+
+  QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+  ASSERT_NE(promised, nullptr);
+
+  // Need to send the promised response headers and initiate the
+  // rendezvous for secondary validation to proceed.
+  QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>(
+      session_.GetOrCreateStream(promise_id_));
+  auto headers = AsHeaderList(headers_);
+  promise_stream->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                                     headers);
+
+  TestPushPromiseDelegate delegate(/*match=*/false);
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promise_id_, QUIC_PROMISE_VARY_MISMATCH));
+  EXPECT_CALL(session_, CloseStream(promise_id_));
+
+  promised->HandleClientRequest(client_request_, &delegate);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseVaryWaits) {
+  ReceivePromise(promise_id_);
+
+  QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+  EXPECT_FALSE(promised->is_validating());
+  ASSERT_NE(promised, nullptr);
+
+  // Now initiate rendezvous.
+  TestPushPromiseDelegate delegate(/*match=*/true);
+  promised->HandleClientRequest(client_request_, &delegate);
+  EXPECT_TRUE(promised->is_validating());
+
+  // Promise is still there, waiting for response.
+  EXPECT_NE(session_.GetPromisedById(promise_id_), nullptr);
+
+  // Send Response, should trigger promise validation and complete rendezvous
+  QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>(
+      session_.GetOrCreateStream(promise_id_));
+  ASSERT_NE(promise_stream, nullptr);
+  auto headers = AsHeaderList(headers_);
+  promise_stream->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                                     headers);
+
+  // Promise is gone
+  EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseVaryNoWait) {
+  ReceivePromise(promise_id_);
+
+  QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+  ASSERT_NE(promised, nullptr);
+
+  QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>(
+      session_.GetOrCreateStream(promise_id_));
+  ASSERT_NE(promise_stream, nullptr);
+
+  // Send Response, should trigger promise validation and complete rendezvous
+  auto headers = AsHeaderList(headers_);
+  promise_stream->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                                     headers);
+
+  // Now initiate rendezvous.
+  TestPushPromiseDelegate delegate(/*match=*/true);
+  promised->HandleClientRequest(client_request_, &delegate);
+
+  // Promise is gone
+  EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+  // Have a push stream
+  EXPECT_TRUE(delegate.rendezvous_fired());
+
+  EXPECT_NE(delegate.rendezvous_stream(), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseWaitCancels) {
+  ReceivePromise(promise_id_);
+
+  QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+  ASSERT_NE(promised, nullptr);
+
+  // Now initiate rendezvous.
+  TestPushPromiseDelegate delegate(/*match=*/true);
+  promised->HandleClientRequest(client_request_, &delegate);
+
+  // Promise is still there, waiting for response.
+  EXPECT_NE(session_.GetPromisedById(promise_id_), nullptr);
+
+  // Create response stream, but no data yet.
+  session_.GetOrCreateStream(promise_id_);
+
+  // Cancel the promised stream.
+  EXPECT_CALL(session_, CloseStream(promise_id_));
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(promise_id_, QUIC_STREAM_CANCELLED));
+  promised->Cancel();
+
+  // Promise is gone
+  EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+}
+
+TEST_F(QuicClientPromisedInfoTest, PushPromiseDataClosed) {
+  ReceivePromise(promise_id_);
+
+  QuicClientPromisedInfo* promised = session_.GetPromisedById(promise_id_);
+  ASSERT_NE(promised, nullptr);
+
+  QuicSpdyClientStream* promise_stream = static_cast<QuicSpdyClientStream*>(
+      session_.GetOrCreateStream(promise_id_));
+  ASSERT_NE(promise_stream, nullptr);
+
+  // Send response, rendezvous will be able to finish synchronously.
+  auto headers = AsHeaderList(headers_);
+  promise_stream->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                                     headers);
+
+  EXPECT_CALL(session_, CloseStream(promise_id_));
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promise_id_, QUIC_STREAM_PEER_GOING_AWAY));
+  session_.SendRstStream(promise_id_, QUIC_STREAM_PEER_GOING_AWAY, 0);
+
+  // Now initiate rendezvous.
+  TestPushPromiseDelegate delegate(/*match=*/true);
+  EXPECT_EQ(promised->HandleClientRequest(client_request_, &delegate),
+            QUIC_FAILURE);
+
+  // Got an indication of the stream failure, client should retry
+  // request.
+  EXPECT_FALSE(delegate.rendezvous_fired());
+  EXPECT_EQ(delegate.rendezvous_stream(), nullptr);
+
+  // Promise is gone
+  EXPECT_EQ(session_.GetPromisedById(promise_id_), nullptr);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_client_push_promise_index.cc b/quic/core/http/quic_client_push_promise_index.cc
new file mode 100644
index 0000000..2ba5951
--- /dev/null
+++ b/quic/core/http/quic_client_push_promise_index.cc
@@ -0,0 +1,47 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.h"
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+QuicClientPushPromiseIndex::QuicClientPushPromiseIndex() {}
+
+QuicClientPushPromiseIndex::~QuicClientPushPromiseIndex() {}
+
+QuicClientPushPromiseIndex::TryHandle::~TryHandle() {}
+
+QuicClientPromisedInfo* QuicClientPushPromiseIndex::GetPromised(
+    const QuicString& url) {
+  auto it = promised_by_url_.find(url);
+  if (it == promised_by_url_.end()) {
+    return nullptr;
+  }
+  return it->second;
+}
+
+QuicAsyncStatus QuicClientPushPromiseIndex::Try(
+    const spdy::SpdyHeaderBlock& request,
+    QuicClientPushPromiseIndex::Delegate* delegate,
+    TryHandle** handle) {
+  QuicString url(SpdyUtils::GetPromisedUrlFromHeaders(request));
+  auto it = promised_by_url_.find(url);
+  if (it != promised_by_url_.end()) {
+    QuicClientPromisedInfo* promised = it->second;
+    QuicAsyncStatus rv = promised->HandleClientRequest(request, delegate);
+    if (rv == QUIC_PENDING) {
+      *handle = promised;
+    }
+    return rv;
+  }
+  return QUIC_FAILURE;
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_client_push_promise_index.h b/quic/core/http/quic_client_push_promise_index.h
new file mode 100644
index 0000000..5dd29b4
--- /dev/null
+++ b/quic/core/http/quic_client_push_promise_index.h
@@ -0,0 +1,98 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PUSH_PROMISE_INDEX_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PUSH_PROMISE_INDEX_H_
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+// QuicClientPushPromiseIndex is the interface to support rendezvous
+// between client requests and resources delivered via server push.
+// The same index can be shared across multiple sessions (e.g. for the
+// same browser users profile), since cross-origin pushes are allowed
+// (subject to authority constraints).
+
+class QUIC_EXPORT_PRIVATE QuicClientPushPromiseIndex {
+ public:
+  // Delegate is used to complete the rendezvous that began with
+  // |Try()|.
+  class QUIC_EXPORT_PRIVATE Delegate {
+   public:
+    virtual ~Delegate() {}
+
+    // The primary lookup matched request with push promise by URL.  A
+    // secondary match is necessary to ensure Vary (RFC 2616, 14.14)
+    // is honored.  If Vary is not present, return true.  If Vary is
+    // present, return whether designated header fields of
+    // |promise_request| and |client_request| match.
+    virtual bool CheckVary(const spdy::SpdyHeaderBlock& client_request,
+                           const spdy::SpdyHeaderBlock& promise_request,
+                           const spdy::SpdyHeaderBlock& promise_response) = 0;
+
+    // On rendezvous success, provides the promised |stream|.  Callee
+    // does not inherit ownership of |stream|.  On rendezvous failure,
+    // |stream| is |nullptr| and the client should retry the request.
+    // Rendezvous can fail due to promise validation failure or RST on
+    // promised stream.  |url| will have been removed from the index
+    // before |OnRendezvousResult()| is invoked, so a recursive call to
+    // |Try()| will return |QUIC_FAILURE|, which may be convenient for
+    // retry purposes.
+    virtual void OnRendezvousResult(QuicSpdyStream* stream) = 0;
+  };
+
+  class QUIC_EXPORT_PRIVATE TryHandle {
+   public:
+    // Cancel the request.
+    virtual void Cancel() = 0;
+
+   protected:
+    TryHandle() {}
+    TryHandle(const TryHandle&) = delete;
+    TryHandle& operator=(const TryHandle&) = delete;
+    ~TryHandle();
+  };
+
+  QuicClientPushPromiseIndex();
+  QuicClientPushPromiseIndex(const QuicClientPushPromiseIndex&) = delete;
+  QuicClientPushPromiseIndex& operator=(const QuicClientPushPromiseIndex&) =
+      delete;
+  virtual ~QuicClientPushPromiseIndex();
+
+  // Called by client code, used to enforce affinity between requests
+  // for promised streams and the session the promise came from.
+  QuicClientPromisedInfo* GetPromised(const QuicString& url);
+
+  // Called by client code, to initiate rendezvous between a request
+  // and a server push stream.  If |request|'s url is in the index,
+  // rendezvous will be attempted and may complete immediately or
+  // asynchronously.  If the matching promise and response headers
+  // have already arrived, the delegate's methods will fire
+  // recursively from within |Try()|.  Returns |QUIC_SUCCESS| if the
+  // rendezvous was a success. Returns |QUIC_FAILURE| if there was no
+  // matching promise, or if there was but the rendezvous has failed.
+  // Returns QUIC_PENDING if a matching promise was found, but the
+  // rendezvous needs to complete asynchronously because the promised
+  // response headers are not yet available.  If result is
+  // QUIC_PENDING, then |*handle| will set so that the caller may
+  // cancel the request if need be.  The caller does not inherit
+  // ownership of |*handle|, and it ceases to be valid if the caller
+  // invokes |handle->Cancel()| or if |delegate->OnReponse()| fires.
+  QuicAsyncStatus Try(const spdy::SpdyHeaderBlock& request,
+                      Delegate* delegate,
+                      TryHandle** handle);
+
+  QuicPromisedByUrlMap* promised_by_url() { return &promised_by_url_; }
+
+ private:
+  QuicPromisedByUrlMap promised_by_url_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_CLIENT_PUSH_PROMISE_INDEX_H_
diff --git a/quic/core/http/quic_client_push_promise_index_test.cc b/quic/core/http/quic_client_push_promise_index_test.cc
new file mode 100644
index 0000000..b68b41f
--- /dev/null
+++ b/quic/core/http/quic_client_push_promise_index_test.cc
@@ -0,0 +1,117 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.h"
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.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/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_quic_client_promised_info.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+using testing::_;
+using testing::Return;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class MockQuicSpdyClientSession : public QuicSpdyClientSession {
+ public:
+  explicit MockQuicSpdyClientSession(
+      const ParsedQuicVersionVector& supported_versions,
+      QuicConnection* connection,
+      QuicClientPushPromiseIndex* push_promise_index)
+      : QuicSpdyClientSession(DefaultQuicConfig(),
+                              supported_versions,
+                              connection,
+                              QuicServerId("example.com", 443, false),
+                              &crypto_config_,
+                              push_promise_index),
+        crypto_config_(crypto_test_utils::ProofVerifierForTesting(),
+                       TlsClientHandshaker::CreateSslCtx()) {}
+  MockQuicSpdyClientSession(const MockQuicSpdyClientSession&) = delete;
+  MockQuicSpdyClientSession& operator=(const MockQuicSpdyClientSession&) =
+      delete;
+  ~MockQuicSpdyClientSession() override {}
+
+  MOCK_METHOD1(CloseStream, void(QuicStreamId stream_id));
+
+ private:
+  QuicCryptoClientConfig crypto_config_;
+};
+
+class QuicClientPushPromiseIndexTest : public QuicTest {
+ public:
+  QuicClientPushPromiseIndexTest()
+      : connection_(new StrictMock<MockQuicConnection>(&helper_,
+                                                       &alarm_factory_,
+                                                       Perspective::IS_CLIENT)),
+        session_(connection_->supported_versions(), connection_, &index_),
+        promised_(&session_,
+                  GetNthServerInitiatedUnidirectionalStreamId(
+                      connection_->transport_version(),
+                      0),
+                  url_) {
+    request_[":path"] = "/bar";
+    request_[":authority"] = "www.google.com";
+    request_[":version"] = "HTTP/1.1";
+    request_[":method"] = "GET";
+    request_[":scheme"] = "https";
+    url_ = SpdyUtils::GetPromisedUrlFromHeaders(request_);
+  }
+
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnection>* connection_;
+  MockQuicSpdyClientSession session_;
+  QuicClientPushPromiseIndex index_;
+  spdy::SpdyHeaderBlock request_;
+  QuicString url_;
+  MockQuicClientPromisedInfo promised_;
+  QuicClientPushPromiseIndex::TryHandle* handle_;
+};
+
+TEST_F(QuicClientPushPromiseIndexTest, TryRequestSuccess) {
+  (*index_.promised_by_url())[url_] = &promised_;
+  EXPECT_CALL(promised_, HandleClientRequest(_, _))
+      .WillOnce(Return(QUIC_SUCCESS));
+  EXPECT_EQ(index_.Try(request_, nullptr, &handle_), QUIC_SUCCESS);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, TryRequestPending) {
+  (*index_.promised_by_url())[url_] = &promised_;
+  EXPECT_CALL(promised_, HandleClientRequest(_, _))
+      .WillOnce(Return(QUIC_PENDING));
+  EXPECT_EQ(index_.Try(request_, nullptr, &handle_), QUIC_PENDING);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, TryRequestFailure) {
+  (*index_.promised_by_url())[url_] = &promised_;
+  EXPECT_CALL(promised_, HandleClientRequest(_, _))
+      .WillOnce(Return(QUIC_FAILURE));
+  EXPECT_EQ(index_.Try(request_, nullptr, &handle_), QUIC_FAILURE);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, TryNoPromise) {
+  EXPECT_EQ(index_.Try(request_, nullptr, &handle_), QUIC_FAILURE);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, GetNoPromise) {
+  EXPECT_EQ(index_.GetPromised(url_), nullptr);
+}
+
+TEST_F(QuicClientPushPromiseIndexTest, GetPromise) {
+  (*index_.promised_by_url())[url_] = &promised_;
+  EXPECT_EQ(index_.GetPromised(url_), &promised_);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_header_list.cc b/quic/core/http/quic_header_list.cc
new file mode 100644
index 0000000..cb10248
--- /dev/null
+++ b/quic/core/http/quic_header_list.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+namespace quic {
+
+QuicHeaderList::QuicHeaderList()
+    : max_header_list_size_(kDefaultMaxUncompressedHeaderSize),
+      current_header_list_size_(0),
+      uncompressed_header_bytes_(0),
+      compressed_header_bytes_(0) {}
+
+QuicHeaderList::QuicHeaderList(QuicHeaderList&& other) = default;
+
+QuicHeaderList::QuicHeaderList(const QuicHeaderList& other) = default;
+
+QuicHeaderList& QuicHeaderList::operator=(const QuicHeaderList& other) =
+    default;
+
+QuicHeaderList& QuicHeaderList::operator=(QuicHeaderList&& other) = default;
+
+QuicHeaderList::~QuicHeaderList() {}
+
+void QuicHeaderList::OnHeaderBlockStart() {
+  QUIC_BUG_IF(current_header_list_size_ != 0)
+      << "OnHeaderBlockStart called more than once!";
+}
+
+void QuicHeaderList::OnHeader(QuicStringPiece name, QuicStringPiece value) {
+  // Avoid infinite buffering of headers. No longer store headers
+  // once the current headers are over the limit.
+  if (current_header_list_size_ < max_header_list_size_) {
+    current_header_list_size_ += name.size();
+    current_header_list_size_ += value.size();
+    current_header_list_size_ += spdy::kPerHeaderOverhead;
+    header_list_.emplace_back(QuicString(name), QuicString(value));
+  }
+}
+
+void QuicHeaderList::OnHeaderBlockEnd(size_t uncompressed_header_bytes,
+                                      size_t compressed_header_bytes) {
+  uncompressed_header_bytes_ = uncompressed_header_bytes;
+  compressed_header_bytes_ = compressed_header_bytes;
+  if (current_header_list_size_ > max_header_list_size_) {
+    Clear();
+  }
+}
+
+void QuicHeaderList::Clear() {
+  header_list_.clear();
+  current_header_list_size_ = 0;
+  uncompressed_header_bytes_ = 0;
+  compressed_header_bytes_ = 0;
+}
+
+QuicString QuicHeaderList::DebugString() const {
+  QuicString s = "{ ";
+  for (const auto& p : *this) {
+    s.append(p.first + "=" + p.second + ", ");
+  }
+  s.append("}");
+  return s;
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_header_list.h b/quic/core/http/quic_header_list.h
new file mode 100644
index 0000000..c7b4c80
--- /dev/null
+++ b/quic/core/http/quic_header_list.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_HEADER_LIST_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_HEADER_LIST_H_
+
+#include <algorithm>
+#include <functional>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.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"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+
+namespace quic {
+
+// A simple class that accumulates header pairs
+class QUIC_EXPORT_PRIVATE QuicHeaderList
+    : public spdy::SpdyHeadersHandlerInterface {
+ public:
+  typedef QuicDeque<std::pair<QuicString, QuicString>> ListType;
+  typedef ListType::const_iterator const_iterator;
+
+  QuicHeaderList();
+  QuicHeaderList(QuicHeaderList&& other);
+  QuicHeaderList(const QuicHeaderList& other);
+  QuicHeaderList& operator=(QuicHeaderList&& other);
+  QuicHeaderList& operator=(const QuicHeaderList& other);
+  ~QuicHeaderList() override;
+
+  // From SpdyHeadersHandlerInteface.
+  void OnHeaderBlockStart() override;
+  void OnHeader(QuicStringPiece name, QuicStringPiece value) override;
+  void OnHeaderBlockEnd(size_t uncompressed_header_bytes,
+                        size_t compressed_header_bytes) override;
+
+  void Clear();
+
+  const_iterator begin() const { return header_list_.begin(); }
+  const_iterator end() const { return header_list_.end(); }
+
+  bool empty() const { return header_list_.empty(); }
+  size_t uncompressed_header_bytes() const {
+    return uncompressed_header_bytes_;
+  }
+  size_t compressed_header_bytes() const { return compressed_header_bytes_; }
+
+  void set_max_header_list_size(size_t max_header_list_size) {
+    max_header_list_size_ = max_header_list_size;
+  }
+
+  size_t max_header_list_size() const { return max_header_list_size_; }
+
+  QuicString DebugString() const;
+
+ private:
+  QuicDeque<std::pair<QuicString, QuicString>> header_list_;
+
+  // The limit on the size of the header list (defined by spec as name + value +
+  // overhead for each header field). Headers over this limit will not be
+  // buffered, and the list will be cleared upon OnHeaderBlockEnd.
+  size_t max_header_list_size_;
+
+  // Defined per the spec as the size of all header fields with an additional
+  // overhead for each field.
+  size_t current_header_list_size_;
+
+  // TODO(dahollings) Are these fields necessary?
+  size_t uncompressed_header_bytes_;
+  size_t compressed_header_bytes_;
+};
+
+inline bool operator==(const QuicHeaderList& l1, const QuicHeaderList& l2) {
+  auto pred = [](const std::pair<QuicString, QuicString>& p1,
+                 const std::pair<QuicString, QuicString>& p2) {
+    return p1.first == p2.first && p1.second == p2.second;
+  };
+  return std::equal(l1.begin(), l1.end(), l2.begin(), pred);
+}
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_HEADER_LIST_H_
diff --git a/quic/core/http/quic_header_list_test.cc b/quic/core/http/quic_header_list_test.cc
new file mode 100644
index 0000000..1b2a394
--- /dev/null
+++ b/quic/core/http/quic_header_list_test.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.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"
+
+namespace quic {
+
+class QuicHeaderListTest : public QuicTest {};
+
+// This test verifies that QuicHeaderList accumulates header pairs in order.
+TEST_F(QuicHeaderListTest, OnHeader) {
+  QuicHeaderList headers;
+  headers.OnHeader("foo", "bar");
+  headers.OnHeader("april", "fools");
+  headers.OnHeader("beep", "");
+
+  EXPECT_EQ("{ foo=bar, april=fools, beep=, }", headers.DebugString());
+}
+
+TEST_F(QuicHeaderListTest, TooLarge) {
+  QuicHeaderList headers;
+  QuicString key = "key";
+  QuicString value(1 << 18, '1');
+  // Send a header that exceeds max_header_list_size.
+  headers.OnHeader(key, value);
+  // Send a second header exceeding max_header_list_size.
+  headers.OnHeader(key + "2", value);
+  // We should not allocate more memory after exceeding max_header_list_size.
+  EXPECT_LT(headers.DebugString().size(), 2 * value.size());
+  size_t total_bytes = 2 * (key.size() + value.size()) + 1;
+  headers.OnHeaderBlockEnd(total_bytes, total_bytes);
+  EXPECT_TRUE(headers.empty());
+
+  EXPECT_EQ("{ }", headers.DebugString());
+}
+
+TEST_F(QuicHeaderListTest, NotTooLarge) {
+  QuicHeaderList headers;
+  headers.set_max_header_list_size(1 << 20);
+  QuicString key = "key";
+  QuicString value(1 << 18, '1');
+  headers.OnHeader(key, value);
+  size_t total_bytes = key.size() + value.size();
+  headers.OnHeaderBlockEnd(total_bytes, total_bytes);
+  EXPECT_FALSE(headers.empty());
+}
+
+// This test verifies that QuicHeaderList is copyable and assignable.
+TEST_F(QuicHeaderListTest, IsCopyableAndAssignable) {
+  QuicHeaderList headers;
+  headers.OnHeader("foo", "bar");
+  headers.OnHeader("april", "fools");
+  headers.OnHeader("beep", "");
+
+  QuicHeaderList headers2(headers);
+  QuicHeaderList headers3 = headers;
+
+  EXPECT_EQ("{ foo=bar, april=fools, beep=, }", headers2.DebugString());
+  EXPECT_EQ("{ foo=bar, april=fools, beep=, }", headers3.DebugString());
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_headers_stream.cc b/quic/core/http/quic_headers_stream.cc
new file mode 100644
index 0000000..9c99ab7
--- /dev/null
+++ b/quic/core/http/quic_headers_stream.cc
@@ -0,0 +1,157 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_headers_stream.h"
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+
+namespace quic {
+
+QuicHeadersStream::CompressedHeaderInfo::CompressedHeaderInfo(
+    QuicStreamOffset headers_stream_offset,
+    QuicStreamOffset full_length,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener)
+    : headers_stream_offset(headers_stream_offset),
+      full_length(full_length),
+      unacked_length(full_length),
+      ack_listener(std::move(ack_listener)) {}
+
+QuicHeadersStream::CompressedHeaderInfo::CompressedHeaderInfo(
+    const CompressedHeaderInfo& other) = default;
+
+QuicHeadersStream::CompressedHeaderInfo::~CompressedHeaderInfo() {}
+
+QuicHeadersStream::QuicHeadersStream(QuicSpdySession* session)
+    : QuicStream(QuicUtils::GetHeadersStreamId(
+                     session->connection()->transport_version()),
+                 session,
+                 /*is_static=*/true,
+                 BIDIRECTIONAL),
+      spdy_session_(session) {
+  // The headers stream is exempt from connection level flow control.
+  DisableConnectionFlowControlForThisStream();
+}
+
+QuicHeadersStream::~QuicHeadersStream() {}
+
+void QuicHeadersStream::OnDataAvailable() {
+  struct iovec iov;
+  while (sequencer()->GetReadableRegion(&iov)) {
+    if (spdy_session_->ProcessHeaderData(iov) != iov.iov_len) {
+      // Error processing data.
+      return;
+    }
+    sequencer()->MarkConsumed(iov.iov_len);
+    MaybeReleaseSequencerBuffer();
+  }
+}
+
+void QuicHeadersStream::MaybeReleaseSequencerBuffer() {
+  if (spdy_session_->ShouldReleaseHeadersStreamSequencerBuffer()) {
+    sequencer()->ReleaseBufferIfEmpty();
+  }
+}
+
+bool QuicHeadersStream::OnStreamFrameAcked(QuicStreamOffset offset,
+                                           QuicByteCount data_length,
+                                           bool fin_acked,
+                                           QuicTime::Delta ack_delay_time,
+                                           QuicByteCount* newly_acked_length) {
+  QuicIntervalSet<QuicStreamOffset> newly_acked(offset, offset + data_length);
+  newly_acked.Difference(bytes_acked());
+  for (const auto& acked : newly_acked) {
+    QuicStreamOffset acked_offset = acked.min();
+    QuicByteCount acked_length = acked.max() - acked.min();
+    for (CompressedHeaderInfo& header : unacked_headers_) {
+      if (acked_offset < header.headers_stream_offset) {
+        // This header frame offset belongs to headers with smaller offset, stop
+        // processing.
+        break;
+      }
+
+      if (acked_offset >= header.headers_stream_offset + header.full_length) {
+        // This header frame belongs to headers with larger offset.
+        continue;
+      }
+
+      QuicByteCount header_offset = acked_offset - header.headers_stream_offset;
+      QuicByteCount header_length =
+          std::min(acked_length, header.full_length - header_offset);
+
+      if (header.unacked_length < header_length) {
+        QUIC_BUG << "Unsent stream data is acked. unacked_length: "
+                 << header.unacked_length << " acked_length: " << header_length;
+        CloseConnectionWithDetails(QUIC_INTERNAL_ERROR,
+                                   "Unsent stream data is acked");
+        return false;
+      }
+      if (header.ack_listener != nullptr && header_length > 0) {
+        header.ack_listener->OnPacketAcked(header_length, ack_delay_time);
+      }
+      header.unacked_length -= header_length;
+      acked_offset += header_length;
+      acked_length -= header_length;
+    }
+  }
+  // Remove headers which are fully acked. Please note, header frames can be
+  // acked out of order, but unacked_headers_ is cleaned up in order.
+  while (!unacked_headers_.empty() &&
+         unacked_headers_.front().unacked_length == 0) {
+    unacked_headers_.pop_front();
+  }
+  return QuicStream::OnStreamFrameAcked(offset, data_length, fin_acked,
+                                        ack_delay_time, newly_acked_length);
+}
+
+void QuicHeadersStream::OnStreamFrameRetransmitted(QuicStreamOffset offset,
+                                                   QuicByteCount data_length,
+                                                   bool /*fin_retransmitted*/) {
+  QuicStream::OnStreamFrameRetransmitted(offset, data_length, false);
+  for (CompressedHeaderInfo& header : unacked_headers_) {
+    if (offset < header.headers_stream_offset) {
+      // This header frame offset belongs to headers with smaller offset, stop
+      // processing.
+      break;
+    }
+
+    if (offset >= header.headers_stream_offset + header.full_length) {
+      // This header frame belongs to headers with larger offset.
+      continue;
+    }
+
+    QuicByteCount header_offset = offset - header.headers_stream_offset;
+    QuicByteCount retransmitted_length =
+        std::min(data_length, header.full_length - header_offset);
+    if (header.ack_listener != nullptr && retransmitted_length > 0) {
+      header.ack_listener->OnPacketRetransmitted(retransmitted_length);
+    }
+    offset += retransmitted_length;
+    data_length -= retransmitted_length;
+  }
+}
+
+void QuicHeadersStream::OnDataBuffered(
+    QuicStreamOffset offset,
+    QuicByteCount data_length,
+    const QuicReferenceCountedPointer<QuicAckListenerInterface>& ack_listener) {
+  // Populate unacked_headers_.
+  if (!unacked_headers_.empty() &&
+      (offset == unacked_headers_.back().headers_stream_offset +
+                     unacked_headers_.back().full_length) &&
+      ack_listener == unacked_headers_.back().ack_listener) {
+    // Try to combine with latest inserted entry if they belong to the same
+    // header (i.e., having contiguous offset and the same ack listener).
+    unacked_headers_.back().full_length += data_length;
+    unacked_headers_.back().unacked_length += data_length;
+  } else {
+    unacked_headers_.push_back(
+        CompressedHeaderInfo(offset, data_length, ack_listener));
+  }
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_headers_stream.h b/quic/core/http/quic_headers_stream.h
new file mode 100644
index 0000000..abc0aeb
--- /dev/null
+++ b/quic/core/http/quic_headers_stream.h
@@ -0,0 +1,97 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_HEADERS_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_HEADERS_STREAM_H_
+
+#include <cstddef>
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+class QuicSpdySession;
+
+namespace test {
+class QuicHeadersStreamPeer;
+}  // namespace test
+
+// Headers in QUIC are sent as HTTP/2 HEADERS or PUSH_PROMISE frames over a
+// reserved stream with the id 3.  Each endpoint (client and server) will
+// allocate an instance of QuicHeadersStream to send and receive headers.
+class QUIC_EXPORT_PRIVATE QuicHeadersStream : public QuicStream {
+ public:
+  explicit QuicHeadersStream(QuicSpdySession* session);
+  QuicHeadersStream(const QuicHeadersStream&) = delete;
+  QuicHeadersStream& operator=(const QuicHeadersStream&) = delete;
+  ~QuicHeadersStream() override;
+
+  // QuicStream implementation
+  void OnDataAvailable() override;
+
+  // Release underlying buffer if allowed.
+  void MaybeReleaseSequencerBuffer();
+
+  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;
+
+ private:
+  friend class test::QuicHeadersStreamPeer;
+
+  // CompressedHeaderInfo includes simple information of a header, including
+  // offset in headers stream, unacked length and ack listener of this header.
+  struct QUIC_EXPORT_PRIVATE CompressedHeaderInfo {
+    CompressedHeaderInfo(
+        QuicStreamOffset headers_stream_offset,
+        QuicStreamOffset full_length,
+        QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+    CompressedHeaderInfo(const CompressedHeaderInfo& other);
+    ~CompressedHeaderInfo();
+
+    // Offset the header was sent on the headers stream.
+    QuicStreamOffset headers_stream_offset;
+    // The full length of the header.
+    QuicByteCount full_length;
+    // The remaining bytes to be acked.
+    QuicByteCount unacked_length;
+    // Ack listener of this header, and it is notified once any of the bytes has
+    // been acked or retransmitted.
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener;
+  };
+
+  // Returns true if the session is still connected.
+  bool IsConnected();
+
+  // Override to store mapping from offset, length to ack_listener. This
+  // ack_listener is notified once data within [offset, offset + length] is
+  // acked or retransmitted.
+  void OnDataBuffered(
+      QuicStreamOffset offset,
+      QuicByteCount data_length,
+      const QuicReferenceCountedPointer<QuicAckListenerInterface>& ack_listener)
+      override;
+
+  QuicSpdySession* spdy_session_;
+
+  // Headers that have not been fully acked.
+  QuicDeque<CompressedHeaderInfo> unacked_headers_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_HEADERS_STREAM_H_
diff --git a/quic/core/http/quic_headers_stream_test.cc b/quic/core/http/quic_headers_stream_test.cc
new file mode 100644
index 0000000..b6409b1
--- /dev/null
+++ b/quic/core/http/quic_headers_stream_test.cc
@@ -0,0 +1,968 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_headers_stream.h"
+
+#include <cstdint>
+#include <ostream>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.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"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+using spdy::ERROR_CODE_PROTOCOL_ERROR;
+using spdy::SETTINGS_ENABLE_PUSH;
+using spdy::SETTINGS_HEADER_TABLE_SIZE;
+using spdy::SETTINGS_INITIAL_WINDOW_SIZE;
+using spdy::SETTINGS_MAX_CONCURRENT_STREAMS;
+using spdy::SETTINGS_MAX_FRAME_SIZE;
+using spdy::SETTINGS_MAX_HEADER_LIST_SIZE;
+using spdy::Spdy3PriorityToHttp2Weight;
+using spdy::SpdyAltSvcWireFormat;
+using spdy::SpdyDataIR;
+using spdy::SpdyErrorCode;
+using spdy::SpdyFramer;
+using spdy::SpdyFramerVisitorInterface;
+using spdy::SpdyGoAwayIR;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyHeadersHandlerInterface;
+using spdy::SpdyHeadersIR;
+using spdy::SpdyKnownSettingsId;
+using spdy::SpdyPingId;
+using spdy::SpdyPingIR;
+using spdy::SpdyPriority;
+using spdy::SpdyPriorityIR;
+using spdy::SpdyPushPromiseIR;
+using spdy::SpdyRstStreamIR;
+using spdy::SpdySerializedFrame;
+using spdy::SpdySettingsId;
+using spdy::SpdySettingsIR;
+using spdy::SpdyStreamId;
+using spdy::SpdyWindowUpdateIR;
+using spdy::test::TestHeadersHandler;
+using testing::_;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+using testing::WithArgs;
+
+namespace quic {
+namespace test {
+
+class MockQuicHpackDebugVisitor : public QuicHpackDebugVisitor {
+ public:
+  MockQuicHpackDebugVisitor() : QuicHpackDebugVisitor() {}
+  MockQuicHpackDebugVisitor(const MockQuicHpackDebugVisitor&) = delete;
+  MockQuicHpackDebugVisitor& operator=(const MockQuicHpackDebugVisitor&) =
+      delete;
+
+  MOCK_METHOD1(OnUseEntry, void(QuicTime::Delta elapsed));
+};
+
+namespace {
+
+class MockVisitor : public SpdyFramerVisitorInterface {
+ public:
+  MOCK_METHOD1(OnError,
+               void(http2::Http2DecoderAdapter::SpdyFramerError error));
+  MOCK_METHOD3(OnDataFrameHeader,
+               void(SpdyStreamId stream_id, size_t length, bool fin));
+  MOCK_METHOD3(OnStreamFrameData,
+               void(SpdyStreamId stream_id, const char* data, size_t len));
+  MOCK_METHOD1(OnStreamEnd, void(SpdyStreamId stream_id));
+  MOCK_METHOD2(OnStreamPadding, void(SpdyStreamId stream_id, size_t len));
+  MOCK_METHOD1(OnHeaderFrameStart,
+               SpdyHeadersHandlerInterface*(SpdyStreamId stream_id));
+  MOCK_METHOD1(OnHeaderFrameEnd, void(SpdyStreamId stream_id));
+  MOCK_METHOD3(OnControlFrameHeaderData,
+               bool(SpdyStreamId stream_id,
+                    const char* header_data,
+                    size_t len));
+  MOCK_METHOD2(OnRstStream,
+               void(SpdyStreamId stream_id, SpdyErrorCode error_code));
+  MOCK_METHOD0(OnSettings, void());
+  MOCK_METHOD2(OnSetting, void(SpdySettingsId id, uint32_t value));
+  MOCK_METHOD0(OnSettingsAck, void());
+  MOCK_METHOD0(OnSettingsEnd, void());
+  MOCK_METHOD2(OnPing, void(SpdyPingId unique_id, bool is_ack));
+  MOCK_METHOD2(OnGoAway,
+               void(SpdyStreamId last_accepted_stream_id,
+                    SpdyErrorCode error_code));
+  MOCK_METHOD7(OnHeaders,
+               void(SpdyStreamId stream_id,
+                    bool has_priority,
+                    int weight,
+                    SpdyStreamId parent_stream_id,
+                    bool exclusive,
+                    bool fin,
+                    bool end));
+  MOCK_METHOD2(OnWindowUpdate,
+               void(SpdyStreamId stream_id, int delta_window_size));
+  MOCK_METHOD1(OnBlocked, void(SpdyStreamId stream_id));
+  MOCK_METHOD3(OnPushPromise,
+               void(SpdyStreamId stream_id,
+                    SpdyStreamId promised_stream_id,
+                    bool end));
+  MOCK_METHOD2(OnContinuation, void(SpdyStreamId stream_id, bool end));
+  MOCK_METHOD3(OnAltSvc,
+               void(SpdyStreamId stream_id,
+                    QuicStringPiece origin,
+                    const SpdyAltSvcWireFormat::AlternativeServiceVector&
+                        altsvc_vector));
+  MOCK_METHOD4(OnPriority,
+               void(SpdyStreamId stream_id,
+                    SpdyStreamId parent_stream_id,
+                    int weight,
+                    bool exclusive));
+  MOCK_METHOD2(OnUnknownFrame,
+               bool(SpdyStreamId stream_id, uint8_t frame_type));
+};
+
+struct TestParams {
+  TestParams(const ParsedQuicVersion& version, Perspective perspective)
+      : version(version), perspective(perspective) {
+    QUIC_LOG(INFO) << "TestParams: version: "
+                   << ParsedQuicVersionToString(version)
+                   << ", perspective: " << perspective;
+  }
+
+  TestParams(const TestParams& other)
+      : version(other.version), perspective(other.perspective) {}
+
+  ParsedQuicVersion version;
+  Perspective perspective;
+};
+
+std::vector<TestParams> GetTestParams() {
+  std::vector<TestParams> params;
+  ParsedQuicVersionVector all_supported_versions = AllSupportedVersions();
+  for (size_t i = 0; i < all_supported_versions.size(); ++i) {
+    for (Perspective p : {Perspective::IS_SERVER, Perspective::IS_CLIENT}) {
+      params.emplace_back(all_supported_versions[i], p);
+    }
+  }
+  return params;
+}
+
+class QuicHeadersStreamTest : public QuicTestWithParam<TestParams> {
+ public:
+  QuicHeadersStreamTest()
+      : connection_(new StrictMock<MockQuicConnection>(&helper_,
+                                                       &alarm_factory_,
+                                                       perspective(),
+                                                       GetVersion())),
+        session_(connection_),
+        body_("hello world"),
+        stream_frame_(
+            QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+            /*fin=*/false,
+            /*offset=*/0,
+            ""),
+        next_promised_stream_id_(2) {
+    session_.Initialize();
+    headers_stream_ = QuicSpdySessionPeer::GetHeadersStream(&session_);
+    headers_[":version"] = "HTTP/1.1";
+    headers_[":status"] = "200 Ok";
+    headers_["content-length"] = "11";
+    framer_ = std::unique_ptr<SpdyFramer>(
+        new SpdyFramer(SpdyFramer::ENABLE_COMPRESSION));
+    deframer_ = std::unique_ptr<http2::Http2DecoderAdapter>(
+        new http2::Http2DecoderAdapter());
+    deframer_->set_visitor(&visitor_);
+    EXPECT_EQ(transport_version(), session_.connection()->transport_version());
+    EXPECT_TRUE(headers_stream_ != nullptr);
+    connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+    client_id_1_ = GetNthClientInitiatedBidirectionalStreamId(
+        connection_->transport_version(), 0);
+    client_id_2_ = GetNthClientInitiatedBidirectionalStreamId(
+        connection_->transport_version(), 1);
+    client_id_3_ = GetNthClientInitiatedBidirectionalStreamId(
+        connection_->transport_version(), 2);
+    next_stream_id_ =
+        QuicUtils::StreamIdDelta(connection_->transport_version());
+  }
+
+  QuicStreamId GetNthClientInitiatedId(int n) {
+    return GetNthClientInitiatedBidirectionalStreamId(
+        connection_->transport_version(), n);
+  }
+
+  QuicConsumedData SaveIov(size_t write_length) {
+    char* buf = new char[write_length];
+    QuicDataWriter writer(write_length, buf, NETWORK_BYTE_ORDER);
+    headers_stream_->WriteStreamData(headers_stream_->stream_bytes_written(),
+                                     write_length, &writer);
+    saved_data_.append(buf, write_length);
+    delete[] buf;
+    return QuicConsumedData(write_length, false);
+  }
+
+  void SavePayload(const char* data, size_t len) {
+    saved_payloads_.append(data, len);
+  }
+
+  bool SaveHeaderData(const char* data, int len) {
+    saved_header_data_.append(data, len);
+    return true;
+  }
+
+  void SaveHeaderDataStringPiece(QuicStringPiece data) {
+    saved_header_data_.append(data.data(), data.length());
+  }
+
+  void SavePromiseHeaderList(QuicStreamId /* stream_id */,
+                             QuicStreamId /* promised_stream_id */,
+                             size_t size,
+                             const QuicHeaderList& header_list) {
+    SaveToHandler(size, header_list);
+  }
+
+  void SaveHeaderList(QuicStreamId /* stream_id */,
+                      bool /* fin */,
+                      size_t size,
+                      const QuicHeaderList& header_list) {
+    SaveToHandler(size, header_list);
+  }
+
+  void SaveToHandler(size_t size, const QuicHeaderList& header_list) {
+    headers_handler_ = QuicMakeUnique<TestHeadersHandler>();
+    headers_handler_->OnHeaderBlockStart();
+    for (const auto& p : header_list) {
+      headers_handler_->OnHeader(p.first, p.second);
+    }
+    headers_handler_->OnHeaderBlockEnd(size, size);
+  }
+
+  void WriteAndExpectRequestHeaders(QuicStreamId stream_id,
+                                    bool fin,
+                                    SpdyPriority priority) {
+    WriteHeadersAndCheckData(stream_id, fin, priority, true /*is_request*/);
+  }
+
+  void WriteAndExpectResponseHeaders(QuicStreamId stream_id, bool fin) {
+    WriteHeadersAndCheckData(stream_id, fin, 0, false /*is_request*/);
+  }
+
+  void WriteHeadersAndCheckData(QuicStreamId stream_id,
+                                bool fin,
+                                SpdyPriority priority,
+                                bool is_request) {
+    // Write the headers and capture the outgoing data
+    EXPECT_CALL(session_, WritevData(headers_stream_,
+                                     QuicUtils::GetHeadersStreamId(
+                                         connection_->transport_version()),
+                                     _, _, NO_FIN))
+        .WillOnce(WithArgs<2>(Invoke(this, &QuicHeadersStreamTest::SaveIov)));
+    QuicSpdySessionPeer::WriteHeadersOnHeadersStream(
+        &session_, stream_id, headers_.Clone(), fin, priority, nullptr);
+
+    // Parse the outgoing data and check that it matches was was written.
+    if (is_request) {
+      EXPECT_CALL(visitor_,
+                  OnHeaders(stream_id, kHasPriority,
+                            Spdy3PriorityToHttp2Weight(priority),
+                            /*parent_stream_id=*/0,
+                            /*exclusive=*/false, fin, kFrameComplete));
+    } else {
+      EXPECT_CALL(visitor_,
+                  OnHeaders(stream_id, !kHasPriority,
+                            /*priority=*/0,
+                            /*parent_stream_id=*/0,
+                            /*exclusive=*/false, fin, kFrameComplete));
+    }
+    headers_handler_ = QuicMakeUnique<TestHeadersHandler>();
+    EXPECT_CALL(visitor_, OnHeaderFrameStart(stream_id))
+        .WillOnce(Return(headers_handler_.get()));
+    EXPECT_CALL(visitor_, OnHeaderFrameEnd(stream_id)).Times(1);
+    if (fin) {
+      EXPECT_CALL(visitor_, OnStreamEnd(stream_id));
+    }
+    deframer_->ProcessInput(saved_data_.data(), saved_data_.length());
+    EXPECT_FALSE(deframer_->HasError())
+        << http2::Http2DecoderAdapter::SpdyFramerErrorToString(
+               deframer_->spdy_framer_error());
+
+    CheckHeaders();
+    saved_data_.clear();
+  }
+
+  void CheckHeaders() {
+    EXPECT_EQ(headers_, headers_handler_->decoded_block());
+    headers_handler_.reset();
+  }
+
+  Perspective perspective() const { return GetParam().perspective; }
+
+  QuicTransportVersion transport_version() const {
+    return GetParam().version.transport_version;
+  }
+
+  ParsedQuicVersionVector GetVersion() {
+    ParsedQuicVersionVector versions;
+    versions.push_back(GetParam().version);
+    return versions;
+  }
+
+  void TearDownLocalConnectionState() {
+    QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+  }
+
+  QuicStreamId NextPromisedStreamId() {
+    return next_promised_stream_id_ += next_stream_id_;
+  }
+
+  static const bool kFrameComplete = true;
+  static const bool kHasPriority = true;
+
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnection>* connection_;
+  StrictMock<MockQuicSpdySession> session_;
+  QuicHeadersStream* headers_stream_;
+  SpdyHeaderBlock headers_;
+  std::unique_ptr<TestHeadersHandler> headers_handler_;
+  QuicString body_;
+  QuicString saved_data_;
+  QuicString saved_header_data_;
+  QuicString saved_payloads_;
+  std::unique_ptr<SpdyFramer> framer_;
+  std::unique_ptr<http2::Http2DecoderAdapter> deframer_;
+  StrictMock<MockVisitor> visitor_;
+  QuicStreamFrame stream_frame_;
+  QuicStreamId next_promised_stream_id_;
+  QuicStreamId client_id_1_;
+  QuicStreamId client_id_2_;
+  QuicStreamId client_id_3_;
+  QuicStreamId next_stream_id_;
+};
+
+// Run all tests with each version and perspective (client or server).
+INSTANTIATE_TEST_SUITE_P(Tests, QuicHeadersStreamTest,
+                         ::testing::ValuesIn(GetTestParams()));
+
+TEST_P(QuicHeadersStreamTest, StreamId) {
+  EXPECT_EQ(QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+            headers_stream_->id());
+}
+
+TEST_P(QuicHeadersStreamTest, WriteHeaders) {
+  for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+       stream_id += next_stream_id_) {
+    for (bool fin : {false, true}) {
+      if (perspective() == Perspective::IS_SERVER) {
+        WriteAndExpectResponseHeaders(stream_id, fin);
+      } else {
+        for (SpdyPriority priority = 0; priority < 7; ++priority) {
+          // TODO(rch): implement priorities correctly.
+          WriteAndExpectRequestHeaders(stream_id, fin, 0);
+        }
+      }
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, WritePushPromises) {
+  for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+       stream_id += next_stream_id_) {
+    QuicStreamId promised_stream_id = NextPromisedStreamId();
+    if (perspective() == Perspective::IS_SERVER) {
+      // Write the headers and capture the outgoing data
+      EXPECT_CALL(session_, WritevData(headers_stream_,
+                                       QuicUtils::GetHeadersStreamId(
+                                           connection_->transport_version()),
+                                       _, _, NO_FIN))
+          .WillOnce(WithArgs<2>(Invoke(this, &QuicHeadersStreamTest::SaveIov)));
+      session_.WritePushPromise(stream_id, promised_stream_id,
+                                headers_.Clone());
+
+      // Parse the outgoing data and check that it matches was was written.
+      EXPECT_CALL(visitor_,
+                  OnPushPromise(stream_id, promised_stream_id, kFrameComplete));
+      headers_handler_ = QuicMakeUnique<TestHeadersHandler>();
+      EXPECT_CALL(visitor_, OnHeaderFrameStart(stream_id))
+          .WillOnce(Return(headers_handler_.get()));
+      EXPECT_CALL(visitor_, OnHeaderFrameEnd(stream_id)).Times(1);
+      deframer_->ProcessInput(saved_data_.data(), saved_data_.length());
+      EXPECT_FALSE(deframer_->HasError())
+          << http2::Http2DecoderAdapter::SpdyFramerErrorToString(
+                 deframer_->spdy_framer_error());
+      CheckHeaders();
+      saved_data_.clear();
+    } else {
+      EXPECT_QUIC_BUG(session_.WritePushPromise(stream_id, promised_stream_id,
+                                                headers_.Clone()),
+                      "Client shouldn't send PUSH_PROMISE");
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessRawData) {
+  for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+       stream_id += next_stream_id_) {
+    for (bool fin : {false, true}) {
+      for (SpdyPriority priority = 0; priority < 7; ++priority) {
+        // Replace with "WriteHeadersAndSaveData"
+        SpdySerializedFrame frame;
+        if (perspective() == Perspective::IS_SERVER) {
+          SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+          headers_frame.set_fin(fin);
+          headers_frame.set_has_priority(true);
+          headers_frame.set_weight(Spdy3PriorityToHttp2Weight(0));
+          frame = framer_->SerializeFrame(headers_frame);
+          EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0));
+        } else {
+          SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+          headers_frame.set_fin(fin);
+          frame = framer_->SerializeFrame(headers_frame);
+        }
+        EXPECT_CALL(session_,
+                    OnStreamHeaderList(stream_id, fin, frame.size(), _))
+            .WillOnce(Invoke(this, &QuicHeadersStreamTest::SaveHeaderList));
+        stream_frame_.data_buffer = frame.data();
+        stream_frame_.data_length = frame.size();
+        headers_stream_->OnStreamFrame(stream_frame_);
+        stream_frame_.offset += frame.size();
+        CheckHeaders();
+      }
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessPushPromise) {
+  if (perspective() == Perspective::IS_SERVER) {
+    return;
+  }
+  for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+       stream_id += next_stream_id_) {
+    QuicStreamId promised_stream_id = NextPromisedStreamId();
+    SpdyPushPromiseIR push_promise(stream_id, promised_stream_id,
+                                   headers_.Clone());
+    SpdySerializedFrame frame(framer_->SerializeFrame(push_promise));
+    if (perspective() == Perspective::IS_SERVER) {
+      EXPECT_CALL(*connection_,
+                  CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                  "PUSH_PROMISE not supported.", _))
+          .WillRepeatedly(InvokeWithoutArgs(
+              this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+    } else {
+      EXPECT_CALL(session_, OnPromiseHeaderList(stream_id, promised_stream_id,
+                                                frame.size(), _))
+          .WillOnce(
+              Invoke(this, &QuicHeadersStreamTest::SavePromiseHeaderList));
+    }
+    stream_frame_.data_buffer = frame.data();
+    stream_frame_.data_length = frame.size();
+    headers_stream_->OnStreamFrame(stream_frame_);
+    if (perspective() == Perspective::IS_CLIENT) {
+      stream_frame_.offset += frame.size();
+      CheckHeaders();
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessPriorityFrame) {
+  QuicStreamId parent_stream_id = 0;
+  for (SpdyPriority priority = 0; priority < 7; ++priority) {
+    for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+         stream_id += next_stream_id_) {
+      int weight = Spdy3PriorityToHttp2Weight(priority);
+      SpdyPriorityIR priority_frame(stream_id, parent_stream_id, weight, true);
+      SpdySerializedFrame frame(framer_->SerializeFrame(priority_frame));
+      parent_stream_id = stream_id;
+      if (transport_version() <= QUIC_VERSION_39) {
+        EXPECT_CALL(*connection_,
+                    CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                    "SPDY PRIORITY frame received.", _))
+            .WillRepeatedly(InvokeWithoutArgs(
+                this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+      } else if (perspective() == Perspective::IS_CLIENT) {
+        EXPECT_CALL(*connection_,
+                    CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                    "Server must not send PRIORITY frames.", _))
+            .WillRepeatedly(InvokeWithoutArgs(
+                this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+      } else {
+        EXPECT_CALL(session_, OnPriorityFrame(stream_id, priority)).Times(1);
+      }
+      stream_frame_.data_buffer = frame.data();
+      stream_frame_.data_length = frame.size();
+      headers_stream_->OnStreamFrame(stream_frame_);
+      stream_frame_.offset += frame.size();
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessPushPromiseDisabledSetting) {
+  session_.OnConfigNegotiated();
+  SpdySettingsIR data;
+  // Respect supported settings frames SETTINGS_ENABLE_PUSH.
+  data.AddSetting(SETTINGS_ENABLE_PUSH, 0);
+  SpdySerializedFrame frame(framer_->SerializeFrame(data));
+  stream_frame_.data_buffer = frame.data();
+  stream_frame_.data_length = frame.size();
+  if (perspective() == Perspective::IS_CLIENT) {
+    EXPECT_CALL(
+        *connection_,
+        CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                        "Unsupported field of HTTP/2 SETTINGS frame: 2", _));
+  }
+  headers_stream_->OnStreamFrame(stream_frame_);
+  EXPECT_EQ(session_.server_push_enabled(),
+            perspective() == Perspective::IS_CLIENT);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessLargeRawData) {
+  QuicSpdySessionPeer::SetMaxUncompressedHeaderBytes(&session_, 256 * 1024);
+  // We want to create a frame that is more than the SPDY Framer's max control
+  // frame size, which is 16K, but less than the HPACK decoders max decode
+  // buffer size, which is 32K.
+  headers_["key0"] = QuicString(1 << 13, '.');
+  headers_["key1"] = QuicString(1 << 13, '.');
+  headers_["key2"] = QuicString(1 << 13, '.');
+  for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+       stream_id += next_stream_id_) {
+    for (bool fin : {false, true}) {
+      for (SpdyPriority priority = 0; priority < 7; ++priority) {
+        // Replace with "WriteHeadersAndSaveData"
+        SpdySerializedFrame frame;
+        if (perspective() == Perspective::IS_SERVER) {
+          SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+          headers_frame.set_fin(fin);
+          headers_frame.set_has_priority(true);
+          headers_frame.set_weight(Spdy3PriorityToHttp2Weight(0));
+          frame = framer_->SerializeFrame(headers_frame);
+          EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0));
+        } else {
+          SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+          headers_frame.set_fin(fin);
+          frame = framer_->SerializeFrame(headers_frame);
+        }
+        EXPECT_CALL(session_,
+                    OnStreamHeaderList(stream_id, fin, frame.size(), _))
+            .WillOnce(Invoke(this, &QuicHeadersStreamTest::SaveHeaderList));
+        stream_frame_.data_buffer = frame.data();
+        stream_frame_.data_length = frame.size();
+        headers_stream_->OnStreamFrame(stream_frame_);
+        stream_frame_.offset += frame.size();
+        CheckHeaders();
+      }
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessBadData) {
+  const char kBadData[] = "blah blah blah";
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+      .Times(::testing::AnyNumber());
+  stream_frame_.data_buffer = kBadData;
+  stream_frame_.data_length = strlen(kBadData);
+  headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyDataFrame) {
+  SpdyDataIR data(/* stream_id = */ 2, "ping");
+  SpdySerializedFrame frame(framer_->SerializeFrame(data));
+
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                            "SPDY DATA frame received.", _))
+      .WillOnce(InvokeWithoutArgs(
+          this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+  stream_frame_.data_buffer = frame.data();
+  stream_frame_.data_length = frame.size();
+  headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyRstStreamFrame) {
+  SpdyRstStreamIR data(/* stream_id = */ 2, ERROR_CODE_PROTOCOL_ERROR);
+  SpdySerializedFrame frame(framer_->SerializeFrame(data));
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                              "SPDY RST_STREAM frame received.", _))
+      .WillOnce(InvokeWithoutArgs(
+          this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+  stream_frame_.data_buffer = frame.data();
+  stream_frame_.data_length = frame.size();
+  headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, RespectHttp2SettingsFrameSupportedFields) {
+  const uint32_t kTestHeaderTableSize = 1000;
+  SpdySettingsIR data;
+  // Respect supported settings frames SETTINGS_HEADER_TABLE_SIZE,
+  // SETTINGS_MAX_HEADER_LIST_SIZE.
+  data.AddSetting(SETTINGS_HEADER_TABLE_SIZE, kTestHeaderTableSize);
+  data.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE, 2000);
+  SpdySerializedFrame frame(framer_->SerializeFrame(data));
+  stream_frame_.data_buffer = frame.data();
+  stream_frame_.data_length = frame.size();
+  headers_stream_->OnStreamFrame(stream_frame_);
+  EXPECT_EQ(kTestHeaderTableSize, QuicSpdySessionPeer::GetSpdyFramer(&session_)
+                                      .header_encoder_table_size());
+}
+
+TEST_P(QuicHeadersStreamTest, RespectHttp2SettingsFrameUnsupportedFields) {
+  SpdySettingsIR data;
+  // Does not support SETTINGS_MAX_CONCURRENT_STREAMS,
+  // SETTINGS_INITIAL_WINDOW_SIZE, SETTINGS_ENABLE_PUSH and
+  // SETTINGS_MAX_FRAME_SIZE.
+  data.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 100);
+  data.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 100);
+  data.AddSetting(SETTINGS_ENABLE_PUSH, 1);
+  data.AddSetting(SETTINGS_MAX_FRAME_SIZE, 1250);
+  SpdySerializedFrame frame(framer_->SerializeFrame(data));
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                      QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ",
+                                 SETTINGS_MAX_CONCURRENT_STREAMS),
+                      _));
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                      QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ",
+                                 SETTINGS_INITIAL_WINDOW_SIZE),
+                      _));
+  if (session_.perspective() == Perspective::IS_CLIENT) {
+    EXPECT_CALL(*connection_,
+                CloseConnection(
+                    QUIC_INVALID_HEADERS_STREAM_DATA,
+                    QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ",
+                               SETTINGS_ENABLE_PUSH),
+                    _));
+  }
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                      QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ",
+                                 SETTINGS_MAX_FRAME_SIZE),
+                      _));
+  stream_frame_.data_buffer = frame.data();
+  stream_frame_.data_length = frame.size();
+  headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyPingFrame) {
+  SpdyPingIR data(1);
+  SpdySerializedFrame frame(framer_->SerializeFrame(data));
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                            "SPDY PING frame received.", _))
+      .WillOnce(InvokeWithoutArgs(
+          this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+  stream_frame_.data_buffer = frame.data();
+  stream_frame_.data_length = frame.size();
+  headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyGoAwayFrame) {
+  SpdyGoAwayIR data(/* last_good_stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR,
+                    "go away");
+  SpdySerializedFrame frame(framer_->SerializeFrame(data));
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                            "SPDY GOAWAY frame received.", _))
+      .WillOnce(InvokeWithoutArgs(
+          this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+  stream_frame_.data_buffer = frame.data();
+  stream_frame_.data_length = frame.size();
+  headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyWindowUpdateFrame) {
+  SpdyWindowUpdateIR data(/* stream_id = */ 1, /* delta = */ 1);
+  SpdySerializedFrame frame(framer_->SerializeFrame(data));
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
+                              "SPDY WINDOW_UPDATE frame received.", _))
+      .WillOnce(InvokeWithoutArgs(
+          this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
+  stream_frame_.data_buffer = frame.data();
+  stream_frame_.data_length = frame.size();
+  headers_stream_->OnStreamFrame(stream_frame_);
+}
+
+TEST_P(QuicHeadersStreamTest, NoConnectionLevelFlowControl) {
+  EXPECT_FALSE(QuicStreamPeer::StreamContributesToConnectionFlowControl(
+      headers_stream_));
+}
+
+TEST_P(QuicHeadersStreamTest, HpackDecoderDebugVisitor) {
+  auto hpack_decoder_visitor =
+      QuicMakeUnique<StrictMock<MockQuicHpackDebugVisitor>>();
+  {
+    InSequence seq;
+    // Number of indexed representations generated in headers below.
+    for (int i = 1; i < 28; i++) {
+      EXPECT_CALL(*hpack_decoder_visitor,
+                  OnUseEntry(QuicTime::Delta::FromMilliseconds(i)))
+          .Times(4);
+    }
+  }
+  QuicSpdySessionPeer::SetHpackDecoderDebugVisitor(
+      &session_, std::move(hpack_decoder_visitor));
+
+  // Create some headers we expect to generate entries in HPACK's
+  // dynamic table, in addition to content-length.
+  headers_["key0"] = QuicString(1 << 1, '.');
+  headers_["key1"] = QuicString(1 << 2, '.');
+  headers_["key2"] = QuicString(1 << 3, '.');
+  for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+       stream_id += next_stream_id_) {
+    for (bool fin : {false, true}) {
+      for (SpdyPriority priority = 0; priority < 7; ++priority) {
+        // Replace with "WriteHeadersAndSaveData"
+        SpdySerializedFrame frame;
+        if (perspective() == Perspective::IS_SERVER) {
+          SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+          headers_frame.set_fin(fin);
+          headers_frame.set_has_priority(true);
+          headers_frame.set_weight(Spdy3PriorityToHttp2Weight(0));
+          frame = framer_->SerializeFrame(headers_frame);
+          EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0));
+        } else {
+          SpdyHeadersIR headers_frame(stream_id, headers_.Clone());
+          headers_frame.set_fin(fin);
+          frame = framer_->SerializeFrame(headers_frame);
+        }
+        EXPECT_CALL(session_,
+                    OnStreamHeaderList(stream_id, fin, frame.size(), _))
+            .WillOnce(Invoke(this, &QuicHeadersStreamTest::SaveHeaderList));
+        stream_frame_.data_buffer = frame.data();
+        stream_frame_.data_length = frame.size();
+        connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+        headers_stream_->OnStreamFrame(stream_frame_);
+        stream_frame_.offset += frame.size();
+        CheckHeaders();
+      }
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, HpackEncoderDebugVisitor) {
+  auto hpack_encoder_visitor =
+      QuicMakeUnique<StrictMock<MockQuicHpackDebugVisitor>>();
+  if (perspective() == Perspective::IS_SERVER) {
+    InSequence seq;
+    for (int i = 1; i < 4; i++) {
+      EXPECT_CALL(*hpack_encoder_visitor,
+                  OnUseEntry(QuicTime::Delta::FromMilliseconds(i)));
+    }
+  } else {
+    InSequence seq;
+    for (int i = 1; i < 28; i++) {
+      EXPECT_CALL(*hpack_encoder_visitor,
+                  OnUseEntry(QuicTime::Delta::FromMilliseconds(i)));
+    }
+  }
+  QuicSpdySessionPeer::SetHpackEncoderDebugVisitor(
+      &session_, std::move(hpack_encoder_visitor));
+
+  for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
+       stream_id += next_stream_id_) {
+    for (bool fin : {false, true}) {
+      if (perspective() == Perspective::IS_SERVER) {
+        WriteAndExpectResponseHeaders(stream_id, fin);
+        connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+      } else {
+        for (SpdyPriority priority = 0; priority < 7; ++priority) {
+          // TODO(rch): implement priorities correctly.
+          WriteAndExpectRequestHeaders(stream_id, fin, 0);
+          connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+        }
+      }
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, AckSentData) {
+  EXPECT_CALL(session_, WritevData(headers_stream_,
+                                   QuicUtils::GetHeadersStreamId(
+                                       connection_->transport_version()),
+                                   _, _, NO_FIN))
+      .WillRepeatedly(Invoke(MockQuicSession::ConsumeData));
+  InSequence s;
+  QuicReferenceCountedPointer<MockAckListener> ack_listener1(
+      new MockAckListener());
+  QuicReferenceCountedPointer<MockAckListener> ack_listener2(
+      new MockAckListener());
+  QuicReferenceCountedPointer<MockAckListener> ack_listener3(
+      new MockAckListener());
+
+  // Packet 1.
+  headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+  headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+  headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+
+  // Packet 2.
+  headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+  headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+
+  // Packet 3.
+  headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+
+  // Packet 2 gets retransmitted.
+  EXPECT_CALL(*ack_listener3, OnPacketRetransmitted(7)).Times(1);
+  EXPECT_CALL(*ack_listener2, OnPacketRetransmitted(7)).Times(1);
+  headers_stream_->OnStreamFrameRetransmitted(21, 7, false);
+  headers_stream_->OnStreamFrameRetransmitted(28, 7, false);
+
+  // Packets are acked in order: 2, 3, 1.
+  QuicByteCount newly_acked_length = 0;
+  EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      21, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(7u, newly_acked_length);
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      28, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(7u, newly_acked_length);
+
+  EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      35, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(7u, newly_acked_length);
+
+  EXPECT_CALL(*ack_listener1, OnPacketAcked(7, _));
+  EXPECT_CALL(*ack_listener1, OnPacketAcked(7, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      0, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(7u, newly_acked_length);
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      7, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(7u, newly_acked_length);
+  // Unsent data is acked.
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      14, 10, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(7u, newly_acked_length);
+}
+
+TEST_P(QuicHeadersStreamTest, FrameContainsMultipleHeaders) {
+  // In this test, a stream frame can contain multiple headers.
+  EXPECT_CALL(session_, WritevData(headers_stream_,
+                                   QuicUtils::GetHeadersStreamId(
+                                       connection_->transport_version()),
+                                   _, _, NO_FIN))
+      .WillRepeatedly(Invoke(MockQuicSession::ConsumeData));
+  InSequence s;
+  QuicReferenceCountedPointer<MockAckListener> ack_listener1(
+      new MockAckListener());
+  QuicReferenceCountedPointer<MockAckListener> ack_listener2(
+      new MockAckListener());
+  QuicReferenceCountedPointer<MockAckListener> ack_listener3(
+      new MockAckListener());
+
+  headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+  headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+  headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+  headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+  headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+  headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+
+  // Frame 1 is retransmitted.
+  EXPECT_CALL(*ack_listener1, OnPacketRetransmitted(14));
+  EXPECT_CALL(*ack_listener2, OnPacketRetransmitted(3));
+  headers_stream_->OnStreamFrameRetransmitted(0, 17, false);
+
+  // Frames are acked in order: 2, 3, 1.
+  QuicByteCount newly_acked_length = 0;
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(4, _));
+  EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(2, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      17, 13, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(13u, newly_acked_length);
+
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(5, _));
+  EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      30, 12, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(12u, newly_acked_length);
+
+  EXPECT_CALL(*ack_listener1, OnPacketAcked(14, _));
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(3, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      0, 17, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(17u, newly_acked_length);
+}
+
+TEST_P(QuicHeadersStreamTest, HeadersGetAckedMultipleTimes) {
+  EXPECT_CALL(session_, WritevData(headers_stream_,
+                                   QuicUtils::GetHeadersStreamId(
+                                       connection_->transport_version()),
+                                   _, _, NO_FIN))
+      .WillRepeatedly(Invoke(MockQuicSession::ConsumeData));
+  InSequence s;
+  QuicReferenceCountedPointer<MockAckListener> ack_listener1(
+      new MockAckListener());
+  QuicReferenceCountedPointer<MockAckListener> ack_listener2(
+      new MockAckListener());
+  QuicReferenceCountedPointer<MockAckListener> ack_listener3(
+      new MockAckListener());
+
+  // Send [0, 42).
+  headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+  headers_stream_->WriteOrBufferData("Header5", false, ack_listener1);
+  headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+  headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+  headers_stream_->WriteOrBufferData("Header7", false, ack_listener2);
+  headers_stream_->WriteOrBufferData("Header9", false, ack_listener3);
+
+  // Ack [15, 20), [5, 25), [10, 17), [0, 12) and [22, 42).
+  QuicByteCount newly_acked_length = 0;
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(5, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      15, 5, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(5u, newly_acked_length);
+
+  EXPECT_CALL(*ack_listener1, OnPacketAcked(9, _));
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(1, _));
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(1, _));
+  EXPECT_CALL(*ack_listener3, OnPacketAcked(4, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      5, 20, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(15u, newly_acked_length);
+
+  // Duplicate ack.
+  EXPECT_FALSE(headers_stream_->OnStreamFrameAcked(
+      10, 7, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(0u, newly_acked_length);
+
+  EXPECT_CALL(*ack_listener1, OnPacketAcked(5, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      0, 12, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(5u, newly_acked_length);
+
+  EXPECT_CALL(*ack_listener3, OnPacketAcked(3, _));
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _));
+  EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _));
+  EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(
+      22, 20, false, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(17u, newly_acked_length);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_server_session_base.cc b/quic/core/http/quic_server_session_base.cc
new file mode 100644
index 0000000..f240ece
--- /dev/null
+++ b/quic/core/http/quic_server_session_base.cc
@@ -0,0 +1,277 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_server_session_base.h"
+
+#include "net/third_party/quiche/src/quic/core/proto/cached_network_parameters.pb.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+QuicServerSessionBase::QuicServerSessionBase(
+    const QuicConfig& config,
+    const ParsedQuicVersionVector& supported_versions,
+    QuicConnection* connection,
+    Visitor* visitor,
+    QuicCryptoServerStream::Helper* helper,
+    const QuicCryptoServerConfig* crypto_config,
+    QuicCompressedCertsCache* compressed_certs_cache)
+    : QuicSpdySession(connection, visitor, config, supported_versions),
+      crypto_config_(crypto_config),
+      compressed_certs_cache_(compressed_certs_cache),
+      helper_(helper),
+      bandwidth_resumption_enabled_(false),
+      bandwidth_estimate_sent_to_client_(QuicBandwidth::Zero()),
+      last_scup_time_(QuicTime::Zero()) {}
+
+QuicServerSessionBase::~QuicServerSessionBase() {}
+
+void QuicServerSessionBase::Initialize() {
+  crypto_stream_.reset(
+      CreateQuicCryptoServerStream(crypto_config_, compressed_certs_cache_));
+  QuicSpdySession::Initialize();
+}
+
+void QuicServerSessionBase::OnConfigNegotiated() {
+  QuicSpdySession::OnConfigNegotiated();
+
+  if (!config()->HasReceivedConnectionOptions()) {
+    return;
+  }
+
+  // Enable bandwidth resumption if peer sent correct connection options.
+  const bool last_bandwidth_resumption =
+      ContainsQuicTag(config()->ReceivedConnectionOptions(), kBWRE);
+  const bool max_bandwidth_resumption =
+      ContainsQuicTag(config()->ReceivedConnectionOptions(), kBWMX);
+  bandwidth_resumption_enabled_ =
+      last_bandwidth_resumption || max_bandwidth_resumption;
+
+  // If the client has provided a bandwidth estimate from the same serving
+  // region as this server, then decide whether to use the data for bandwidth
+  // resumption.
+  const CachedNetworkParameters* cached_network_params =
+      crypto_stream_->PreviousCachedNetworkParams();
+  if (cached_network_params != nullptr &&
+      cached_network_params->serving_region() == serving_region_) {
+    // Log the received connection parameters, regardless of how they
+    // get used for bandwidth resumption.
+    connection()->OnReceiveConnectionState(*cached_network_params);
+
+    if (bandwidth_resumption_enabled_) {
+      // Only do bandwidth resumption if estimate is recent enough.
+      const uint64_t seconds_since_estimate =
+          connection()->clock()->WallNow().ToUNIXSeconds() -
+          cached_network_params->timestamp();
+      if (seconds_since_estimate <= kNumSecondsPerHour) {
+        connection()->ResumeConnectionState(*cached_network_params,
+                                            max_bandwidth_resumption);
+      }
+    }
+  }
+}
+
+void QuicServerSessionBase::OnConnectionClosed(QuicErrorCode error,
+                                               const QuicString& error_details,
+                                               ConnectionCloseSource source) {
+  QuicSession::OnConnectionClosed(error, error_details, source);
+  // In the unlikely event we get a connection close while doing an asynchronous
+  // crypto event, make sure we cancel the callback.
+  if (crypto_stream_ != nullptr) {
+    crypto_stream_->CancelOutstandingCallbacks();
+  }
+}
+
+void QuicServerSessionBase::OnCongestionWindowChange(QuicTime now) {
+  if (!bandwidth_resumption_enabled_) {
+    return;
+  }
+  // Only send updates when the application has no data to write.
+  if (HasDataToWrite()) {
+    return;
+  }
+
+  // If not enough time has passed since the last time we sent an update to the
+  // client, or not enough packets have been sent, then return early.
+  const QuicSentPacketManager& sent_packet_manager =
+      connection()->sent_packet_manager();
+  int64_t srtt_ms =
+      sent_packet_manager.GetRttStats()->smoothed_rtt().ToMilliseconds();
+  int64_t now_ms = (now - last_scup_time_).ToMilliseconds();
+  int64_t packets_since_last_scup = 0;
+  const QuicPacketNumber largest_sent_packet =
+      connection()->sent_packet_manager().GetLargestSentPacket();
+  if (largest_sent_packet.IsInitialized()) {
+    packets_since_last_scup =
+        last_scup_packet_number_.IsInitialized()
+            ? largest_sent_packet - last_scup_packet_number_
+            : largest_sent_packet.ToUint64();
+  }
+  if (now_ms < (kMinIntervalBetweenServerConfigUpdatesRTTs * srtt_ms) ||
+      now_ms < kMinIntervalBetweenServerConfigUpdatesMs ||
+      packets_since_last_scup < kMinPacketsBetweenServerConfigUpdates) {
+    return;
+  }
+
+  // If the bandwidth recorder does not have a valid estimate, return early.
+  const QuicSustainedBandwidthRecorder* bandwidth_recorder =
+      sent_packet_manager.SustainedBandwidthRecorder();
+  if (bandwidth_recorder == nullptr || !bandwidth_recorder->HasEstimate()) {
+    return;
+  }
+
+  // The bandwidth recorder has recorded at least one sustained bandwidth
+  // estimate. Check that it's substantially different from the last one that
+  // we sent to the client, and if so, send the new one.
+  QuicBandwidth new_bandwidth_estimate =
+      bandwidth_recorder->BandwidthEstimate();
+
+  int64_t bandwidth_delta =
+      std::abs(new_bandwidth_estimate.ToBitsPerSecond() -
+               bandwidth_estimate_sent_to_client_.ToBitsPerSecond());
+
+  // Define "substantial" difference as a 50% increase or decrease from the
+  // last estimate.
+  bool substantial_difference =
+      bandwidth_delta >
+      0.5 * bandwidth_estimate_sent_to_client_.ToBitsPerSecond();
+  if (!substantial_difference) {
+    return;
+  }
+
+  bandwidth_estimate_sent_to_client_ = new_bandwidth_estimate;
+  QUIC_DVLOG(1) << "Server: sending new bandwidth estimate (KBytes/s): "
+                << bandwidth_estimate_sent_to_client_.ToKBytesPerSecond();
+
+  // Include max bandwidth in the update.
+  QuicBandwidth max_bandwidth_estimate =
+      bandwidth_recorder->MaxBandwidthEstimate();
+  int32_t max_bandwidth_timestamp = bandwidth_recorder->MaxBandwidthTimestamp();
+
+  // Fill the proto before passing it to the crypto stream to send.
+  const int32_t bw_estimate_bytes_per_second =
+      BandwidthToCachedParameterBytesPerSecond(
+          bandwidth_estimate_sent_to_client_);
+  const int32_t max_bw_estimate_bytes_per_second =
+      BandwidthToCachedParameterBytesPerSecond(max_bandwidth_estimate);
+  QUIC_BUG_IF(max_bw_estimate_bytes_per_second < 0)
+      << max_bw_estimate_bytes_per_second;
+  QUIC_BUG_IF(bw_estimate_bytes_per_second < 0) << bw_estimate_bytes_per_second;
+
+  CachedNetworkParameters cached_network_params;
+  cached_network_params.set_bandwidth_estimate_bytes_per_second(
+      bw_estimate_bytes_per_second);
+  cached_network_params.set_max_bandwidth_estimate_bytes_per_second(
+      max_bw_estimate_bytes_per_second);
+  cached_network_params.set_max_bandwidth_timestamp_seconds(
+      max_bandwidth_timestamp);
+  cached_network_params.set_min_rtt_ms(
+      sent_packet_manager.GetRttStats()->min_rtt().ToMilliseconds());
+  cached_network_params.set_previous_connection_state(
+      bandwidth_recorder->EstimateRecordedDuringSlowStart()
+          ? CachedNetworkParameters::SLOW_START
+          : CachedNetworkParameters::CONGESTION_AVOIDANCE);
+  cached_network_params.set_timestamp(
+      connection()->clock()->WallNow().ToUNIXSeconds());
+  if (!serving_region_.empty()) {
+    cached_network_params.set_serving_region(serving_region_);
+  }
+
+  crypto_stream_->SendServerConfigUpdate(&cached_network_params);
+
+  connection()->OnSendConnectionState(cached_network_params);
+
+  last_scup_time_ = now;
+  last_scup_packet_number_ =
+      connection()->sent_packet_manager().GetLargestSentPacket();
+}
+
+bool QuicServerSessionBase::ShouldCreateIncomingStream(QuicStreamId id) {
+  if (!connection()->connected()) {
+    QUIC_BUG << "ShouldCreateIncomingStream called when disconnected";
+    return false;
+  }
+
+  if (QuicUtils::IsServerInitiatedStreamId(connection()->transport_version(),
+                                           id)) {
+    QUIC_DLOG(INFO) << "Invalid incoming even stream_id:" << id;
+    connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID, "Client created even numbered stream",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
+  return true;
+}
+
+bool QuicServerSessionBase::ShouldCreateOutgoingBidirectionalStream() {
+  if (!connection()->connected()) {
+    QUIC_BUG
+        << "ShouldCreateOutgoingBidirectionalStream called when disconnected";
+    return false;
+  }
+  if (!crypto_stream_->encryption_established()) {
+    QUIC_BUG << "Encryption not established so no outgoing stream created.";
+    return false;
+  }
+
+  if (!GetQuicReloadableFlag(quic_use_common_stream_check) &&
+      connection()->transport_version() != QUIC_VERSION_99) {
+    if (GetNumOpenOutgoingStreams() >=
+        stream_id_manager().max_open_outgoing_streams()) {
+      VLOG(1) << "No more streams should be created. "
+              << "Already " << GetNumOpenOutgoingStreams() << " open.";
+      return false;
+    }
+  }
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_common_stream_check, 2, 2);
+  return CanOpenNextOutgoingBidirectionalStream();
+}
+
+bool QuicServerSessionBase::ShouldCreateOutgoingUnidirectionalStream() {
+  if (!connection()->connected()) {
+    QUIC_BUG
+        << "ShouldCreateOutgoingUnidirectionalStream called when disconnected";
+    return false;
+  }
+  if (!crypto_stream_->encryption_established()) {
+    QUIC_BUG << "Encryption not established so no outgoing stream created.";
+    return false;
+  }
+
+  if (!GetQuicReloadableFlag(quic_use_common_stream_check) &&
+      connection()->transport_version() != QUIC_VERSION_99) {
+    if (GetNumOpenOutgoingStreams() >=
+        stream_id_manager().max_open_outgoing_streams()) {
+      VLOG(1) << "No more streams should be created. "
+              << "Already " << GetNumOpenOutgoingStreams() << " open.";
+      return false;
+    }
+  }
+
+  return CanOpenNextOutgoingUnidirectionalStream();
+}
+
+QuicCryptoServerStreamBase* QuicServerSessionBase::GetMutableCryptoStream() {
+  return crypto_stream_.get();
+}
+
+const QuicCryptoServerStreamBase* QuicServerSessionBase::GetCryptoStream()
+    const {
+  return crypto_stream_.get();
+}
+
+int32_t QuicServerSessionBase::BandwidthToCachedParameterBytesPerSecond(
+    const QuicBandwidth& bandwidth) {
+  return static_cast<int32_t>(std::min<int64_t>(
+      bandwidth.ToBytesPerSecond(), std::numeric_limits<uint32_t>::max()));
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_server_session_base.h b/quic/core/http/quic_server_session_base.h
new file mode 100644
index 0000000..55a8d60
--- /dev/null
+++ b/quic/core/http/quic_server_session_base.h
@@ -0,0 +1,140 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// A server specific QuicSession subclass.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SERVER_SESSION_BASE_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SERVER_SESSION_BASE_H_
+
+#include <cstdint>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_compressed_certs_cache.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+class QuicConfig;
+class QuicConnection;
+class QuicCryptoServerConfig;
+
+namespace test {
+class QuicServerSessionBasePeer;
+class QuicSimpleServerSessionPeer;
+}  // namespace test
+
+class QUIC_EXPORT_PRIVATE QuicServerSessionBase : public QuicSpdySession {
+ public:
+  // Does not take ownership of |connection|. |crypto_config| must outlive the
+  // session. |helper| must outlive any created crypto streams.
+  QuicServerSessionBase(const QuicConfig& config,
+                        const ParsedQuicVersionVector& supported_versions,
+                        QuicConnection* connection,
+                        QuicSession::Visitor* visitor,
+                        QuicCryptoServerStream::Helper* helper,
+                        const QuicCryptoServerConfig* crypto_config,
+                        QuicCompressedCertsCache* compressed_certs_cache);
+  QuicServerSessionBase(const QuicServerSessionBase&) = delete;
+  QuicServerSessionBase& operator=(const QuicServerSessionBase&) = delete;
+
+  // Override the base class to cancel any ongoing asychronous crypto.
+  void OnConnectionClosed(QuicErrorCode error,
+                          const QuicString& error_details,
+                          ConnectionCloseSource source) override;
+
+  // Sends a server config update to the client, containing new bandwidth
+  // estimate.
+  void OnCongestionWindowChange(QuicTime now) override;
+
+  ~QuicServerSessionBase() override;
+
+  void Initialize() override;
+
+  const QuicCryptoServerStreamBase* crypto_stream() const {
+    return crypto_stream_.get();
+  }
+
+  // Override base class to process bandwidth related config received from
+  // client.
+  void OnConfigNegotiated() override;
+
+  void set_serving_region(const QuicString& serving_region) {
+    serving_region_ = serving_region;
+  }
+
+ protected:
+  // QuicSession methods(override them with return type of QuicSpdyStream*):
+  QuicCryptoServerStreamBase* GetMutableCryptoStream() override;
+
+  const QuicCryptoServerStreamBase* GetCryptoStream() const override;
+
+  // If an outgoing stream can be created, return true.
+  // Return false when connection is closed or forward secure encryption hasn't
+  // established yet or number of server initiated streams already reaches the
+  // upper limit.
+  bool ShouldCreateOutgoingBidirectionalStream() override;
+  bool ShouldCreateOutgoingUnidirectionalStream() override;
+
+  // If we should create an incoming stream, returns true. Otherwise
+  // does error handling, including communicating the error to the client and
+  // possibly closing the connection, and returns false.
+  bool ShouldCreateIncomingStream(QuicStreamId id) override;
+
+  virtual QuicCryptoServerStreamBase* CreateQuicCryptoServerStream(
+      const QuicCryptoServerConfig* crypto_config,
+      QuicCompressedCertsCache* compressed_certs_cache) = 0;
+
+  const QuicCryptoServerConfig* crypto_config() { return crypto_config_; }
+
+  QuicCryptoServerStream::Helper* stream_helper() { return helper_; }
+
+ private:
+  friend class test::QuicServerSessionBasePeer;
+  friend class test::QuicSimpleServerSessionPeer;
+
+  const QuicCryptoServerConfig* crypto_config_;
+
+  // The cache which contains most recently compressed certs.
+  // Owned by QuicDispatcher.
+  QuicCompressedCertsCache* compressed_certs_cache_;
+
+  std::unique_ptr<QuicCryptoServerStreamBase> crypto_stream_;
+
+  // Pointer to the helper used to create crypto server streams. Must outlive
+  // streams created via CreateQuicCryptoServerStream.
+  QuicCryptoServerStream::Helper* helper_;
+
+  // Whether bandwidth resumption is enabled for this connection.
+  bool bandwidth_resumption_enabled_;
+
+  // The most recent bandwidth estimate sent to the client.
+  QuicBandwidth bandwidth_estimate_sent_to_client_;
+
+  // Text describing server location. Sent to the client as part of the bandwith
+  // estimate in the source-address token. Optional, can be left empty.
+  QuicString serving_region_;
+
+  // Time at which we send the last SCUP to the client.
+  QuicTime last_scup_time_;
+
+  // Number of packets sent to the peer, at the time we last sent a SCUP.
+  QuicPacketNumber last_scup_packet_number_;
+
+  // Converts QuicBandwidth to an int32 bytes/second that can be
+  // stored in CachedNetworkParameters.  TODO(jokulik): This function
+  // should go away once we fix http://b//27897982
+  int32_t BandwidthToCachedParameterBytesPerSecond(
+      const QuicBandwidth& bandwidth);
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_SERVER_SESSION_BASE_H_
diff --git a/quic/core/http/quic_server_session_base_test.cc b/quic/core/http/quic_server_session_base_test.cc
new file mode 100644
index 0000000..efc2388
--- /dev/null
+++ b/quic/core/http/quic_server_session_base_test.cc
@@ -0,0 +1,740 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_server_session_base.h"
+
+#include <cstdint>
+#include <memory>
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/proto/cached_network_parameters.pb.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.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/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/fake_proof_source.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_quic_session_visitor.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_crypto_server_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_server_session_base_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_id_manager_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h"
+#include "net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h"
+
+using testing::_;
+using testing::StrictMock;
+
+using testing::AtLeast;
+using testing::Return;
+
+namespace quic {
+namespace test {
+namespace {
+
+class TestServerSession : public QuicServerSessionBase {
+ public:
+  TestServerSession(const QuicConfig& config,
+                    QuicConnection* connection,
+                    QuicSession::Visitor* visitor,
+                    QuicCryptoServerStream::Helper* helper,
+                    const QuicCryptoServerConfig* crypto_config,
+                    QuicCompressedCertsCache* compressed_certs_cache,
+                    QuicSimpleServerBackend* quic_simple_server_backend)
+      : QuicServerSessionBase(config,
+                              CurrentSupportedVersions(),
+                              connection,
+                              visitor,
+                              helper,
+                              crypto_config,
+                              compressed_certs_cache),
+        quic_simple_server_backend_(quic_simple_server_backend) {}
+
+  ~TestServerSession() override { delete connection(); }
+
+ protected:
+  QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override {
+    if (!ShouldCreateIncomingStream(id)) {
+      return nullptr;
+    }
+    QuicSpdyStream* stream = new QuicSimpleServerStream(
+        id, this, BIDIRECTIONAL, quic_simple_server_backend_);
+    ActivateStream(QuicWrapUnique(stream));
+    return stream;
+  }
+
+  QuicSpdyStream* CreateIncomingStream(PendingStream pending) override {
+    QuicSpdyStream* stream = new QuicSimpleServerStream(
+        std::move(pending), this, BIDIRECTIONAL, quic_simple_server_backend_);
+    ActivateStream(QuicWrapUnique(stream));
+    return stream;
+  }
+
+  QuicSpdyStream* CreateOutgoingBidirectionalStream() override {
+    DCHECK(false);
+    return nullptr;
+  }
+
+  QuicSpdyStream* CreateOutgoingUnidirectionalStream() override {
+    if (!ShouldCreateOutgoingUnidirectionalStream()) {
+      return nullptr;
+    }
+
+    QuicSpdyStream* stream = new QuicSimpleServerStream(
+        GetNextOutgoingUnidirectionalStreamId(), this, WRITE_UNIDIRECTIONAL,
+        quic_simple_server_backend_);
+    ActivateStream(QuicWrapUnique(stream));
+    return stream;
+  }
+
+  QuicCryptoServerStreamBase* CreateQuicCryptoServerStream(
+      const QuicCryptoServerConfig* crypto_config,
+      QuicCompressedCertsCache* compressed_certs_cache) override {
+    return new QuicCryptoServerStream(
+        crypto_config, compressed_certs_cache,
+        GetQuicReloadableFlag(enable_quic_stateless_reject_support), this,
+        stream_helper());
+  }
+
+ private:
+  QuicSimpleServerBackend*
+      quic_simple_server_backend_;  // Owned by QuicServerSessionBaseTest
+};
+
+const size_t kMaxStreamsForTest = 10;
+
+class QuicServerSessionBaseTest : public QuicTestWithParam<ParsedQuicVersion> {
+ protected:
+  QuicServerSessionBaseTest()
+      : QuicServerSessionBaseTest(crypto_test_utils::ProofSourceForTesting()) {}
+
+  explicit QuicServerSessionBaseTest(std::unique_ptr<ProofSource> proof_source)
+      : crypto_config_(QuicCryptoServerConfig::TESTING,
+                       QuicRandom::GetInstance(),
+                       std::move(proof_source),
+                       KeyExchangeSource::Default(),
+                       TlsServerHandshaker::CreateSslCtx()),
+        compressed_certs_cache_(
+            QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) {
+    config_.SetMaxIncomingDynamicStreamsToSend(kMaxStreamsForTest);
+    QuicConfigPeer::SetReceivedMaxIncomingDynamicStreams(&config_,
+                                                         kMaxStreamsForTest);
+    config_.SetInitialStreamFlowControlWindowToSend(
+        kInitialStreamFlowControlWindowForTest);
+    config_.SetInitialSessionFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+
+    ParsedQuicVersionVector supported_versions = SupportedVersions(GetParam());
+    connection_ = new StrictMock<MockQuicConnection>(
+        &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions);
+    connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    session_ = QuicMakeUnique<TestServerSession>(
+        config_, connection_, &owner_, &stream_helper_, &crypto_config_,
+        &compressed_certs_cache_, &memory_cache_backend_);
+    MockClock clock;
+    handshake_message_.reset(crypto_config_.AddDefaultConfig(
+        QuicRandom::GetInstance(), &clock,
+        QuicCryptoServerConfig::ConfigOptions()));
+    session_->Initialize();
+    QuicSessionPeer::GetMutableCryptoStream(session_.get())
+        ->OnSuccessfulVersionNegotiation(supported_versions.front());
+    visitor_ = QuicConnectionPeer::GetVisitor(connection_);
+  }
+
+  QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+    return GetNthClientInitiatedBidirectionalStreamId(
+        connection_->transport_version(), n);
+  }
+
+  QuicStreamId GetNthServerInitiatedUnidirectionalId(int n) {
+    return quic::test::GetNthServerInitiatedUnidirectionalStreamId(
+        connection_->transport_version(), n);
+  }
+
+  QuicTransportVersion transport_version() const {
+    return connection_->transport_version();
+  }
+
+  // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+  // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes a
+  // one-way close. This method can be used to inject a STOP_SENDING, which
+  // would cause a close in the opposite direction. This allows tests to do the
+  // extra work to get a two-way (full) close where desired. Also sets up
+  // expects needed to ensure that the STOP_SENDING worked as expected.
+  void InjectStopSendingFrame(QuicStreamId stream_id,
+                              QuicRstStreamErrorCode rst_stream_code) {
+    if (transport_version() != QUIC_VERSION_99) {
+      // Only needed for version 99/IETF QUIC. Noop otherwise.
+      return;
+    }
+    QuicStopSendingFrame stop_sending(
+        kInvalidControlFrameId, stream_id,
+        static_cast<QuicApplicationErrorCode>(rst_stream_code));
+    EXPECT_CALL(owner_, OnStopSendingReceived(_)).Times(1);
+    // Expect the RESET_STREAM that is generated in response to receiving a
+    // STOP_SENDING.
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*connection_, OnStreamReset(stream_id, rst_stream_code));
+    session_->OnStopSendingFrame(stop_sending);
+  }
+
+  StrictMock<MockQuicSessionVisitor> owner_;
+  StrictMock<MockQuicCryptoServerStreamHelper> stream_helper_;
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnection>* connection_;
+  QuicConfig config_;
+  QuicCryptoServerConfig crypto_config_;
+  QuicCompressedCertsCache compressed_certs_cache_;
+  QuicMemoryCacheBackend memory_cache_backend_;
+  std::unique_ptr<TestServerSession> session_;
+  std::unique_ptr<CryptoHandshakeMessage> handshake_message_;
+  QuicConnectionVisitorInterface* visitor_;
+};
+
+// Compares CachedNetworkParameters.
+MATCHER_P(EqualsProto, network_params, "") {
+  CachedNetworkParameters reference(network_params);
+  return (arg->bandwidth_estimate_bytes_per_second() ==
+              reference.bandwidth_estimate_bytes_per_second() &&
+          arg->bandwidth_estimate_bytes_per_second() ==
+              reference.bandwidth_estimate_bytes_per_second() &&
+          arg->max_bandwidth_estimate_bytes_per_second() ==
+              reference.max_bandwidth_estimate_bytes_per_second() &&
+          arg->max_bandwidth_timestamp_seconds() ==
+              reference.max_bandwidth_timestamp_seconds() &&
+          arg->min_rtt_ms() == reference.min_rtt_ms() &&
+          arg->previous_connection_state() ==
+              reference.previous_connection_state());
+}
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+                         QuicServerSessionBaseTest,
+                         ::testing::ValuesIn(AllSupportedVersions()));
+TEST_P(QuicServerSessionBaseTest, CloseStreamDueToReset) {
+  // Open a stream, then reset it.
+  // Send two bytes of payload to open it.
+  QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece("HT"));
+  session_->OnStreamFrame(data1);
+  EXPECT_EQ(1u, session_->GetNumOpenIncomingStreams());
+
+  // Send a reset (and expect the peer to send a RST in response).
+  QuicRstStreamFrame rst1(kInvalidControlFrameId,
+                          GetNthClientInitiatedBidirectionalId(0),
+                          QUIC_ERROR_PROCESSING_STREAM, 0);
+  EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+  if (transport_version() != QUIC_VERSION_99) {
+    // For non-version 99, the RESET_STREAM will do the full close.
+    // Set up expects accordingly.
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*connection_,
+                OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+                              QUIC_RST_ACKNOWLEDGEMENT));
+  }
+  visitor_->OnRstStream(rst1);
+
+  // For version-99 will create and receive a stop-sending, completing
+  // the full-close expected by this test.
+  InjectStopSendingFrame(GetNthClientInitiatedBidirectionalId(0),
+                         QUIC_ERROR_PROCESSING_STREAM);
+
+  EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+
+  // Send the same two bytes of payload in a new packet.
+  visitor_->OnStreamFrame(data1);
+
+  // The stream should not be re-opened.
+  EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+  EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicServerSessionBaseTest, NeverOpenStreamDueToReset) {
+  // Send a reset (and expect the peer to send a RST in response).
+  QuicRstStreamFrame rst1(kInvalidControlFrameId,
+                          GetNthClientInitiatedBidirectionalId(0),
+                          QUIC_ERROR_PROCESSING_STREAM, 0);
+  EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+  if (transport_version() != QUIC_VERSION_99) {
+    // For non-version 99, the RESET_STREAM will do the full close.
+    // Set up expects accordingly.
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*connection_,
+                OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+                              QUIC_RST_ACKNOWLEDGEMENT));
+  }
+  visitor_->OnRstStream(rst1);
+
+  // For version-99 will create and receive a stop-sending, completing
+  // the full-close expected by this test.
+  InjectStopSendingFrame(GetNthClientInitiatedBidirectionalId(0),
+                         QUIC_ERROR_PROCESSING_STREAM);
+
+  EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+
+  // Send two bytes of payload.
+  QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece("HT"));
+  visitor_->OnStreamFrame(data1);
+
+  // The stream should never be opened, now that the reset is received.
+  EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+  EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicServerSessionBaseTest, AcceptClosedStream) {
+  // Send (empty) compressed headers followed by two bytes of data.
+  QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                         QuicStringPiece("\1\0\0\0\0\0\0\0HT"));
+  QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+                         QuicStringPiece("\2\0\0\0\0\0\0\0HT"));
+  visitor_->OnStreamFrame(frame1);
+  visitor_->OnStreamFrame(frame2);
+  EXPECT_EQ(2u, session_->GetNumOpenIncomingStreams());
+
+  // Send a reset (and expect the peer to send a RST in response).
+  QuicRstStreamFrame rst(kInvalidControlFrameId,
+                         GetNthClientInitiatedBidirectionalId(0),
+                         QUIC_ERROR_PROCESSING_STREAM, 0);
+  EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+  if (transport_version() != QUIC_VERSION_99) {
+    // For non-version 99, the RESET_STREAM will do the full close.
+    // Set up expects accordingly.
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*connection_,
+                OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+                              QUIC_RST_ACKNOWLEDGEMENT));
+  }
+  visitor_->OnRstStream(rst);
+
+  // For version-99 will create and receive a stop-sending, completing
+  // the full-close expected by this test.
+  InjectStopSendingFrame(GetNthClientInitiatedBidirectionalId(0),
+                         QUIC_ERROR_PROCESSING_STREAM);
+
+  // If we were tracking, we'd probably want to reject this because it's data
+  // past the reset point of stream 3.  As it's a closed stream we just drop the
+  // data on the floor, but accept the packet because it has data for stream 5.
+  QuicStreamFrame frame3(GetNthClientInitiatedBidirectionalId(0), false, 2,
+                         QuicStringPiece("TP"));
+  QuicStreamFrame frame4(GetNthClientInitiatedBidirectionalId(1), false, 2,
+                         QuicStringPiece("TP"));
+  visitor_->OnStreamFrame(frame3);
+  visitor_->OnStreamFrame(frame4);
+  // The stream should never be opened, now that the reset is received.
+  EXPECT_EQ(1u, session_->GetNumOpenIncomingStreams());
+  EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicServerSessionBaseTest, MaxOpenStreams) {
+  // Test that the server refuses if a client attempts to open too many data
+  // streams.  For versions other than version 99, the server accepts slightly
+  // more than the negotiated stream limit to deal with rare cases where a
+  // client FIN/RST is lost.
+
+  session_->OnConfigNegotiated();
+  if (transport_version() != QUIC_VERSION_99) {
+    // The slightly increased stream limit is set during config negotiation.  It
+    // is either an increase of 10 over negotiated limit, or a fixed percentage
+    // scaling, whichever is larger. Test both before continuing.
+    EXPECT_LT(kMaxStreamsMultiplier * kMaxStreamsForTest,
+              kMaxStreamsForTest + kMaxStreamsMinimumIncrement);
+    EXPECT_EQ(kMaxStreamsForTest + kMaxStreamsMinimumIncrement,
+              session_->max_open_incoming_bidirectional_streams());
+  }
+  EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+  QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+  // Open the max configured number of streams, should be no problem.
+  for (size_t i = 0; i < kMaxStreamsForTest; ++i) {
+    EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+        session_.get(), stream_id));
+    stream_id += QuicUtils::StreamIdDelta(connection_->transport_version());
+  }
+
+  if (transport_version() != QUIC_VERSION_99) {
+    // Open more streams: server should accept slightly more than the limit.
+    // Excess streams are for non-version-99 only.
+    for (size_t i = 0; i < kMaxStreamsMinimumIncrement; ++i) {
+      EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+          session_.get(), stream_id));
+      stream_id += QuicUtils::StreamIdDelta(connection_->transport_version());
+    }
+  }
+  // Now violate the server's internal stream limit.
+  stream_id += QuicUtils::StreamIdDelta(connection_->transport_version());
+
+  if (transport_version() != QUIC_VERSION_99) {
+    // For non-version 99, QUIC responds to an attempt to exceed the stream
+    // limit by resetting the stream.
+    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*connection_, OnStreamReset(stream_id, QUIC_REFUSED_STREAM));
+  } else {
+    // In version 99 QUIC responds to an attempt to exceed the stream limit by
+    // closing the connection.
+    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+  }
+  // Even if the connection remains open, the stream creation should fail.
+  EXPECT_FALSE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+      session_.get(), stream_id));
+}
+
+TEST_P(QuicServerSessionBaseTest, MaxAvailableBidirectionalStreams) {
+  // Test that the server closes the connection if a client makes too many data
+  // streams available.  The server accepts slightly more than the negotiated
+  // stream limit to deal with rare cases where a client FIN/RST is lost.
+
+  session_->OnConfigNegotiated();
+  const size_t kAvailableStreamLimit =
+      session_->MaxAvailableBidirectionalStreams();
+
+  EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+  EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+      session_.get(), GetNthClientInitiatedBidirectionalId(0)));
+
+  // Establish available streams up to the server's limit.
+  QuicStreamId next_id =
+      QuicUtils::StreamIdDelta(connection_->transport_version());
+  const int kLimitingStreamId =
+      GetNthClientInitiatedBidirectionalId(kAvailableStreamLimit + 1);
+  if (transport_version() != QUIC_VERSION_99) {
+    // This exceeds the stream limit. In versions other than 99
+    // this is allowed. Version 99 hews to the IETF spec and does
+    // not allow it.
+    EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+        session_.get(), kLimitingStreamId));
+    // A further available stream will result in connection close.
+    EXPECT_CALL(*connection_,
+                CloseConnection(QUIC_TOO_MANY_AVAILABLE_STREAMS, _, _));
+  } else {
+    // A further available stream will result in connection close.
+    EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+  }
+
+  // This forces stream kLimitingStreamId + 2 to become available, which
+  // violates the quota.
+  EXPECT_FALSE(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+      session_.get(), kLimitingStreamId + 2 * next_id));
+}
+
+TEST_P(QuicServerSessionBaseTest, GetEvenIncomingError) {
+  // Incoming streams on the server session must be odd.
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+  EXPECT_EQ(nullptr,
+            QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+                session_.get(), GetNthServerInitiatedUnidirectionalId(0)));
+}
+
+TEST_P(QuicServerSessionBaseTest, GetStreamDisconnected) {
+  // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+  if (GetParam() != AllSupportedVersions()[0]) {
+    return;
+  }
+
+  // Don't create new streams if the connection is disconnected.
+  QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+  EXPECT_QUIC_BUG(QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+                      session_.get(), GetNthClientInitiatedBidirectionalId(0)),
+                  "ShouldCreateIncomingStream called when disconnected");
+}
+
+class MockQuicCryptoServerStream : public QuicCryptoServerStream {
+ public:
+  explicit MockQuicCryptoServerStream(
+      const QuicCryptoServerConfig* crypto_config,
+      QuicCompressedCertsCache* compressed_certs_cache,
+      QuicServerSessionBase* session,
+      QuicCryptoServerStream::Helper* helper)
+      : QuicCryptoServerStream(
+            crypto_config,
+            compressed_certs_cache,
+            GetQuicReloadableFlag(enable_quic_stateless_reject_support),
+            session,
+            helper) {}
+  MockQuicCryptoServerStream(const MockQuicCryptoServerStream&) = delete;
+  MockQuicCryptoServerStream& operator=(const MockQuicCryptoServerStream&) =
+      delete;
+  ~MockQuicCryptoServerStream() override {}
+
+  MOCK_METHOD1(SendServerConfigUpdate,
+               void(const CachedNetworkParameters* cached_network_parameters));
+};
+
+TEST_P(QuicServerSessionBaseTest, BandwidthEstimates) {
+  // Test that bandwidth estimate updates are sent to the client, only when
+  // bandwidth resumption is enabled, the bandwidth estimate has changed
+  // sufficiently, enough time has passed,
+  // and we don't have any other data to write.
+
+  // Client has sent kBWRE connection option to trigger bandwidth resumption.
+  QuicTagVector copt;
+  copt.push_back(kBWRE);
+  QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
+  session_->OnConfigNegotiated();
+  EXPECT_TRUE(
+      QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+
+  int32_t bandwidth_estimate_kbytes_per_second = 123;
+  int32_t max_bandwidth_estimate_kbytes_per_second = 134;
+  int32_t max_bandwidth_estimate_timestamp = 1122334455;
+  const QuicString serving_region = "not a real region";
+  session_->set_serving_region(serving_region);
+
+  session_->UnregisterStreamPriority(
+      QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+      /*is_static=*/true);
+  QuicServerSessionBasePeer::SetCryptoStream(session_.get(), nullptr);
+  MockQuicCryptoServerStream* crypto_stream =
+      new MockQuicCryptoServerStream(&crypto_config_, &compressed_certs_cache_,
+                                     session_.get(), &stream_helper_);
+  QuicServerSessionBasePeer::SetCryptoStream(session_.get(), crypto_stream);
+  session_->RegisterStreamPriority(
+      QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+      /*is_static=*/true, QuicStream::kDefaultPriority);
+
+  // Set some initial bandwidth values.
+  QuicSentPacketManager* sent_packet_manager =
+      QuicConnectionPeer::GetSentPacketManager(session_->connection());
+  QuicSustainedBandwidthRecorder& bandwidth_recorder =
+      QuicSentPacketManagerPeer::GetBandwidthRecorder(sent_packet_manager);
+  // Seed an rtt measurement equal to the initial default rtt.
+  RttStats* rtt_stats =
+      const_cast<RttStats*>(sent_packet_manager->GetRttStats());
+  rtt_stats->UpdateRtt(rtt_stats->initial_rtt(), QuicTime::Delta::Zero(),
+                       QuicTime::Zero());
+  QuicSustainedBandwidthRecorderPeer::SetBandwidthEstimate(
+      &bandwidth_recorder, bandwidth_estimate_kbytes_per_second);
+  QuicSustainedBandwidthRecorderPeer::SetMaxBandwidthEstimate(
+      &bandwidth_recorder, max_bandwidth_estimate_kbytes_per_second,
+      max_bandwidth_estimate_timestamp);
+  // Queue up some pending data.
+  session_->MarkConnectionLevelWriteBlocked(QuicUtils::GetCryptoStreamId(
+      session_->connection()->transport_version()));
+  EXPECT_TRUE(session_->HasDataToWrite());
+
+  // There will be no update sent yet - not enough time has passed.
+  QuicTime now = QuicTime::Zero();
+  session_->OnCongestionWindowChange(now);
+
+  // Bandwidth estimate has now changed sufficiently but not enough time has
+  // passed to send a Server Config Update.
+  bandwidth_estimate_kbytes_per_second =
+      bandwidth_estimate_kbytes_per_second * 1.6;
+  session_->OnCongestionWindowChange(now);
+
+  // Bandwidth estimate has now changed sufficiently and enough time has passed,
+  // but not enough packets have been sent.
+  int64_t srtt_ms =
+      sent_packet_manager->GetRttStats()->smoothed_rtt().ToMilliseconds();
+  now = now + QuicTime::Delta::FromMilliseconds(
+                  kMinIntervalBetweenServerConfigUpdatesRTTs * srtt_ms);
+  session_->OnCongestionWindowChange(now);
+
+  // The connection no longer has pending data to be written.
+  session_->OnCanWrite();
+  EXPECT_FALSE(session_->HasDataToWrite());
+  session_->OnCongestionWindowChange(now);
+
+  // Bandwidth estimate has now changed sufficiently, enough time has passed,
+  // and enough packets have been sent.
+  SerializedPacket packet(
+      QuicPacketNumber(1) + kMinPacketsBetweenServerConfigUpdates,
+      PACKET_4BYTE_PACKET_NUMBER, nullptr, 1000, false, false);
+  sent_packet_manager->OnPacketSent(&packet, QuicPacketNumber(), now,
+                                    NOT_RETRANSMISSION,
+                                    HAS_RETRANSMITTABLE_DATA);
+
+  // Verify that the proto has exactly the values we expect.
+  CachedNetworkParameters expected_network_params;
+  expected_network_params.set_bandwidth_estimate_bytes_per_second(
+      bandwidth_recorder.BandwidthEstimate().ToBytesPerSecond());
+  expected_network_params.set_max_bandwidth_estimate_bytes_per_second(
+      bandwidth_recorder.MaxBandwidthEstimate().ToBytesPerSecond());
+  expected_network_params.set_max_bandwidth_timestamp_seconds(
+      bandwidth_recorder.MaxBandwidthTimestamp());
+  expected_network_params.set_min_rtt_ms(session_->connection()
+                                             ->sent_packet_manager()
+                                             .GetRttStats()
+                                             ->min_rtt()
+                                             .ToMilliseconds());
+  expected_network_params.set_previous_connection_state(
+      CachedNetworkParameters::CONGESTION_AVOIDANCE);
+  expected_network_params.set_timestamp(
+      session_->connection()->clock()->WallNow().ToUNIXSeconds());
+  expected_network_params.set_serving_region(serving_region);
+
+  EXPECT_CALL(*crypto_stream,
+              SendServerConfigUpdate(EqualsProto(expected_network_params)))
+      .Times(1);
+  EXPECT_CALL(*connection_, OnSendConnectionState(_)).Times(1);
+  session_->OnCongestionWindowChange(now);
+}
+
+TEST_P(QuicServerSessionBaseTest, BandwidthResumptionExperiment) {
+  if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+    // This test relies on resumption, which is not currently supported by the
+    // TLS handshake.
+    // TODO(nharper): Add support for resumption to the TLS handshake.
+    return;
+  }
+  // Test that if a client provides a CachedNetworkParameters with the same
+  // serving region as the current server, and which was made within an hour of
+  // now, that this data is passed down to the send algorithm.
+
+  // Client has sent kBWRE connection option to trigger bandwidth resumption.
+  QuicTagVector copt;
+  copt.push_back(kBWRE);
+  QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
+
+  const QuicString kTestServingRegion = "a serving region";
+  session_->set_serving_region(kTestServingRegion);
+
+  // Set the time to be one hour + one second from the 0 baseline.
+  connection_->AdvanceTime(
+      QuicTime::Delta::FromSeconds(kNumSecondsPerHour + 1));
+
+  QuicCryptoServerStream* crypto_stream = static_cast<QuicCryptoServerStream*>(
+      QuicSessionPeer::GetMutableCryptoStream(session_.get()));
+
+  // No effect if no CachedNetworkParameters provided.
+  EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(0);
+  session_->OnConfigNegotiated();
+
+  // No effect if CachedNetworkParameters provided, but different serving
+  // regions.
+  CachedNetworkParameters cached_network_params;
+  cached_network_params.set_bandwidth_estimate_bytes_per_second(1);
+  cached_network_params.set_serving_region("different serving region");
+  crypto_stream->SetPreviousCachedNetworkParams(cached_network_params);
+  EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(0);
+  session_->OnConfigNegotiated();
+
+  // Same serving region, but timestamp is too old, should have no effect.
+  cached_network_params.set_serving_region(kTestServingRegion);
+  cached_network_params.set_timestamp(0);
+  crypto_stream->SetPreviousCachedNetworkParams(cached_network_params);
+  EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(0);
+  session_->OnConfigNegotiated();
+
+  // Same serving region, and timestamp is recent: estimate is stored.
+  cached_network_params.set_timestamp(
+      connection_->clock()->WallNow().ToUNIXSeconds());
+  crypto_stream->SetPreviousCachedNetworkParams(cached_network_params);
+  EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(1);
+  session_->OnConfigNegotiated();
+}
+
+TEST_P(QuicServerSessionBaseTest, BandwidthMaxEnablesResumption) {
+  EXPECT_FALSE(
+      QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+
+  // Client has sent kBWMX connection option to trigger bandwidth resumption.
+  QuicTagVector copt;
+  copt.push_back(kBWMX);
+  QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
+  session_->OnConfigNegotiated();
+  EXPECT_TRUE(
+      QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+}
+
+TEST_P(QuicServerSessionBaseTest, NoBandwidthResumptionByDefault) {
+  EXPECT_FALSE(
+      QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+  session_->OnConfigNegotiated();
+  EXPECT_FALSE(
+      QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
+}
+
+// Tests which check the lifetime management of data members of
+// QuicCryptoServerStream objects when async GetProof is in use.
+class StreamMemberLifetimeTest : public QuicServerSessionBaseTest {
+ public:
+  StreamMemberLifetimeTest()
+      : QuicServerSessionBaseTest(
+            std::unique_ptr<FakeProofSource>(new FakeProofSource())),
+        crypto_config_peer_(&crypto_config_) {
+    GetFakeProofSource()->Activate();
+  }
+
+  FakeProofSource* GetFakeProofSource() const {
+    return static_cast<FakeProofSource*>(crypto_config_peer_.GetProofSource());
+  }
+
+ private:
+  QuicCryptoServerConfigPeer crypto_config_peer_;
+};
+
+INSTANTIATE_TEST_SUITE_P(StreamMemberLifetimeTests,
+                         StreamMemberLifetimeTest,
+                         ::testing::ValuesIn(AllSupportedVersions()));
+
+// Trigger an operation which causes an async invocation of
+// ProofSource::GetProof.  Delay the completion of the operation until after the
+// stream has been destroyed, and verify that there are no memory bugs.
+TEST_P(StreamMemberLifetimeTest, Basic) {
+  if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+    // This test depends on the QUIC crypto protocol, so it is disabled for the
+    // TLS handshake.
+    // TODO(nharper): Fix this test so it doesn't rely on QUIC crypto.
+    return;
+  }
+  SetQuicReloadableFlag(enable_quic_stateless_reject_support, true);
+  SetQuicReloadableFlag(quic_use_cheap_stateless_rejects, true);
+
+  const QuicClock* clock = helper_.GetClock();
+  CryptoHandshakeMessage chlo = crypto_test_utils::GenerateDefaultInchoateCHLO(
+      clock, GetParam().transport_version, &crypto_config_);
+  chlo.SetVector(kCOPT, QuicTagVector{kSREJ});
+  std::vector<ParsedQuicVersion> packet_version_list = {GetParam()};
+  std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
+      TestConnectionId(1), EmptyQuicConnectionId(), true, false, 1,
+      QuicString(chlo.GetSerialized().AsStringPiece()), CONNECTION_ID_PRESENT,
+      CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER, &packet_version_list));
+
+  EXPECT_CALL(stream_helper_, CanAcceptClientHello(_, _, _, _, _))
+      .WillOnce(testing::Return(true));
+  EXPECT_CALL(stream_helper_, GenerateConnectionIdForReject(_, _))
+      .WillOnce(testing::Return(TestConnectionId(12345)));
+
+  // Set the current packet
+  QuicConnectionPeer::SetCurrentPacket(session_->connection(),
+                                       packet->AsStringPiece());
+
+  // Yes, this is horrible.  But it's the easiest way to trigger the behavior we
+  // need to exercise.
+  QuicCryptoServerStreamBase* crypto_stream =
+      const_cast<QuicCryptoServerStreamBase*>(session_->crypto_stream());
+
+  // Feed the CHLO into the crypto stream, which will trigger a call to
+  // ProofSource::GetProof
+  crypto_test_utils::SendHandshakeMessageToStream(crypto_stream, chlo,
+                                                  Perspective::IS_CLIENT);
+  ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1);
+
+  // Destroy the stream
+  session_.reset();
+
+  // Allow the async ProofSource::GetProof call to complete.  Verify (under
+  // memory access checkers) that this does not result in accesses to any
+  // freed memory from the session or its subobjects.
+  GetFakeProofSource()->InvokePendingCallback(0);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_client_session.cc b/quic/core/http/quic_spdy_client_session.cc
new file mode 100644
index 0000000..9e8170b
--- /dev/null
+++ b/quic/core/http/quic_spdy_client_session.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_server_id.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+QuicSpdyClientSession::QuicSpdyClientSession(
+    const QuicConfig& config,
+    const ParsedQuicVersionVector& supported_versions,
+    QuicConnection* connection,
+    const QuicServerId& server_id,
+    QuicCryptoClientConfig* crypto_config,
+    QuicClientPushPromiseIndex* push_promise_index)
+    : QuicSpdyClientSessionBase(connection,
+                                push_promise_index,
+                                config,
+                                supported_versions),
+      server_id_(server_id),
+      crypto_config_(crypto_config),
+      respect_goaway_(true) {}
+
+QuicSpdyClientSession::~QuicSpdyClientSession() = default;
+
+void QuicSpdyClientSession::Initialize() {
+  crypto_stream_ = CreateQuicCryptoStream();
+  QuicSpdyClientSessionBase::Initialize();
+}
+
+void QuicSpdyClientSession::OnProofValid(
+    const QuicCryptoClientConfig::CachedState& /*cached*/) {}
+
+void QuicSpdyClientSession::OnProofVerifyDetailsAvailable(
+    const ProofVerifyDetails& /*verify_details*/) {}
+
+bool QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream() {
+  if (!crypto_stream_->encryption_established()) {
+    QUIC_DLOG(INFO) << "Encryption not active so no outgoing stream created.";
+    return false;
+  }
+  if (!GetQuicReloadableFlag(quic_use_common_stream_check) &&
+      connection()->transport_version() != QUIC_VERSION_99) {
+    if (GetNumOpenOutgoingStreams() >=
+        stream_id_manager().max_open_outgoing_streams()) {
+      QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
+                      << "Already " << GetNumOpenOutgoingStreams() << " open.";
+      return false;
+    }
+    if (goaway_received() && respect_goaway_) {
+      QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
+                      << "Already received goaway.";
+      return false;
+    }
+    return true;
+  }
+  if (goaway_received() && respect_goaway_) {
+    QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
+                    << "Already received goaway.";
+    return false;
+  }
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_common_stream_check, 1, 2);
+  return CanOpenNextOutgoingBidirectionalStream();
+}
+
+bool QuicSpdyClientSession::ShouldCreateOutgoingUnidirectionalStream() {
+  QUIC_BUG << "Try to create outgoing unidirectional client data streams";
+  return false;
+}
+
+QuicSpdyClientStream*
+QuicSpdyClientSession::CreateOutgoingBidirectionalStream() {
+  if (!ShouldCreateOutgoingBidirectionalStream()) {
+    return nullptr;
+  }
+  std::unique_ptr<QuicSpdyClientStream> stream = CreateClientStream();
+  QuicSpdyClientStream* stream_ptr = stream.get();
+  ActivateStream(std::move(stream));
+  return stream_ptr;
+}
+
+QuicSpdyClientStream*
+QuicSpdyClientSession::CreateOutgoingUnidirectionalStream() {
+  QUIC_BUG << "Try to create outgoing unidirectional client data streams";
+  return nullptr;
+}
+
+std::unique_ptr<QuicSpdyClientStream>
+QuicSpdyClientSession::CreateClientStream() {
+  return QuicMakeUnique<QuicSpdyClientStream>(
+      GetNextOutgoingBidirectionalStreamId(), this, BIDIRECTIONAL);
+}
+
+QuicCryptoClientStreamBase* QuicSpdyClientSession::GetMutableCryptoStream() {
+  return crypto_stream_.get();
+}
+
+const QuicCryptoClientStreamBase* QuicSpdyClientSession::GetCryptoStream()
+    const {
+  return crypto_stream_.get();
+}
+
+void QuicSpdyClientSession::CryptoConnect() {
+  DCHECK(flow_controller());
+  crypto_stream_->CryptoConnect();
+}
+
+int QuicSpdyClientSession::GetNumSentClientHellos() const {
+  return crypto_stream_->num_sent_client_hellos();
+}
+
+int QuicSpdyClientSession::GetNumReceivedServerConfigUpdates() const {
+  return crypto_stream_->num_scup_messages_received();
+}
+
+bool QuicSpdyClientSession::ShouldCreateIncomingStream(QuicStreamId id) {
+  if (!connection()->connected()) {
+    QUIC_BUG << "ShouldCreateIncomingStream called when disconnected";
+    return false;
+  }
+  if (goaway_received() && respect_goaway_) {
+    QUIC_DLOG(INFO) << "Failed to create a new outgoing stream. "
+                    << "Already received goaway.";
+    return false;
+  }
+  if (QuicUtils::IsClientInitiatedStreamId(connection()->transport_version(),
+                                           id) ||
+      (connection()->transport_version() == QUIC_VERSION_99 &&
+       QuicUtils::IsBidirectionalStreamId(id))) {
+    QUIC_LOG(WARNING) << "Received invalid push stream id " << id;
+    connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID,
+        "Server created non write unidirectional stream",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
+  return true;
+}
+
+QuicSpdyStream* QuicSpdyClientSession::CreateIncomingStream(
+    PendingStream pending) {
+  QuicSpdyStream* stream =
+      new QuicSpdyClientStream(std::move(pending), this, READ_UNIDIRECTIONAL);
+  ActivateStream(QuicWrapUnique(stream));
+  return stream;
+}
+
+QuicSpdyStream* QuicSpdyClientSession::CreateIncomingStream(QuicStreamId id) {
+  if (!ShouldCreateIncomingStream(id)) {
+    return nullptr;
+  }
+  QuicSpdyStream* stream =
+      new QuicSpdyClientStream(id, this, READ_UNIDIRECTIONAL);
+  ActivateStream(QuicWrapUnique(stream));
+  return stream;
+}
+
+std::unique_ptr<QuicCryptoClientStreamBase>
+QuicSpdyClientSession::CreateQuicCryptoStream() {
+  return QuicMakeUnique<QuicCryptoClientStream>(
+      server_id_, this,
+      crypto_config_->proof_verifier()->CreateDefaultContext(), crypto_config_,
+      this);
+}
+
+bool QuicSpdyClientSession::IsAuthorized(const QuicString& authority) {
+  return true;
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_client_session.h b/quic/core/http/quic_spdy_client_session.h
new file mode 100644
index 0000000..c92753b
--- /dev/null
+++ b/quic/core/http/quic_spdy_client_session.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// A client specific QuicSession subclass.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+class QuicConnection;
+class QuicServerId;
+
+class QuicSpdyClientSession : public QuicSpdyClientSessionBase {
+ public:
+  // Takes ownership of |connection|. Caller retains ownership of
+  // |promised_by_url|.
+  QuicSpdyClientSession(const QuicConfig& config,
+                        const ParsedQuicVersionVector& supported_versions,
+                        QuicConnection* connection,
+                        const QuicServerId& server_id,
+                        QuicCryptoClientConfig* crypto_config,
+                        QuicClientPushPromiseIndex* push_promise_index);
+  QuicSpdyClientSession(const QuicSpdyClientSession&) = delete;
+  QuicSpdyClientSession& operator=(const QuicSpdyClientSession&) = delete;
+  ~QuicSpdyClientSession() override;
+  // Set up the QuicSpdyClientSession. Must be called prior to use.
+  void Initialize() override;
+
+  // QuicSession methods:
+  QuicSpdyClientStream* CreateOutgoingBidirectionalStream() override;
+  QuicSpdyClientStream* CreateOutgoingUnidirectionalStream() override;
+  QuicCryptoClientStreamBase* GetMutableCryptoStream() override;
+  const QuicCryptoClientStreamBase* GetCryptoStream() const override;
+
+  bool IsAuthorized(const QuicString& authority) override;
+
+  // QuicSpdyClientSessionBase methods:
+  void OnProofValid(const QuicCryptoClientConfig::CachedState& cached) override;
+  void OnProofVerifyDetailsAvailable(
+      const ProofVerifyDetails& verify_details) override;
+
+  // Performs a crypto handshake with the server.
+  virtual void CryptoConnect();
+
+  // Returns the number of client hello messages that have been sent on the
+  // crypto stream. If the handshake has completed then this is one greater
+  // than the number of round-trips needed for the handshake.
+  int GetNumSentClientHellos() const;
+
+  int GetNumReceivedServerConfigUpdates() const;
+
+  void set_respect_goaway(bool respect_goaway) {
+    respect_goaway_ = respect_goaway;
+  }
+
+ protected:
+  // QuicSession methods:
+  QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override;
+  QuicSpdyStream* CreateIncomingStream(PendingStream pending) override;
+  // If an outgoing stream can be created, return true.
+  bool ShouldCreateOutgoingBidirectionalStream() override;
+  bool ShouldCreateOutgoingUnidirectionalStream() override;
+
+  // If an incoming stream can be created, return true.
+  // TODO(fayang): move this up to QuicSpdyClientSessionBase.
+  bool ShouldCreateIncomingStream(QuicStreamId id) override;
+
+  // Create the crypto stream. Called by Initialize().
+  virtual std::unique_ptr<QuicCryptoClientStreamBase> CreateQuicCryptoStream();
+
+  // Unlike CreateOutgoingBidirectionalStream, which applies a bunch of
+  // sanity checks, this simply returns a new QuicSpdyClientStream. This may be
+  // used by subclasses which want to use a subclass of QuicSpdyClientStream for
+  // streams but wish to use the sanity checks in
+  // CreateOutgoingBidirectionalStream.
+  virtual std::unique_ptr<QuicSpdyClientStream> CreateClientStream();
+
+  const QuicServerId& server_id() { return server_id_; }
+  QuicCryptoClientConfig* crypto_config() { return crypto_config_; }
+
+ private:
+  std::unique_ptr<QuicCryptoClientStreamBase> crypto_stream_;
+  QuicServerId server_id_;
+  QuicCryptoClientConfig* crypto_config_;
+
+  // If this is set to false, the client will ignore server GOAWAYs and allow
+  // the creation of streams regardless of the high chance they will fail.
+  bool respect_goaway_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_H_
diff --git a/quic/core/http/quic_spdy_client_session_base.cc b/quic/core/http/quic_spdy_client_session_base.cc
new file mode 100644
index 0000000..5a8fe8d
--- /dev/null
+++ b/quic/core/http/quic_spdy_client_session_base.cc
@@ -0,0 +1,209 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session_base.h"
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+QuicSpdyClientSessionBase::QuicSpdyClientSessionBase(
+    QuicConnection* connection,
+    QuicClientPushPromiseIndex* push_promise_index,
+    const QuicConfig& config,
+    const ParsedQuicVersionVector& supported_versions)
+    : QuicSpdySession(connection, nullptr, config, supported_versions),
+      push_promise_index_(push_promise_index),
+      largest_promised_stream_id_(
+          QuicUtils::GetInvalidStreamId(connection->transport_version())) {}
+
+QuicSpdyClientSessionBase::~QuicSpdyClientSessionBase() {
+  //  all promised streams for this session
+  for (auto& it : promised_by_id_) {
+    QUIC_DVLOG(1) << "erase stream " << it.first << " url " << it.second->url();
+    push_promise_index_->promised_by_url()->erase(it.second->url());
+  }
+  delete connection();
+}
+
+void QuicSpdyClientSessionBase::OnConfigNegotiated() {
+  QuicSpdySession::OnConfigNegotiated();
+}
+
+void QuicSpdyClientSessionBase::OnCryptoHandshakeEvent(
+    CryptoHandshakeEvent event) {
+  QuicSpdySession::OnCryptoHandshakeEvent(event);
+}
+
+void QuicSpdyClientSessionBase::OnInitialHeadersComplete(
+    QuicStreamId stream_id,
+    const SpdyHeaderBlock& response_headers) {
+  // Note that the strong ordering of the headers stream means that
+  // QuicSpdyClientStream::OnPromiseHeadersComplete must have already
+  // been called (on the associated stream) if this is a promised
+  // stream. However, this stream may not have existed at this time,
+  // hence the need to query the session.
+  QuicClientPromisedInfo* promised = GetPromisedById(stream_id);
+  if (!promised)
+    return;
+
+  promised->OnResponseHeaders(response_headers);
+}
+
+void QuicSpdyClientSessionBase::OnPromiseHeaderList(
+    QuicStreamId stream_id,
+    QuicStreamId promised_stream_id,
+    size_t frame_len,
+    const QuicHeaderList& header_list) {
+  if (QuicContainsKey(static_streams(), stream_id)) {
+    connection()->CloseConnection(
+        QUIC_INVALID_HEADERS_STREAM_DATA, "stream is static",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  if (promised_stream_id !=
+          QuicUtils::GetInvalidStreamId(connection()->transport_version()) &&
+      largest_promised_stream_id_ !=
+          QuicUtils::GetInvalidStreamId(connection()->transport_version()) &&
+      promised_stream_id <= largest_promised_stream_id_) {
+    connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID,
+        "Received push stream id lesser or equal to the"
+        " last accepted before",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  if (!IsIncomingStream(promised_stream_id)) {
+    connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID, "Received push stream id for outgoing stream.",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  largest_promised_stream_id_ = promised_stream_id;
+
+  QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+  if (!stream) {
+    // It's quite possible to receive headers after a stream has been reset.
+    return;
+  }
+  stream->OnPromiseHeaderList(promised_stream_id, frame_len, header_list);
+}
+
+bool QuicSpdyClientSessionBase::HandlePromised(QuicStreamId /* associated_id */,
+                                               QuicStreamId promised_id,
+                                               const SpdyHeaderBlock& headers) {
+  // Due to pathalogical packet re-ordering, it is possible that
+  // frames for the promised stream have already arrived, and the
+  // promised stream could be active or closed.
+  if (IsClosedStream(promised_id)) {
+    // There was a RST on the data stream already, perhaps
+    // QUIC_REFUSED_STREAM?
+    QUIC_DVLOG(1) << "Promise ignored for stream " << promised_id
+                  << " that is already closed";
+    return false;
+  }
+
+  if (push_promise_index_->promised_by_url()->size() >= get_max_promises()) {
+    QUIC_DVLOG(1) << "Too many promises, rejecting promise for stream "
+                  << promised_id;
+    ResetPromised(promised_id, QUIC_REFUSED_STREAM);
+    return false;
+  }
+
+  const QuicString url = SpdyUtils::GetPromisedUrlFromHeaders(headers);
+  QuicClientPromisedInfo* old_promised = GetPromisedByUrl(url);
+  if (old_promised) {
+    QUIC_DVLOG(1) << "Promise for stream " << promised_id
+                  << " is duplicate URL " << url
+                  << " of previous promise for stream " << old_promised->id();
+    ResetPromised(promised_id, QUIC_DUPLICATE_PROMISE_URL);
+    return false;
+  }
+
+  if (GetPromisedById(promised_id)) {
+    // OnPromiseHeadersComplete() would have closed the connection if
+    // promised id is a duplicate.
+    QUIC_BUG << "Duplicate promise for id " << promised_id;
+    return false;
+  }
+
+  QuicClientPromisedInfo* promised =
+      new QuicClientPromisedInfo(this, promised_id, url);
+  std::unique_ptr<QuicClientPromisedInfo> promised_owner(promised);
+  promised->Init();
+  QUIC_DVLOG(1) << "stream " << promised_id << " emplace url " << url;
+  (*push_promise_index_->promised_by_url())[url] = promised;
+  promised_by_id_[promised_id] = std::move(promised_owner);
+  bool result = promised->OnPromiseHeaders(headers);
+  if (result) {
+    DCHECK(promised_by_id_.find(promised_id) != promised_by_id_.end());
+  }
+  return result;
+}
+
+QuicClientPromisedInfo* QuicSpdyClientSessionBase::GetPromisedByUrl(
+    const QuicString& url) {
+  auto it = push_promise_index_->promised_by_url()->find(url);
+  if (it != push_promise_index_->promised_by_url()->end()) {
+    return it->second;
+  }
+  return nullptr;
+}
+
+QuicClientPromisedInfo* QuicSpdyClientSessionBase::GetPromisedById(
+    const QuicStreamId id) {
+  auto it = promised_by_id_.find(id);
+  if (it != promised_by_id_.end()) {
+    return it->second.get();
+  }
+  return nullptr;
+}
+
+QuicSpdyStream* QuicSpdyClientSessionBase::GetPromisedStream(
+    const QuicStreamId id) {
+  DynamicStreamMap::iterator it = dynamic_streams().find(id);
+  if (it != dynamic_streams().end()) {
+    return static_cast<QuicSpdyStream*>(it->second.get());
+  }
+  return nullptr;
+}
+
+void QuicSpdyClientSessionBase::DeletePromised(
+    QuicClientPromisedInfo* promised) {
+  push_promise_index_->promised_by_url()->erase(promised->url());
+  // Since promised_by_id_ contains the unique_ptr, this will destroy
+  // promised.
+  promised_by_id_.erase(promised->id());
+  headers_stream()->MaybeReleaseSequencerBuffer();
+}
+
+void QuicSpdyClientSessionBase::OnPushStreamTimedOut(QuicStreamId stream_id) {}
+
+void QuicSpdyClientSessionBase::ResetPromised(
+    QuicStreamId id,
+    QuicRstStreamErrorCode error_code) {
+  SendRstStream(id, error_code, 0);
+  if (!IsOpenStream(id)) {
+    MaybeIncreaseLargestPeerStreamId(id);
+  }
+}
+
+void QuicSpdyClientSessionBase::CloseStreamInner(QuicStreamId stream_id,
+                                                 bool locally_reset) {
+  QuicSpdySession::CloseStreamInner(stream_id, locally_reset);
+  headers_stream()->MaybeReleaseSequencerBuffer();
+}
+
+bool QuicSpdyClientSessionBase::ShouldReleaseHeadersStreamSequencerBuffer() {
+  return num_active_requests() == 0 && promised_by_id_.empty();
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_client_session_base.h b/quic/core/http/quic_spdy_client_session_base.h
new file mode 100644
index 0000000..f27708c
--- /dev/null
+++ b/quic/core/http/quic_spdy_client_session_base.h
@@ -0,0 +1,141 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_BASE_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_BASE_H_
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+
+namespace quic {
+
+class QuicClientPromisedInfo;
+class QuicClientPushPromiseIndex;
+class QuicSpdyClientStream;
+
+// For client/http layer code. Lookup promised streams based on
+// matching promised request url. The same map can be shared across
+// multiple sessions, since cross-origin pushes are allowed (subject
+// to authority constraints).  Clients should use this map to enforce
+// session affinity for requests corresponding to cross-origin push
+// promised streams.
+using QuicPromisedByUrlMap =
+    QuicUnorderedMap<QuicString, QuicClientPromisedInfo*>;
+
+// The maximum time a promises stream can be reserved without being
+// claimed by a client request.
+const int64_t kPushPromiseTimeoutSecs = 60;
+
+// Base class for all client-specific QuicSession subclasses.
+class QUIC_EXPORT_PRIVATE QuicSpdyClientSessionBase
+    : public QuicSpdySession,
+      public QuicCryptoClientStream::ProofHandler {
+ public:
+  // Takes ownership of |connection|. Caller retains ownership of
+  // |promised_by_url|.
+  QuicSpdyClientSessionBase(QuicConnection* connection,
+                            QuicClientPushPromiseIndex* push_promise_index,
+                            const QuicConfig& config,
+                            const ParsedQuicVersionVector& supported_versions);
+  QuicSpdyClientSessionBase(const QuicSpdyClientSessionBase&) = delete;
+  QuicSpdyClientSessionBase& operator=(const QuicSpdyClientSessionBase&) =
+      delete;
+
+  ~QuicSpdyClientSessionBase() override;
+
+  void OnConfigNegotiated() override;
+
+  // Override base class to set FEC policy before any data is sent by client.
+  void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
+
+  // Called by |headers_stream_| when push promise headers have been
+  // completely received.
+  void OnPromiseHeaderList(QuicStreamId stream_id,
+                           QuicStreamId promised_stream_id,
+                           size_t frame_len,
+                           const QuicHeaderList& header_list) override;
+
+  // Called by |QuicSpdyClientStream| on receipt of response headers,
+  // needed to detect promised server push streams, as part of
+  // client-request to push-stream rendezvous.
+  void OnInitialHeadersComplete(QuicStreamId stream_id,
+                                const spdy::SpdyHeaderBlock& response_headers);
+
+  // Called by |QuicSpdyClientStream| on receipt of PUSH_PROMISE, does
+  // some session level validation and creates the
+  // |QuicClientPromisedInfo| inserting into maps by (promised) id and
+  // url. Returns true if a new push promise is accepted. Resets the promised
+  // stream and returns false otherwise.
+  virtual bool HandlePromised(QuicStreamId associated_id,
+                              QuicStreamId promised_id,
+                              const spdy::SpdyHeaderBlock& headers);
+
+  // For cross-origin server push, this should verify the server is
+  // authoritative per [RFC2818], Section 3.  Roughly, subjectAltName
+  // list in the certificate should contain a matching DNS name, or IP
+  // address.  |hostname| is derived from the ":authority" header field of
+  // the PUSH_PROMISE frame, port if present there will be dropped.
+  virtual bool IsAuthorized(const QuicString& hostname) = 0;
+
+  // Session retains ownership.
+  QuicClientPromisedInfo* GetPromisedByUrl(const QuicString& url);
+  // Session retains ownership.
+  QuicClientPromisedInfo* GetPromisedById(const QuicStreamId id);
+
+  //
+  QuicSpdyStream* GetPromisedStream(const QuicStreamId id);
+
+  // Removes |promised| from the maps by url.
+  void ErasePromisedByUrl(QuicClientPromisedInfo* promised);
+
+  // Removes |promised| from the maps by url and id and destroys
+  // promised.
+  virtual void DeletePromised(QuicClientPromisedInfo* promised);
+
+  virtual void OnPushStreamTimedOut(QuicStreamId stream_id);
+
+  // Sends Rst for the stream, and makes sure that future calls to
+  // IsClosedStream(id) return true, which ensures that any subsequent
+  // frames related to this stream will be ignored (modulo flow
+  // control accounting).
+  void ResetPromised(QuicStreamId id, QuicRstStreamErrorCode error_code);
+
+  // Release headers stream's sequencer buffer if it's empty.
+  void CloseStreamInner(QuicStreamId stream_id, bool locally_reset) override;
+
+  // Returns true if there are no active requests and no promised streams.
+  bool ShouldReleaseHeadersStreamSequencerBuffer() override;
+
+  size_t get_max_promises() const {
+    return max_open_incoming_unidirectional_streams() *
+           kMaxPromisedStreamsMultiplier;
+  }
+
+  QuicClientPushPromiseIndex* push_promise_index() {
+    return push_promise_index_;
+  }
+
+ private:
+  // For QuicSpdyClientStream to detect that a response corresponds to a
+  // promise.
+  using QuicPromisedByIdMap =
+      QuicUnorderedMap<QuicStreamId, std::unique_ptr<QuicClientPromisedInfo>>;
+
+  // As per rfc7540, section 10.5: track promise streams in "reserved
+  // (remote)".  The primary key is URL from the promise request
+  // headers.  The promised stream id is a secondary key used to get
+  // promise info when the response headers of the promised stream
+  // arrive.
+  QuicClientPushPromiseIndex* push_promise_index_;
+  QuicPromisedByIdMap promised_by_id_;
+  QuicStreamId largest_promised_stream_id_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_SESSION_BASE_H_
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
new file mode 100644
index 0000000..3a40043
--- /dev/null
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -0,0 +1,788 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+
+#include <memory>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.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_str_cat.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/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_quic_spdy_client_stream.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_framer_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_packet_creator_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+using spdy::SpdyHeaderBlock;
+using testing::_;
+using testing::AnyNumber;
+using testing::Invoke;
+using testing::Truly;
+
+namespace quic {
+namespace test {
+namespace {
+
+const char kServerHostname[] = "test.example.com";
+const uint16_t kPort = 443;
+
+class TestQuicSpdyClientSession : public QuicSpdyClientSession {
+ public:
+  explicit TestQuicSpdyClientSession(
+      const QuicConfig& config,
+      const ParsedQuicVersionVector& supported_versions,
+      QuicConnection* connection,
+      const QuicServerId& server_id,
+      QuicCryptoClientConfig* crypto_config,
+      QuicClientPushPromiseIndex* push_promise_index)
+      : QuicSpdyClientSession(config,
+                              supported_versions,
+                              connection,
+                              server_id,
+                              crypto_config,
+                              push_promise_index) {}
+
+  std::unique_ptr<QuicSpdyClientStream> CreateClientStream() override {
+    return QuicMakeUnique<MockQuicSpdyClientStream>(
+        GetNextOutgoingBidirectionalStreamId(), this, BIDIRECTIONAL);
+  }
+
+  MockQuicSpdyClientStream* CreateIncomingStream(QuicStreamId id) override {
+    if (!ShouldCreateIncomingStream(id)) {
+      return nullptr;
+    }
+    MockQuicSpdyClientStream* stream =
+        new MockQuicSpdyClientStream(id, this, READ_UNIDIRECTIONAL);
+    ActivateStream(QuicWrapUnique(stream));
+    return stream;
+  }
+};
+
+class QuicSpdyClientSessionTest : public QuicTestWithParam<ParsedQuicVersion> {
+ protected:
+  QuicSpdyClientSessionTest()
+      : crypto_config_(crypto_test_utils::ProofVerifierForTesting(),
+                       TlsClientHandshaker::CreateSslCtx()),
+        promised_stream_id_(
+            QuicUtils::GetInvalidStreamId(GetParam().transport_version)),
+        associated_stream_id_(
+            QuicUtils::GetInvalidStreamId(GetParam().transport_version)) {
+    Initialize();
+    // Advance the time, because timers do not like uninitialized times.
+    connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  }
+
+  ~QuicSpdyClientSessionTest() override {
+    // Session must be destroyed before promised_by_url_
+    session_.reset(nullptr);
+  }
+
+  void Initialize() {
+    session_.reset();
+    connection_ = new PacketSavingConnection(&helper_, &alarm_factory_,
+                                             Perspective::IS_CLIENT,
+                                             SupportedVersions(GetParam()));
+    session_ = QuicMakeUnique<TestQuicSpdyClientSession>(
+        DefaultQuicConfig(), SupportedVersions(GetParam()), connection_,
+        QuicServerId(kServerHostname, kPort, false), &crypto_config_,
+        &push_promise_index_);
+    session_->Initialize();
+    push_promise_[":path"] = "/bar";
+    push_promise_[":authority"] = "www.google.com";
+    push_promise_[":version"] = "HTTP/1.1";
+    push_promise_[":method"] = "GET";
+    push_promise_[":scheme"] = "https";
+    promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
+    promised_stream_id_ = GetNthServerInitiatedUnidirectionalStreamId(
+        connection_->transport_version(), 0);
+    associated_stream_id_ = GetNthClientInitiatedBidirectionalStreamId(
+        connection_->transport_version(), 0);
+  }
+
+  // The function ensures that A) the max stream id frames get properly deleted
+  // (since the test uses a 'did we leak memory' check ... if we just lose the
+  // frame, the test fails) and B) returns true (instead of the default, false)
+  // which ensures that the rest of the system thinks that the frame actually
+  // was transmitted.
+  bool ClearMaxStreamIdControlFrame(const QuicFrame& frame) {
+    if (frame.type == MAX_STREAM_ID_FRAME) {
+      DeleteFrame(&const_cast<QuicFrame&>(frame));
+      return true;
+    }
+    return false;
+  }
+
+ public:
+  bool ClearStreamIdBlockedControlFrame(const QuicFrame& frame) {
+    if (frame.type == STREAM_ID_BLOCKED_FRAME) {
+      DeleteFrame(&const_cast<QuicFrame&>(frame));
+      return true;
+    }
+    return false;
+  }
+
+ protected:
+  void CompleteCryptoHandshake() {
+    CompleteCryptoHandshake(kDefaultMaxStreamsPerConnection);
+  }
+
+  void CompleteCryptoHandshake(uint32_t server_max_incoming_streams) {
+    if (connection_->transport_version() == QUIC_VERSION_99) {
+      EXPECT_CALL(*connection_, SendControlFrame(_))
+          .Times(testing::AnyNumber())
+          .WillRepeatedly(Invoke(
+              this, &QuicSpdyClientSessionTest::ClearMaxStreamIdControlFrame));
+    }
+    session_->CryptoConnect();
+    QuicCryptoClientStream* stream = static_cast<QuicCryptoClientStream*>(
+        session_->GetMutableCryptoStream());
+    crypto_test_utils::FakeServerOptions options;
+    QuicConfig config = DefaultQuicConfig();
+    config.SetMaxIncomingDynamicStreamsToSend(server_max_incoming_streams);
+    crypto_test_utils::HandshakeWithFakeServer(
+        &config, &helper_, &alarm_factory_, connection_, stream, options);
+  }
+
+  QuicCryptoClientConfig crypto_config_;
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  PacketSavingConnection* connection_;
+  std::unique_ptr<TestQuicSpdyClientSession> session_;
+  QuicClientPushPromiseIndex push_promise_index_;
+  SpdyHeaderBlock push_promise_;
+  QuicString promise_url_;
+  QuicStreamId promised_stream_id_;
+  QuicStreamId associated_stream_id_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+                         QuicSpdyClientSessionTest,
+                         ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdyClientSessionTest, CryptoConnect) {
+  CompleteCryptoHandshake();
+}
+
+TEST_P(QuicSpdyClientSessionTest, NoEncryptionAfterInitialEncryption) {
+  if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+    // This test relies on resumption and is QUIC crypto specific, so it is
+    // disabled for TLS.
+    // TODO(nharper): Add support for resumption to the TLS handshake, and fix
+    // this test to not rely on QUIC crypto.
+    return;
+  }
+  // Complete a handshake in order to prime the crypto config for 0-RTT.
+  CompleteCryptoHandshake();
+
+  // Now create a second session using the same crypto config.
+  Initialize();
+
+  EXPECT_CALL(*connection_, OnCanWrite());
+  // Starting the handshake should move immediately to encryption
+  // established and will allow streams to be created.
+  session_->CryptoConnect();
+  EXPECT_TRUE(session_->IsEncryptionEstablished());
+  QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+  ASSERT_TRUE(stream != nullptr);
+  EXPECT_NE(QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+            stream->id());
+
+  // Process an "inchoate" REJ from the server which will cause
+  // an inchoate CHLO to be sent and will leave the encryption level
+  // at NONE.
+  CryptoHandshakeMessage rej;
+  crypto_test_utils::FillInDummyReject(&rej, /* stateless */ false);
+  EXPECT_TRUE(session_->IsEncryptionEstablished());
+  crypto_test_utils::SendHandshakeMessageToStream(
+      session_->GetMutableCryptoStream(), rej, Perspective::IS_CLIENT);
+  EXPECT_FALSE(session_->IsEncryptionEstablished());
+  EXPECT_EQ(ENCRYPTION_NONE,
+            QuicPacketCreatorPeer::GetEncryptionLevel(
+                QuicConnectionPeer::GetPacketCreator(connection_)));
+  // Verify that no new streams may be created.
+  EXPECT_TRUE(session_->CreateOutgoingBidirectionalStream() == nullptr);
+  // Verify that no data may be send on existing streams.
+  char data[] = "hello world";
+  QuicConsumedData consumed = session_->WritevData(
+      stream, stream->id(), QUIC_ARRAYSIZE(data), 0, NO_FIN);
+  EXPECT_FALSE(consumed.fin_consumed);
+  EXPECT_EQ(0u, consumed.bytes_consumed);
+}
+
+TEST_P(QuicSpdyClientSessionTest, MaxNumStreamsWithNoFinOrRst) {
+  if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+    // This test relies on the MIDS transport parameter, which is not yet
+    // supported in TLS 1.3.
+    // TODO(nharper): Add support for Transport Parameters in the TLS handshake.
+    return;
+  }
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AnyNumber());
+  EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(AnyNumber());
+
+  const uint32_t kServerMaxIncomingStreams = 1;
+  CompleteCryptoHandshake(kServerMaxIncomingStreams);
+
+  QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+  ASSERT_TRUE(stream);
+  EXPECT_FALSE(session_->CreateOutgoingBidirectionalStream());
+
+  // Close the stream, but without having received a FIN or a RST_STREAM
+  // or MAX_STREAM_ID (V99) and check that a new one can not be created.
+  session_->CloseStream(stream->id());
+  EXPECT_EQ(1u, session_->GetNumOpenOutgoingStreams());
+
+  stream = session_->CreateOutgoingBidirectionalStream();
+  EXPECT_FALSE(stream);
+}
+
+TEST_P(QuicSpdyClientSessionTest, MaxNumStreamsWithRst) {
+  if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+    // This test relies on the MIDS transport parameter, which is not yet
+    // supported in TLS 1.3.
+    // TODO(nharper): Add support for Transport Parameters in the TLS handshake.
+    return;
+  }
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AnyNumber());
+  EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(AnyNumber());
+
+  const uint32_t kServerMaxIncomingStreams = 1;
+  CompleteCryptoHandshake(kServerMaxIncomingStreams);
+
+  QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+  ASSERT_NE(nullptr, stream);
+  EXPECT_EQ(nullptr, session_->CreateOutgoingBidirectionalStream());
+
+  // Close the stream and receive an RST frame to remove the unfinished stream
+  session_->CloseStream(stream->id());
+  session_->OnRstStream(QuicRstStreamFrame(kInvalidControlFrameId, stream->id(),
+                                           QUIC_RST_ACKNOWLEDGEMENT, 0));
+  // Check that a new one can be created.
+  EXPECT_EQ(0u, session_->GetNumOpenOutgoingStreams());
+  if (GetParam().transport_version == QUIC_VERSION_99) {
+    // In V99 the stream limit increases only if we get a MAX_STREAM_ID
+    // frame; pretend we got one.
+
+    // Note that this is to be the second stream created, but GetNth... starts
+    // numbering at 0 (the first stream is 0, second is 1...)
+    QuicMaxStreamIdFrame frame(0, GetNthClientInitiatedBidirectionalStreamId(
+                                      connection_->transport_version(), 1));
+    session_->OnMaxStreamIdFrame(frame);
+  }
+  stream = session_->CreateOutgoingBidirectionalStream();
+  EXPECT_NE(nullptr, stream);
+}
+
+TEST_P(QuicSpdyClientSessionTest, ResetAndTrailers) {
+  if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+    // This test relies on the MIDS transport parameter, which is not yet
+    // supported in TLS 1.3.
+    // TODO(nharper): Add support for Transport Parameters in the TLS handshake.
+    return;
+  }
+  // Tests the situation in which the client sends a RST at the same time that
+  // the server sends trailing headers (trailers). Receipt of the trailers by
+  // the client should result in all outstanding stream state being tidied up
+  // (including flow control, and number of available outgoing streams).
+  const uint32_t kServerMaxIncomingStreams = 1;
+  CompleteCryptoHandshake(kServerMaxIncomingStreams);
+
+  QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+  ASSERT_NE(nullptr, stream);
+
+  if (GetParam().transport_version == QUIC_VERSION_99) {
+    // For v99, trying to open a stream and failing due to lack
+    // of stream ids will result in a STREAM_ID_BLOCKED. Make
+    // sure we get one. Also clear out the frame because if it's
+    // left sitting, the later SendRstStream will not actually
+    // transmit the RST_STREAM because the connection will be in write-blocked
+    // state. This means that the SendControlFrame that is expected w.r.t. the
+    // RST_STREAM, below, will not be satisfied.
+    EXPECT_CALL(*connection_, SendControlFrame(_))
+        .WillOnce(Invoke(
+            this,
+            &QuicSpdyClientSessionTest::ClearStreamIdBlockedControlFrame));
+  }
+
+  EXPECT_EQ(nullptr, session_->CreateOutgoingBidirectionalStream());
+
+  QuicStreamId stream_id = stream->id();
+
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+  EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(1);
+  session_->SendRstStream(stream_id, QUIC_STREAM_PEER_GOING_AWAY, 0);
+
+  // A new stream cannot be created as the reset stream still counts as an open
+  // outgoing stream until closed by the server.
+  EXPECT_EQ(1u, session_->GetNumOpenOutgoingStreams());
+  stream = session_->CreateOutgoingBidirectionalStream();
+  EXPECT_EQ(nullptr, stream);
+
+  // The stream receives trailers with final byte offset: this is one of three
+  // ways that a peer can signal the end of a stream (the others being RST,
+  // stream data + FIN).
+  QuicHeaderList trailers;
+  trailers.OnHeaderBlockStart();
+  trailers.OnHeader(kFinalOffsetHeaderKey, "0");
+  trailers.OnHeaderBlockEnd(0, 0);
+  session_->OnStreamHeaderList(stream_id, /*fin=*/false, 0, trailers);
+
+  // The stream is now complete from the client's perspective, and it should
+  // be able to create a new outgoing stream.
+  EXPECT_EQ(0u, session_->GetNumOpenOutgoingStreams());
+  if (GetParam().transport_version == QUIC_VERSION_99) {
+    // Note that this is to be the second stream created, but GetNth... starts
+    // numbering at 0 (the first stream is 0, second is 1...)
+    QuicMaxStreamIdFrame frame(0, GetNthClientInitiatedBidirectionalStreamId(
+                                      connection_->transport_version(), 1));
+    session_->OnMaxStreamIdFrame(frame);
+  }
+  stream = session_->CreateOutgoingBidirectionalStream();
+  EXPECT_NE(nullptr, stream);
+}
+
+TEST_P(QuicSpdyClientSessionTest, ReceivedMalformedTrailersAfterSendingRst) {
+  // Tests the situation where the client has sent a RST to the server, and has
+  // received trailing headers with a malformed final byte offset value.
+  CompleteCryptoHandshake();
+
+  QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+  ASSERT_NE(nullptr, stream);
+
+  // Send the RST, which results in the stream being closed locally (but some
+  // state remains while the client waits for a response from the server).
+  QuicStreamId stream_id = stream->id();
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+  EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(1);
+  session_->SendRstStream(stream_id, QUIC_STREAM_PEER_GOING_AWAY, 0);
+
+  // The stream receives trailers with final byte offset, but the header value
+  // is non-numeric and should be treated as malformed.
+  QuicHeaderList trailers;
+  trailers.OnHeaderBlockStart();
+  trailers.OnHeader(kFinalOffsetHeaderKey, "invalid non-numeric value");
+  trailers.OnHeaderBlockEnd(0, 0);
+
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+  session_->OnStreamHeaderList(stream_id, /*fin=*/false, 0, trailers);
+}
+
+TEST_P(QuicSpdyClientSessionTest, OnStreamHeaderListWithStaticStream) {
+  // Test situation where OnStreamHeaderList is called by stream with static id.
+  CompleteCryptoHandshake();
+
+  QuicHeaderList trailers;
+  trailers.OnHeaderBlockStart();
+  trailers.OnHeader(kFinalOffsetHeaderKey, "0");
+  trailers.OnHeaderBlockEnd(0, 0);
+
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+  session_->OnStreamHeaderList(
+      QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+      /*fin=*/false, 0, trailers);
+}
+
+TEST_P(QuicSpdyClientSessionTest, OnPromiseHeaderListWithStaticStream) {
+  // Test situation where OnPromiseHeaderList is called by stream with static
+  // id.
+  CompleteCryptoHandshake();
+
+  QuicHeaderList trailers;
+  trailers.OnHeaderBlockStart();
+  trailers.OnHeader(kFinalOffsetHeaderKey, "0");
+  trailers.OnHeaderBlockEnd(0, 0);
+
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+  session_->OnPromiseHeaderList(
+      QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+      promised_stream_id_, 0, trailers);
+}
+
+TEST_P(QuicSpdyClientSessionTest, GoAwayReceived) {
+  CompleteCryptoHandshake();
+
+  // After receiving a GoAway, I should no longer be able to create outgoing
+  // streams.
+  session_->connection()->OnGoAwayFrame(QuicGoAwayFrame(
+      kInvalidControlFrameId, QUIC_PEER_GOING_AWAY, 1u, "Going away."));
+  EXPECT_EQ(nullptr, session_->CreateOutgoingBidirectionalStream());
+}
+
+static bool CheckForDecryptionError(QuicFramer* framer) {
+  return framer->error() == QUIC_DECRYPTION_FAILURE;
+}
+
+// Various sorts of invalid packets that should not cause a connection
+// to be closed.
+TEST_P(QuicSpdyClientSessionTest, InvalidPacketReceived) {
+  QuicSocketAddress server_address(TestPeerIPAddress(), kTestPort);
+  QuicSocketAddress client_address(TestPeerIPAddress(), kTestPort);
+
+  EXPECT_CALL(*connection_, ProcessUdpPacket(server_address, client_address, _))
+      .WillRepeatedly(Invoke(static_cast<MockQuicConnection*>(connection_),
+                             &MockQuicConnection::ReallyProcessUdpPacket));
+  EXPECT_CALL(*connection_, OnCanWrite()).Times(AnyNumber());
+  EXPECT_CALL(*connection_, OnError(_)).Times(1);
+
+  // Verify that empty packets don't close the connection.
+  QuicReceivedPacket zero_length_packet(nullptr, 0, QuicTime::Zero(), false);
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+  session_->ProcessUdpPacket(client_address, server_address,
+                             zero_length_packet);
+
+  // Verifiy that small, invalid packets don't close the connection.
+  char buf[2] = {0x00, 0x01};
+  QuicConnectionId connection_id = session_->connection()->connection_id();
+  QuicReceivedPacket valid_packet(buf, 2, QuicTime::Zero(), false);
+  // Close connection shouldn't be called.
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+  if (connection_->transport_version() > QUIC_VERSION_44) {
+    // Illegal fixed bit value.
+    EXPECT_CALL(*connection_, OnError(_)).Times(1);
+  }
+  session_->ProcessUdpPacket(client_address, server_address, valid_packet);
+
+  // Verify that a non-decryptable packet doesn't close the connection.
+  QuicFramerPeer::SetLastSerializedConnectionId(
+      QuicConnectionPeer::GetFramer(connection_), connection_id);
+  ParsedQuicVersionVector versions = SupportedVersions(GetParam());
+  std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
+      connection_id, EmptyQuicConnectionId(), false, false, 100, "data",
+      CONNECTION_ID_ABSENT, CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER,
+      &versions, Perspective::IS_SERVER));
+  std::unique_ptr<QuicReceivedPacket> received(
+      ConstructReceivedPacket(*packet, QuicTime::Zero()));
+  // Change the last byte of the encrypted data.
+  *(const_cast<char*>(received->data() + received->length() - 1)) += 1;
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+  EXPECT_CALL(*connection_, OnError(Truly(CheckForDecryptionError))).Times(1);
+  session_->ProcessUdpPacket(client_address, server_address, *received);
+}
+
+// A packet with invalid framing should cause a connection to be closed.
+TEST_P(QuicSpdyClientSessionTest, InvalidFramedPacketReceived) {
+  QuicSocketAddress server_address(TestPeerIPAddress(), kTestPort);
+  QuicSocketAddress client_address(TestPeerIPAddress(), kTestPort);
+
+  EXPECT_CALL(*connection_, ProcessUdpPacket(server_address, client_address, _))
+      .WillRepeatedly(Invoke(static_cast<MockQuicConnection*>(connection_),
+                             &MockQuicConnection::ReallyProcessUdpPacket));
+  EXPECT_CALL(*connection_, OnError(_)).Times(1);
+
+  // Verify that a decryptable packet with bad frames does close the connection.
+  QuicConnectionId connection_id = session_->connection()->connection_id();
+  QuicFramerPeer::SetLastSerializedConnectionId(
+      QuicConnectionPeer::GetFramer(connection_), connection_id);
+  ParsedQuicVersionVector versions = {GetParam()};
+  std::unique_ptr<QuicEncryptedPacket> packet(ConstructMisFramedEncryptedPacket(
+      connection_id, EmptyQuicConnectionId(), false, false, 100, "data",
+      CONNECTION_ID_ABSENT, CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER,
+      &versions, Perspective::IS_SERVER));
+  std::unique_ptr<QuicReceivedPacket> received(
+      ConstructReceivedPacket(*packet, QuicTime::Zero()));
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+  session_->ProcessUdpPacket(client_address, server_address, *received);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseOnPromiseHeaders) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  MockQuicSpdyClientStream* stream = static_cast<MockQuicSpdyClientStream*>(
+      session_->CreateOutgoingBidirectionalStream());
+
+  EXPECT_CALL(*stream, OnPromiseHeaderList(_, _, _));
+  session_->OnPromiseHeaderList(associated_stream_id_, promised_stream_id_, 0,
+                                QuicHeaderList());
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseOnPromiseHeadersAlreadyClosed) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  session_->CreateOutgoingBidirectionalStream();
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(associated_stream_id_, QUIC_REFUSED_STREAM));
+  session_->ResetPromised(associated_stream_id_, QUIC_REFUSED_STREAM);
+
+  session_->OnPromiseHeaderList(associated_stream_id_, promised_stream_id_, 0,
+                                QuicHeaderList());
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseOutOfOrder) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  MockQuicSpdyClientStream* stream = static_cast<MockQuicSpdyClientStream*>(
+      session_->CreateOutgoingBidirectionalStream());
+
+  EXPECT_CALL(*stream, OnPromiseHeaderList(promised_stream_id_, _, _));
+  session_->OnPromiseHeaderList(associated_stream_id_, promised_stream_id_, 0,
+                                QuicHeaderList());
+  associated_stream_id_ +=
+      QuicUtils::StreamIdDelta(connection_->transport_version());
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_STREAM_ID,
+                              "Received push stream id lesser or equal to the"
+                              " last accepted before",
+                              _));
+  session_->OnPromiseHeaderList(associated_stream_id_, promised_stream_id_, 0,
+                                QuicHeaderList());
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseOutgoingStreamId) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  MockQuicSpdyClientStream* stream = static_cast<MockQuicSpdyClientStream*>(
+      session_->CreateOutgoingBidirectionalStream());
+
+  // Promise an illegal (outgoing) stream id.
+  promised_stream_id_ = GetNthClientInitiatedBidirectionalStreamId(
+      connection_->transport_version(), 0);
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_INVALID_STREAM_ID,
+                      "Received push stream id for outgoing stream.", _));
+
+  session_->OnPromiseHeaderList(stream->id(), promised_stream_id_, 0,
+                                QuicHeaderList());
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseHandlePromise) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  session_->CreateOutgoingBidirectionalStream();
+
+  EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+                                       promised_stream_id_, push_promise_));
+
+  EXPECT_NE(session_->GetPromisedById(promised_stream_id_), nullptr);
+  EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseAlreadyClosed) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  session_->CreateOutgoingBidirectionalStream();
+  session_->GetOrCreateStream(promised_stream_id_);
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promised_stream_id_, QUIC_REFUSED_STREAM));
+
+  session_->ResetPromised(promised_stream_id_, QUIC_REFUSED_STREAM);
+  SpdyHeaderBlock promise_headers;
+  EXPECT_FALSE(session_->HandlePromised(associated_stream_id_,
+                                        promised_stream_id_, promise_headers));
+
+  // Verify that the promise was not created.
+  EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+  EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseDuplicateUrl) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  session_->CreateOutgoingBidirectionalStream();
+
+  EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+                                       promised_stream_id_, push_promise_));
+
+  EXPECT_NE(session_->GetPromisedById(promised_stream_id_), nullptr);
+  EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+  promised_stream_id_ +=
+      QuicUtils::StreamIdDelta(connection_->transport_version());
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promised_stream_id_, QUIC_DUPLICATE_PROMISE_URL));
+
+  EXPECT_FALSE(session_->HandlePromised(associated_stream_id_,
+                                        promised_stream_id_, push_promise_));
+
+  // Verify that the promise was not created.
+  EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, ReceivingPromiseEnhanceYourCalm) {
+  for (size_t i = 0u; i < session_->get_max_promises(); i++) {
+    push_promise_[":path"] = QuicStringPrintf("/bar%zu", i);
+
+    QuicStreamId id =
+        promised_stream_id_ +
+        i * QuicUtils::StreamIdDelta(connection_->transport_version());
+
+    EXPECT_TRUE(
+        session_->HandlePromised(associated_stream_id_, id, push_promise_));
+
+    // Verify that the promise is in the unclaimed streams map.
+    QuicString promise_url(SpdyUtils::GetPromisedUrlFromHeaders(push_promise_));
+    EXPECT_NE(session_->GetPromisedByUrl(promise_url), nullptr);
+    EXPECT_NE(session_->GetPromisedById(id), nullptr);
+  }
+
+  // One more promise, this should be refused.
+  int i = session_->get_max_promises();
+  push_promise_[":path"] = QuicStringPrintf("/bar%d", i);
+
+  QuicStreamId id =
+      promised_stream_id_ +
+      i * QuicUtils::StreamIdDelta(connection_->transport_version());
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(id, QUIC_REFUSED_STREAM));
+  EXPECT_FALSE(
+      session_->HandlePromised(associated_stream_id_, id, push_promise_));
+
+  // Verify that the promise was not created.
+  QuicString promise_url(SpdyUtils::GetPromisedUrlFromHeaders(push_promise_));
+  EXPECT_EQ(session_->GetPromisedById(id), nullptr);
+  EXPECT_EQ(session_->GetPromisedByUrl(promise_url), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, IsClosedTrueAfterResetPromisedAlreadyOpen) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  session_->GetOrCreateStream(promised_stream_id_);
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promised_stream_id_, QUIC_REFUSED_STREAM));
+  session_->ResetPromised(promised_stream_id_, QUIC_REFUSED_STREAM);
+  EXPECT_TRUE(session_->IsClosedStream(promised_stream_id_));
+}
+
+TEST_P(QuicSpdyClientSessionTest, IsClosedTrueAfterResetPromisedNonexistant) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promised_stream_id_, QUIC_REFUSED_STREAM));
+  session_->ResetPromised(promised_stream_id_, QUIC_REFUSED_STREAM);
+  EXPECT_TRUE(session_->IsClosedStream(promised_stream_id_));
+}
+
+TEST_P(QuicSpdyClientSessionTest, OnInitialHeadersCompleteIsPush) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+  session_->GetOrCreateStream(promised_stream_id_);
+  EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+                                       promised_stream_id_, push_promise_));
+  EXPECT_NE(session_->GetPromisedById(promised_stream_id_), nullptr);
+  EXPECT_NE(session_->GetPromisedStream(promised_stream_id_), nullptr);
+  EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+  session_->OnInitialHeadersComplete(promised_stream_id_, SpdyHeaderBlock());
+}
+
+TEST_P(QuicSpdyClientSessionTest, OnInitialHeadersCompleteIsNotPush) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+  session_->CreateOutgoingBidirectionalStream();
+  session_->OnInitialHeadersComplete(promised_stream_id_, SpdyHeaderBlock());
+}
+
+TEST_P(QuicSpdyClientSessionTest, DeletePromised) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+  session_->GetOrCreateStream(promised_stream_id_);
+  EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+                                       promised_stream_id_, push_promise_));
+  QuicClientPromisedInfo* promised =
+      session_->GetPromisedById(promised_stream_id_);
+  EXPECT_NE(promised, nullptr);
+  EXPECT_NE(session_->GetPromisedStream(promised_stream_id_), nullptr);
+  EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+
+  session_->DeletePromised(promised);
+  EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+  EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, ResetPromised) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+  session_->GetOrCreateStream(promised_stream_id_);
+  EXPECT_TRUE(session_->HandlePromised(associated_stream_id_,
+                                       promised_stream_id_, push_promise_));
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promised_stream_id_, QUIC_STREAM_PEER_GOING_AWAY));
+  session_->SendRstStream(promised_stream_id_, QUIC_STREAM_PEER_GOING_AWAY, 0);
+  QuicClientPromisedInfo* promised =
+      session_->GetPromisedById(promised_stream_id_);
+  EXPECT_NE(promised, nullptr);
+  EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
+  EXPECT_EQ(session_->GetPromisedStream(promised_stream_id_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseInvalidMethod) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  session_->CreateOutgoingBidirectionalStream();
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promised_stream_id_, QUIC_INVALID_PROMISE_METHOD));
+
+  push_promise_[":method"] = "POST";
+  EXPECT_FALSE(session_->HandlePromised(associated_stream_id_,
+                                        promised_stream_id_, push_promise_));
+
+  EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+  EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest, PushPromiseInvalidHost) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+
+  session_->CreateOutgoingBidirectionalStream();
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(promised_stream_id_, QUIC_INVALID_PROMISE_URL));
+
+  push_promise_[":authority"] = "";
+  EXPECT_FALSE(session_->HandlePromised(associated_stream_id_,
+                                        promised_stream_id_, push_promise_));
+
+  EXPECT_EQ(session_->GetPromisedById(promised_stream_id_), nullptr);
+  EXPECT_EQ(session_->GetPromisedByUrl(promise_url_), nullptr);
+}
+
+TEST_P(QuicSpdyClientSessionTest,
+       TryToCreateServerInitiatedBidirectionalStream) {
+  if (connection_->transport_version() == QUIC_VERSION_99) {
+    EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+  } else {
+    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+  }
+  session_->GetOrCreateStream(GetNthServerInitiatedBidirectionalStreamId(
+      connection_->transport_version(), 0));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_client_stream.cc b/quic/core/http/quic_spdy_client_stream.cc
new file mode 100644
index 0000000..d4e8a69
--- /dev/null
+++ b/quic/core/http/quic_spdy_client_stream.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_client_promised_info.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+QuicSpdyClientStream::QuicSpdyClientStream(QuicStreamId id,
+                                           QuicSpdyClientSession* session,
+                                           StreamType type)
+    : QuicSpdyStream(id, session, type),
+      content_length_(-1),
+      response_code_(0),
+      header_bytes_read_(0),
+      header_bytes_written_(0),
+      session_(session),
+      has_preliminary_headers_(false) {}
+
+QuicSpdyClientStream::QuicSpdyClientStream(PendingStream pending,
+                                           QuicSpdyClientSession* session,
+                                           StreamType type)
+    : QuicSpdyStream(std::move(pending), session, type),
+      content_length_(-1),
+      response_code_(0),
+      header_bytes_read_(0),
+      header_bytes_written_(0),
+      session_(session),
+      has_preliminary_headers_(false) {}
+
+QuicSpdyClientStream::~QuicSpdyClientStream() = default;
+
+void QuicSpdyClientStream::OnInitialHeadersComplete(
+    bool fin,
+    size_t frame_len,
+    const QuicHeaderList& header_list) {
+  QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list);
+
+  DCHECK(headers_decompressed());
+  header_bytes_read_ += frame_len;
+  if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_,
+                                         &response_headers_)) {
+    QUIC_DLOG(ERROR) << "Failed to parse header list: "
+                     << header_list.DebugString();
+    Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+    return;
+  }
+
+  if (!ParseHeaderStatusCode(response_headers_, &response_code_)) {
+    QUIC_DLOG(ERROR) << "Received invalid response code: "
+                     << response_headers_[":status"].as_string();
+    Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+    return;
+  }
+
+  if (response_code_ == 100 && !has_preliminary_headers_) {
+    // These are preliminary 100 Continue headers, not the actual response
+    // headers.
+    set_headers_decompressed(false);
+    has_preliminary_headers_ = true;
+    preliminary_headers_ = std::move(response_headers_);
+  }
+
+  ConsumeHeaderList();
+  QUIC_DVLOG(1) << "headers complete for stream " << id();
+
+  session_->OnInitialHeadersComplete(id(), response_headers_);
+}
+
+void QuicSpdyClientStream::OnTrailingHeadersComplete(
+    bool fin,
+    size_t frame_len,
+    const QuicHeaderList& header_list) {
+  QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list);
+  MarkTrailersConsumed();
+}
+
+void QuicSpdyClientStream::OnPromiseHeaderList(
+    QuicStreamId promised_id,
+    size_t frame_len,
+    const QuicHeaderList& header_list) {
+  header_bytes_read_ += frame_len;
+  int64_t content_length = -1;
+  SpdyHeaderBlock promise_headers;
+  if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length,
+                                         &promise_headers)) {
+    QUIC_DLOG(ERROR) << "Failed to parse promise headers: "
+                     << header_list.DebugString();
+    Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+    return;
+  }
+
+  session_->HandlePromised(id(), promised_id, promise_headers);
+  if (visitor() != nullptr) {
+    visitor()->OnPromiseHeadersComplete(promised_id, frame_len);
+  }
+}
+
+void QuicSpdyClientStream::OnBodyAvailable() {
+  // For push streams, visitor will not be set until the rendezvous
+  // between server promise and client request is complete.
+  if (visitor() == nullptr)
+    return;
+
+  while (HasBytesToRead()) {
+    struct iovec iov;
+    if (GetReadableRegions(&iov, 1) == 0) {
+      // No more data to read.
+      break;
+    }
+    QUIC_DVLOG(1) << "Client processed " << iov.iov_len << " bytes for stream "
+                  << id();
+    data_.append(static_cast<char*>(iov.iov_base), iov.iov_len);
+
+    if (content_length_ >= 0 &&
+        data_.size() > static_cast<uint64_t>(content_length_)) {
+      QUIC_DLOG(ERROR) << "Invalid content length (" << content_length_
+                       << ") with data of size " << data_.size();
+      Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+      return;
+    }
+    MarkConsumed(iov.iov_len);
+  }
+  if (sequencer()->IsClosed()) {
+    OnFinRead();
+  } else {
+    sequencer()->SetUnblocked();
+  }
+}
+
+size_t QuicSpdyClientStream::SendRequest(SpdyHeaderBlock headers,
+                                         QuicStringPiece body,
+                                         bool fin) {
+  QuicConnection::ScopedPacketFlusher flusher(
+      session_->connection(), QuicConnection::SEND_ACK_IF_QUEUED);
+  bool send_fin_with_headers = fin && body.empty();
+  size_t bytes_sent = body.size();
+  header_bytes_written_ =
+      WriteHeaders(std::move(headers), send_fin_with_headers, nullptr);
+  bytes_sent += header_bytes_written_;
+
+  if (!body.empty()) {
+    WriteOrBufferBody(body, fin);
+  }
+
+  return bytes_sent;
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_client_stream.h b/quic/core/http/quic_spdy_client_stream.h
new file mode 100644
index 0000000..8a66740
--- /dev/null
+++ b/quic/core/http/quic_spdy_client_stream.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_STREAM_H_
+
+#include <cstddef>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.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"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+class QuicSpdyClientSession;
+
+// All this does right now is send an SPDY request, and aggregate the
+// SPDY response.
+class QuicSpdyClientStream : public QuicSpdyStream {
+ public:
+  QuicSpdyClientStream(QuicStreamId id,
+                       QuicSpdyClientSession* session,
+                       StreamType type);
+  QuicSpdyClientStream(PendingStream pending,
+                       QuicSpdyClientSession* spdy_session,
+                       StreamType type);
+  QuicSpdyClientStream(const QuicSpdyClientStream&) = delete;
+  QuicSpdyClientStream& operator=(const QuicSpdyClientStream&) = delete;
+  ~QuicSpdyClientStream() override;
+
+  // Override the base class to parse and store headers.
+  void OnInitialHeadersComplete(bool fin,
+                                size_t frame_len,
+                                const QuicHeaderList& header_list) override;
+
+  // Override the base class to parse and store trailers.
+  void OnTrailingHeadersComplete(bool fin,
+                                 size_t frame_len,
+                                 const QuicHeaderList& header_list) override;
+
+  // Override the base class to handle creation of the push stream.
+  void OnPromiseHeaderList(QuicStreamId promised_id,
+                           size_t frame_len,
+                           const QuicHeaderList& header_list) override;
+
+  // QuicStream implementation called by the session when there's data for us.
+  void OnBodyAvailable() override;
+
+  // Serializes the headers and body, sends it to the server, and
+  // returns the number of bytes sent.
+  size_t SendRequest(spdy::SpdyHeaderBlock headers,
+                     QuicStringPiece body,
+                     bool fin);
+
+  // Returns the response data.
+  const QuicString& data() { return data_; }
+
+  // Returns whatever headers have been received for this stream.
+  const spdy::SpdyHeaderBlock& response_headers() { return response_headers_; }
+
+  const spdy::SpdyHeaderBlock& preliminary_headers() {
+    return preliminary_headers_;
+  }
+
+  size_t header_bytes_read() const { return header_bytes_read_; }
+
+  size_t header_bytes_written() const { return header_bytes_written_; }
+
+  int response_code() const { return response_code_; }
+
+  // While the server's SetPriority shouldn't be called externally, the creator
+  // of client-side streams should be able to set the priority.
+  using QuicSpdyStream::SetPriority;
+
+ private:
+  // The parsed headers received from the server.
+  spdy::SpdyHeaderBlock response_headers_;
+
+  // The parsed content-length, or -1 if none is specified.
+  int64_t content_length_;
+  int response_code_;
+  QuicString data_;
+  size_t header_bytes_read_;
+  size_t header_bytes_written_;
+
+  QuicSpdyClientSession* session_;
+
+  // These preliminary headers are used for the 100 Continue headers
+  // that may arrive before the response headers when the request has
+  // Expect: 100-continue.
+  bool has_preliminary_headers_;
+  spdy::SpdyHeaderBlock preliminary_headers_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_CLIENT_STREAM_H_
diff --git a/quic/core/http/quic_spdy_client_stream_test.cc b/quic/core/http/quic_spdy_client_stream_test.cc
new file mode 100644
index 0000000..0b2c157
--- /dev/null
+++ b/quic/core/http/quic_spdy_client_stream_test.cc
@@ -0,0 +1,233 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_stream.h"
+
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.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_text_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+using spdy::SpdyHeaderBlock;
+using testing::_;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+
+namespace {
+
+class MockQuicSpdyClientSession : public QuicSpdyClientSession {
+ public:
+  explicit MockQuicSpdyClientSession(
+      const ParsedQuicVersionVector& supported_versions,
+      QuicConnection* connection,
+      QuicClientPushPromiseIndex* push_promise_index)
+      : QuicSpdyClientSession(DefaultQuicConfig(),
+                              supported_versions,
+                              connection,
+                              QuicServerId("example.com", 443, false),
+                              &crypto_config_,
+                              push_promise_index),
+        crypto_config_(crypto_test_utils::ProofVerifierForTesting(),
+                       TlsClientHandshaker::CreateSslCtx()) {}
+  MockQuicSpdyClientSession(const MockQuicSpdyClientSession&) = delete;
+  MockQuicSpdyClientSession& operator=(const MockQuicSpdyClientSession&) =
+      delete;
+  ~MockQuicSpdyClientSession() override = default;
+
+  MOCK_METHOD1(CloseStream, void(QuicStreamId stream_id));
+
+ private:
+  QuicCryptoClientConfig crypto_config_;
+};
+
+class QuicSpdyClientStreamTest : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+  class StreamVisitor;
+
+  QuicSpdyClientStreamTest()
+      : connection_(
+            new StrictMock<MockQuicConnection>(&helper_,
+                                               &alarm_factory_,
+                                               Perspective::IS_CLIENT,
+                                               SupportedVersions(GetParam()))),
+        session_(connection_->supported_versions(),
+                 connection_,
+                 &push_promise_index_),
+        body_("hello world") {
+    session_.Initialize();
+
+    headers_[":status"] = "200";
+    headers_["content-length"] = "11";
+
+    stream_ = QuicMakeUnique<QuicSpdyClientStream>(
+        GetNthClientInitiatedBidirectionalStreamId(
+            connection_->transport_version(), 0),
+        &session_, BIDIRECTIONAL);
+    stream_visitor_ = QuicMakeUnique<StreamVisitor>();
+    stream_->set_visitor(stream_visitor_.get());
+  }
+
+  class StreamVisitor : public QuicSpdyClientStream::Visitor {
+    void OnClose(QuicSpdyStream* stream) override {
+      QUIC_DVLOG(1) << "stream " << stream->id();
+    }
+  };
+
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnection>* connection_;
+  QuicClientPushPromiseIndex push_promise_index_;
+
+  MockQuicSpdyClientSession session_;
+  std::unique_ptr<QuicSpdyClientStream> stream_;
+  std::unique_ptr<StreamVisitor> stream_visitor_;
+  SpdyHeaderBlock headers_;
+  QuicString body_;
+  HttpEncoder encoder_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+                         QuicSpdyClientStreamTest,
+                         ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdyClientStreamTest, TestReceivingIllegalResponseStatusCode) {
+  headers_[":status"] = "200 ok";
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  EXPECT_EQ(QUIC_BAD_APPLICATION_PAYLOAD, stream_->stream_error());
+}
+
+TEST_P(QuicSpdyClientStreamTest, TestFraming) {
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body_.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  QuicString data = VersionHasDataFrameHeader(connection_->transport_version())
+                        ? header + body_
+                        : body_;
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+  EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
+  EXPECT_EQ(200, stream_->response_code());
+  EXPECT_EQ(body_, stream_->data());
+}
+
+TEST_P(QuicSpdyClientStreamTest, TestFraming100Continue) {
+  headers_[":status"] = "100";
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, body_));
+  EXPECT_EQ("100", stream_->preliminary_headers().find(":status")->second);
+  EXPECT_EQ(0u, stream_->response_headers().size());
+  EXPECT_EQ(100, stream_->response_code());
+  EXPECT_EQ("", stream_->data());
+}
+
+TEST_P(QuicSpdyClientStreamTest, TestFramingOnePacket) {
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body_.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  QuicString data = VersionHasDataFrameHeader(connection_->transport_version())
+                        ? header + body_
+                        : body_;
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+  EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
+  EXPECT_EQ(200, stream_->response_code());
+  EXPECT_EQ(body_, stream_->data());
+}
+
+TEST_P(QuicSpdyClientStreamTest,
+       QUIC_TEST_DISABLED_IN_CHROME(TestFramingExtraData)) {
+  QuicString large_body = "hello world!!!!!!";
+
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  // The headers should parse successfully.
+  EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+  EXPECT_EQ("200", stream_->response_headers().find(":status")->second);
+  EXPECT_EQ(200, stream_->response_code());
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(large_body.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  QuicString data = VersionHasDataFrameHeader(connection_->transport_version())
+                        ? header + large_body
+                        : large_body;
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_,
+              OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
+
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+
+  EXPECT_NE(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+}
+
+TEST_P(QuicSpdyClientStreamTest, ReceivingTrailers) {
+  // Test that receiving trailing headers, containing a final offset, results in
+  // the stream being closed at that byte offset.
+
+  // Send headers as usual.
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+
+  // Send trailers before sending the body. Even though a FIN has been received
+  // the stream should not be closed, as it does not yet have all the data bytes
+  // promised by the final offset field.
+  SpdyHeaderBlock trailer_block;
+  trailer_block["trailer key"] = "trailer value";
+  trailer_block[kFinalOffsetHeaderKey] =
+      QuicTextUtils::Uint64ToString(body_.size());
+  auto trailers = AsHeaderList(trailer_block);
+  stream_->OnStreamHeaderList(true, trailers.uncompressed_header_bytes(),
+                              trailers);
+
+  // Now send the body, which should close the stream as the FIN has been
+  // received, as well as all data.
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body_.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  QuicString data = VersionHasDataFrameHeader(connection_->transport_version())
+                        ? header + body_
+                        : body_;
+  stream_->OnStreamFrame(
+      QuicStreamFrame(stream_->id(), /*fin=*/false, /*offset=*/0, data));
+  EXPECT_TRUE(stream_->reading_stopped());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_server_stream_base.cc b/quic/core/http/quic_spdy_server_stream_base.cc
new file mode 100644
index 0000000..cbe479e
--- /dev/null
+++ b/quic/core/http/quic_spdy_server_stream_base.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.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/platform/api/quic_logging.h"
+
+namespace quic {
+
+QuicSpdyServerStreamBase::QuicSpdyServerStreamBase(QuicStreamId id,
+                                                   QuicSpdySession* session,
+                                                   StreamType type)
+    : QuicSpdyStream(id, session, type) {}
+
+QuicSpdyServerStreamBase::QuicSpdyServerStreamBase(PendingStream pending,
+                                                   QuicSpdySession* session,
+                                                   StreamType type)
+    : QuicSpdyStream(std::move(pending), session, type) {}
+
+void QuicSpdyServerStreamBase::CloseWriteSide() {
+  if (!fin_received() && !rst_received() && sequencer()->ignore_read_data() &&
+      !rst_sent()) {
+    // Early cancel the stream if it has stopped reading before receiving FIN
+    // or RST.
+    DCHECK(fin_sent() || !session()->connection()->connected());
+    // Tell the peer to stop sending further data.
+    QUIC_DVLOG(1) << " Server: Send QUIC_STREAM_NO_ERROR on stream " << id();
+    Reset(QUIC_STREAM_NO_ERROR);
+  }
+
+  QuicSpdyStream::CloseWriteSide();
+}
+
+void QuicSpdyServerStreamBase::StopReading() {
+  if (!fin_received() && !rst_received() && write_side_closed() &&
+      !rst_sent()) {
+    DCHECK(fin_sent());
+    // Tell the peer to stop sending further data.
+    QUIC_DVLOG(1) << " Server: Send QUIC_STREAM_NO_ERROR on stream " << id();
+    Reset(QUIC_STREAM_NO_ERROR);
+  }
+  QuicSpdyStream::StopReading();
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_server_stream_base.h b/quic/core/http/quic_spdy_server_stream_base.h
new file mode 100644
index 0000000..438d152
--- /dev/null
+++ b/quic/core/http/quic_spdy_server_stream_base.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SERVER_STREAM_BASE_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SERVER_STREAM_BASE_H_
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+
+namespace quic {
+
+class QuicSpdyServerStreamBase : public QuicSpdyStream {
+ public:
+  QuicSpdyServerStreamBase(QuicStreamId id,
+                           QuicSpdySession* session,
+                           StreamType type);
+  QuicSpdyServerStreamBase(PendingStream pending,
+                           QuicSpdySession* session,
+                           StreamType type);
+  QuicSpdyServerStreamBase(const QuicSpdyServerStreamBase&) = delete;
+  QuicSpdyServerStreamBase& operator=(const QuicSpdyServerStreamBase&) = delete;
+
+  // Override the base class to send QUIC_STREAM_NO_ERROR to the peer
+  // when the stream has not received all the data.
+  void CloseWriteSide() override;
+  void StopReading() override;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SERVER_STREAM_BASE_H_
diff --git a/quic/core/http/quic_spdy_server_stream_base_test.cc b/quic/core/http/quic_spdy_server_stream_base_test.cc
new file mode 100644
index 0000000..4e1aa1b
--- /dev/null
+++ b/quic/core/http/quic_spdy_server_stream_base_test.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_server_stream_base.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+using testing::_;
+
+namespace quic {
+namespace test {
+namespace {
+
+class TestQuicSpdyServerStream : public QuicSpdyServerStreamBase {
+ public:
+  TestQuicSpdyServerStream(QuicStreamId id,
+                           QuicSpdySession* session,
+                           StreamType type)
+      : QuicSpdyServerStreamBase(id, session, type) {}
+
+  void OnBodyAvailable() override {}
+};
+
+class QuicSpdyServerStreamBaseTest : public QuicTest {
+ protected:
+  QuicSpdyServerStreamBaseTest()
+      : session_(new MockQuicConnection(&helper_,
+                                        &alarm_factory_,
+                                        Perspective::IS_SERVER)) {
+    stream_ = new TestQuicSpdyServerStream(
+        GetNthClientInitiatedBidirectionalStreamId(
+            session_.connection()->transport_version(), 0),
+        &session_, BIDIRECTIONAL);
+    session_.ActivateStream(QuicWrapUnique(stream_));
+    helper_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  }
+
+  QuicSpdyServerStreamBase* stream_ = nullptr;
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  MockQuicSpdySession session_;
+};
+
+TEST_F(QuicSpdyServerStreamBaseTest,
+       SendQuicRstStreamNoErrorWithEarlyResponse) {
+  stream_->StopReading();
+  EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(1);
+  stream_->set_fin_sent(true);
+  stream_->CloseWriteSide();
+}
+
+TEST_F(QuicSpdyServerStreamBaseTest,
+       DoNotSendQuicRstStreamNoErrorWithRstReceived) {
+  EXPECT_FALSE(stream_->reading_stopped());
+
+  EXPECT_CALL(session_, SendRstStream(_, QUIC_STREAM_NO_ERROR, _)).Times(0);
+
+  if (session_.connection()->transport_version() != QUIC_VERSION_99) {
+    EXPECT_CALL(session_, SendRstStream(_, QUIC_RST_ACKNOWLEDGEMENT, _))
+        .Times(1);
+  } else {
+    // Intercept & check that the call to the QuicConnection's OnStreamReast
+    // has correct stream ID and error code -- for V99/IETF Quic, it should
+    // have the STREAM_CANCELLED error code, not RST_ACK... Capture
+    // OnStreamReset (rather than SendRstStream) because the V99 path bypasses
+    // SendRstStream, calling SendRstStreamInner directly. Mocking
+    // SendRstStreamInner is problematic since the test relies on it to perform
+    // the closing operations and getting the stream in the correct state.
+    EXPECT_CALL(*(static_cast<MockQuicConnection*>(session_.connection())),
+                OnStreamReset(stream_->id(), QUIC_STREAM_CANCELLED));
+  }
+  QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream_->id(),
+                               QUIC_STREAM_CANCELLED, 1234);
+  stream_->OnStreamReset(rst_frame);
+  if (session_.connection()->transport_version() == QUIC_VERSION_99) {
+    // Create and inject a STOP SENDING frame to complete the close
+    // of the stream. This is only needed for version 99/IETF QUIC.
+    QuicStopSendingFrame stop_sending(
+        kInvalidControlFrameId, stream_->id(),
+        static_cast<QuicApplicationErrorCode>(QUIC_STREAM_CANCELLED));
+    session_.OnStopSendingFrame(stop_sending);
+  }
+
+  EXPECT_TRUE(stream_->reading_stopped());
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
new file mode 100644
index 0000000..01e630d
--- /dev/null
+++ b/quic/core/http/quic_spdy_session.cc
@@ -0,0 +1,696 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_headers_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fallthrough.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+
+using http2::Http2DecoderAdapter;
+using spdy::HpackEntry;
+using spdy::HpackHeaderTable;
+using spdy::Http2WeightToSpdy3Priority;
+using spdy::SETTINGS_ENABLE_PUSH;
+using spdy::SETTINGS_HEADER_TABLE_SIZE;
+using spdy::SETTINGS_MAX_HEADER_LIST_SIZE;
+using spdy::Spdy3PriorityToHttp2Weight;
+using spdy::SpdyErrorCode;
+using spdy::SpdyFramer;
+using spdy::SpdyFramerDebugVisitorInterface;
+using spdy::SpdyFramerVisitorInterface;
+using spdy::SpdyFrameType;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyHeadersHandlerInterface;
+using spdy::SpdyHeadersIR;
+using spdy::SpdyKnownSettingsId;
+using spdy::SpdyPingId;
+using spdy::SpdyPriority;
+using spdy::SpdyPriorityIR;
+using spdy::SpdyPushPromiseIR;
+using spdy::SpdySerializedFrame;
+using spdy::SpdySettingsId;
+using spdy::SpdySettingsIR;
+using spdy::SpdyStreamId;
+
+namespace quic {
+
+namespace {
+
+class HeaderTableDebugVisitor : public HpackHeaderTable::DebugVisitorInterface {
+ public:
+  HeaderTableDebugVisitor(const QuicClock* clock,
+                          std::unique_ptr<QuicHpackDebugVisitor> visitor)
+      : clock_(clock), headers_stream_hpack_visitor_(std::move(visitor)) {}
+  HeaderTableDebugVisitor(const HeaderTableDebugVisitor&) = delete;
+  HeaderTableDebugVisitor& operator=(const HeaderTableDebugVisitor&) = delete;
+
+  int64_t OnNewEntry(const HpackEntry& entry) override {
+    QUIC_DVLOG(1) << entry.GetDebugString();
+    return (clock_->ApproximateNow() - QuicTime::Zero()).ToMicroseconds();
+  }
+
+  void OnUseEntry(const HpackEntry& entry) override {
+    const QuicTime::Delta elapsed(
+        clock_->ApproximateNow() -
+        QuicTime::Delta::FromMicroseconds(entry.time_added()) -
+        QuicTime::Zero());
+    QUIC_DVLOG(1) << entry.GetDebugString() << " " << elapsed.ToMilliseconds()
+                  << " ms";
+    headers_stream_hpack_visitor_->OnUseEntry(elapsed);
+  }
+
+ private:
+  const QuicClock* clock_;
+  std::unique_ptr<QuicHpackDebugVisitor> headers_stream_hpack_visitor_;
+};
+
+}  // namespace
+
+// A SpdyFramerVisitor that passes HEADERS frames to the QuicSpdyStream, and
+// closes the connection if any unexpected frames are received.
+class QuicSpdySession::SpdyFramerVisitor
+    : public SpdyFramerVisitorInterface,
+      public SpdyFramerDebugVisitorInterface {
+ public:
+  explicit SpdyFramerVisitor(QuicSpdySession* session) : session_(session) {}
+  SpdyFramerVisitor(const SpdyFramerVisitor&) = delete;
+  SpdyFramerVisitor& operator=(const SpdyFramerVisitor&) = delete;
+
+  SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+      SpdyStreamId /* stream_id */) override {
+    return &header_list_;
+  }
+
+  void OnHeaderFrameEnd(SpdyStreamId /* stream_id */) override {
+    if (session_->IsConnected()) {
+      session_->OnHeaderList(header_list_);
+    }
+    header_list_.Clear();
+  }
+
+  void OnStreamFrameData(SpdyStreamId stream_id,
+                         const char* data,
+                         size_t len) override {
+    CloseConnection("SPDY DATA frame received.",
+                    QUIC_INVALID_HEADERS_STREAM_DATA);
+  }
+
+  void OnStreamEnd(SpdyStreamId stream_id) override {
+    // The framer invokes OnStreamEnd after processing a frame that had the fin
+    // bit set.
+  }
+
+  void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
+    CloseConnection("SPDY frame padding received.",
+                    QUIC_INVALID_HEADERS_STREAM_DATA);
+  }
+
+  void OnError(Http2DecoderAdapter::SpdyFramerError error) override {
+    QuicErrorCode code = QUIC_INVALID_HEADERS_STREAM_DATA;
+    switch (error) {
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_DECOMPRESS_FAILURE:
+        code = QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE;
+        break;
+      default:
+        break;
+    }
+    CloseConnection(
+        QuicStrCat("SPDY framing error: ",
+                   Http2DecoderAdapter::SpdyFramerErrorToString(error)),
+        code);
+  }
+
+  void OnDataFrameHeader(SpdyStreamId stream_id,
+                         size_t length,
+                         bool fin) override {
+    CloseConnection("SPDY DATA frame received.",
+                    QUIC_INVALID_HEADERS_STREAM_DATA);
+  }
+
+  void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override {
+    CloseConnection("SPDY RST_STREAM frame received.",
+                    QUIC_INVALID_HEADERS_STREAM_DATA);
+  }
+
+  void OnSetting(SpdySettingsId id, uint32_t value) override {
+    switch (id) {
+      case SETTINGS_HEADER_TABLE_SIZE:
+        session_->UpdateHeaderEncoderTableSize(value);
+        break;
+      case SETTINGS_ENABLE_PUSH:
+        if (session_->perspective() == Perspective::IS_SERVER) {
+          // See rfc7540, Section 6.5.2.
+          if (value > 1) {
+            CloseConnection(
+                QuicStrCat("Invalid value for SETTINGS_ENABLE_PUSH: ", value),
+                QUIC_INVALID_HEADERS_STREAM_DATA);
+            return;
+          }
+          session_->UpdateEnableServerPush(value > 0);
+          break;
+        } else {
+          CloseConnection(
+              QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id),
+              QUIC_INVALID_HEADERS_STREAM_DATA);
+        }
+        break;
+      // TODO(fayang): Need to support SETTINGS_MAX_HEADER_LIST_SIZE when
+      // clients are actually sending it.
+      case SETTINGS_MAX_HEADER_LIST_SIZE:
+        break;
+      default:
+        CloseConnection(
+            QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id),
+            QUIC_INVALID_HEADERS_STREAM_DATA);
+    }
+  }
+
+  void OnSettingsEnd() override {}
+
+  void OnPing(SpdyPingId unique_id, bool is_ack) override {
+    CloseConnection("SPDY PING frame received.",
+                    QUIC_INVALID_HEADERS_STREAM_DATA);
+  }
+
+  void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                SpdyErrorCode error_code) override {
+    CloseConnection("SPDY GOAWAY frame received.",
+                    QUIC_INVALID_HEADERS_STREAM_DATA);
+  }
+
+  void OnHeaders(SpdyStreamId stream_id,
+                 bool has_priority,
+                 int weight,
+                 SpdyStreamId /*parent_stream_id*/,
+                 bool /*exclusive*/,
+                 bool fin,
+                 bool end) override {
+    if (!session_->IsConnected()) {
+      return;
+    }
+
+    // TODO(mpw): avoid down-conversion and plumb SpdyStreamPrecedence through
+    // QuicHeadersStream.
+    SpdyPriority priority =
+        has_priority ? Http2WeightToSpdy3Priority(weight) : 0;
+    session_->OnHeaders(stream_id, has_priority, priority, fin);
+  }
+
+  void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {
+    CloseConnection("SPDY WINDOW_UPDATE frame received.",
+                    QUIC_INVALID_HEADERS_STREAM_DATA);
+  }
+
+  void OnPushPromise(SpdyStreamId stream_id,
+                     SpdyStreamId promised_stream_id,
+                     bool end) override {
+    if (!session_->supports_push_promise()) {
+      CloseConnection("PUSH_PROMISE not supported.",
+                      QUIC_INVALID_HEADERS_STREAM_DATA);
+      return;
+    }
+    if (!session_->IsConnected()) {
+      return;
+    }
+    session_->OnPushPromise(stream_id, promised_stream_id, end);
+  }
+
+  void OnContinuation(SpdyStreamId stream_id, bool end) override {}
+
+  void OnPriority(SpdyStreamId stream_id,
+                  SpdyStreamId parent_id,
+                  int weight,
+                  bool exclusive) override {
+    if (session_->connection()->transport_version() <= QUIC_VERSION_39) {
+      CloseConnection("SPDY PRIORITY frame received.",
+                      QUIC_INVALID_HEADERS_STREAM_DATA);
+      return;
+    }
+    if (!session_->IsConnected()) {
+      return;
+    }
+    // TODO (wangyix): implement real HTTP/2 weights and dependencies instead of
+    // converting to SpdyPriority.
+    SpdyPriority priority = Http2WeightToSpdy3Priority(weight);
+    session_->OnPriority(stream_id, priority);
+  }
+
+  bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override {
+    CloseConnection("Unknown frame type received.",
+                    QUIC_INVALID_HEADERS_STREAM_DATA);
+    return false;
+  }
+
+  // SpdyFramerDebugVisitorInterface implementation
+  void OnSendCompressedFrame(SpdyStreamId stream_id,
+                             SpdyFrameType type,
+                             size_t payload_len,
+                             size_t frame_len) override {
+    if (payload_len == 0) {
+      QUIC_BUG << "Zero payload length.";
+      return;
+    }
+    int compression_pct = 100 - (100 * frame_len) / payload_len;
+    QUIC_DVLOG(1) << "Net.QuicHpackCompressionPercentage: " << compression_pct;
+  }
+
+  void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+                                SpdyFrameType type,
+                                size_t frame_len) override {
+    if (session_->IsConnected()) {
+      session_->OnCompressedFrameSize(frame_len);
+    }
+  }
+
+  void set_max_uncompressed_header_bytes(
+      size_t set_max_uncompressed_header_bytes) {
+    header_list_.set_max_header_list_size(set_max_uncompressed_header_bytes);
+  }
+
+ private:
+  void CloseConnection(const QuicString& details, QuicErrorCode code) {
+    if (session_->IsConnected()) {
+      session_->CloseConnectionWithDetails(code, details);
+    }
+  }
+
+ private:
+  QuicSpdySession* session_;
+  QuicHeaderList header_list_;
+};
+
+QuicHpackDebugVisitor::QuicHpackDebugVisitor() {}
+
+QuicHpackDebugVisitor::~QuicHpackDebugVisitor() {}
+
+QuicSpdySession::QuicSpdySession(
+    QuicConnection* connection,
+    QuicSession::Visitor* visitor,
+    const QuicConfig& config,
+    const ParsedQuicVersionVector& supported_versions)
+    : QuicSession(connection, visitor, config, supported_versions),
+      max_inbound_header_list_size_(kDefaultMaxUncompressedHeaderSize),
+      server_push_enabled_(true),
+      stream_id_(
+          QuicUtils::GetInvalidStreamId(connection->transport_version())),
+      promised_stream_id_(
+          QuicUtils::GetInvalidStreamId(connection->transport_version())),
+      fin_(false),
+      frame_len_(0),
+      uncompressed_frame_len_(0),
+      supports_push_promise_(perspective() == Perspective::IS_CLIENT),
+      spdy_framer_(SpdyFramer::ENABLE_COMPRESSION),
+      spdy_framer_visitor_(new SpdyFramerVisitor(this)) {
+  h2_deframer_.set_visitor(spdy_framer_visitor_.get());
+  h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get());
+  spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
+}
+
+QuicSpdySession::~QuicSpdySession() {
+  // Set the streams' session pointers in closed and dynamic stream lists
+  // to null to avoid subsequent use of this session.
+  for (auto& stream : *closed_streams()) {
+    static_cast<QuicSpdyStream*>(stream.get())->ClearSession();
+  }
+  for (auto const& kv : zombie_streams()) {
+    static_cast<QuicSpdyStream*>(kv.second.get())->ClearSession();
+  }
+  for (auto const& kv : dynamic_streams()) {
+    static_cast<QuicSpdyStream*>(kv.second.get())->ClearSession();
+  }
+}
+
+void QuicSpdySession::Initialize() {
+  QuicSession::Initialize();
+
+  if (perspective() == Perspective::IS_SERVER) {
+    set_largest_peer_created_stream_id(
+        QuicUtils::GetHeadersStreamId(connection()->transport_version()));
+  } else {
+    QuicStreamId headers_stream_id = GetNextOutgoingBidirectionalStreamId();
+    DCHECK_EQ(headers_stream_id,
+              QuicUtils::GetHeadersStreamId(connection()->transport_version()));
+  }
+
+  if (VersionUsesQpack(connection()->transport_version())) {
+    qpack_encoder_ = QuicMakeUnique<QpackEncoder>(this, this);
+    qpack_decoder_ = QuicMakeUnique<QpackDecoder>(this, this);
+  }
+
+  headers_stream_ = QuicMakeUnique<QuicHeadersStream>((this));
+  DCHECK_EQ(QuicUtils::GetHeadersStreamId(connection()->transport_version()),
+            headers_stream_->id());
+  RegisterStaticStream(
+      QuicUtils::GetHeadersStreamId(connection()->transport_version()),
+      headers_stream_.get());
+
+  set_max_uncompressed_header_bytes(max_inbound_header_list_size_);
+
+  // Limit HPACK buffering to 2x header list size limit.
+  set_max_decode_buffer_size_bytes(2 * max_inbound_header_list_size_);
+}
+
+void QuicSpdySession::OnDecoderStreamError(QuicStringPiece error_message) {
+  DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+  // TODO(112770235): Signal connection error on decoder stream errors.
+  QUIC_NOTREACHED();
+}
+
+void QuicSpdySession::WriteEncoderStreamData(QuicStringPiece data) {
+  DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+  // TODO(112770235): Send encoder stream data on encoder stream.
+  QUIC_NOTREACHED();
+}
+
+void QuicSpdySession::OnEncoderStreamError(QuicStringPiece error_message) {
+  DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+  // TODO(112770235): Signal connection error on encoder stream errors.
+  QUIC_NOTREACHED();
+}
+
+void QuicSpdySession::WriteDecoderStreamData(QuicStringPiece data) {
+  DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+  // TODO(112770235): Send decoder stream data on decoder stream.
+  QUIC_NOTREACHED();
+}
+
+void QuicSpdySession::OnStreamHeadersPriority(QuicStreamId stream_id,
+                                              SpdyPriority priority) {
+  QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+  if (!stream) {
+    // It's quite possible to receive headers after a stream has been reset.
+    return;
+  }
+  stream->OnStreamHeadersPriority(priority);
+}
+
+void QuicSpdySession::OnStreamHeaderList(QuicStreamId stream_id,
+                                         bool fin,
+                                         size_t frame_len,
+                                         const QuicHeaderList& header_list) {
+  if (QuicContainsKey(static_streams(), stream_id)) {
+    connection()->CloseConnection(
+        QUIC_INVALID_HEADERS_STREAM_DATA, "stream is static",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+  if (stream == nullptr) {
+    // The stream no longer exists, but trailing headers may contain the final
+    // byte offset necessary for flow control and open stream accounting.
+    size_t final_byte_offset = 0;
+    for (const auto& header : header_list) {
+      const QuicString& header_key = header.first;
+      const QuicString& header_value = header.second;
+      if (header_key == kFinalOffsetHeaderKey) {
+        if (!QuicTextUtils::StringToSizeT(header_value, &final_byte_offset)) {
+          connection()->CloseConnection(
+              QUIC_INVALID_HEADERS_STREAM_DATA,
+              "Trailers are malformed (no final offset)",
+              ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+          return;
+        }
+        DVLOG(1) << "Received final byte offset in trailers for stream "
+                 << stream_id << ", which no longer exists.";
+        OnFinalByteOffsetReceived(stream_id, final_byte_offset);
+      }
+    }
+
+    // It's quite possible to receive headers after a stream has been reset.
+    return;
+  }
+  stream->OnStreamHeaderList(fin, frame_len, header_list);
+}
+
+void QuicSpdySession::OnPriorityFrame(QuicStreamId stream_id,
+                                      SpdyPriority priority) {
+  QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
+  if (!stream) {
+    // It's quite possible to receive a PRIORITY frame after a stream has been
+    // reset.
+    return;
+  }
+  stream->OnPriorityFrame(priority);
+}
+
+size_t QuicSpdySession::ProcessHeaderData(const struct iovec& iov) {
+  return h2_deframer_.ProcessInput(static_cast<char*>(iov.iov_base),
+                                   iov.iov_len);
+}
+
+size_t QuicSpdySession::WriteHeadersOnHeadersStream(
+    QuicStreamId id,
+    SpdyHeaderBlock headers,
+    bool fin,
+    SpdyPriority priority,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  return WriteHeadersOnHeadersStreamImpl(
+      id, std::move(headers), fin,
+      /* parent_stream_id = */ 0, Spdy3PriorityToHttp2Weight(priority),
+      /* exclusive = */ false, std::move(ack_listener));
+}
+
+size_t QuicSpdySession::WritePriority(QuicStreamId id,
+                                      QuicStreamId parent_stream_id,
+                                      int weight,
+                                      bool exclusive) {
+  if (connection()->transport_version() <= QUIC_VERSION_39) {
+    return 0;
+  }
+  SpdyPriorityIR priority_frame(id, parent_stream_id, weight, exclusive);
+  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(priority_frame));
+  headers_stream_->WriteOrBufferData(
+      QuicStringPiece(frame.data(), frame.size()), false, nullptr);
+  return frame.size();
+}
+
+size_t QuicSpdySession::WritePushPromise(QuicStreamId original_stream_id,
+                                         QuicStreamId promised_stream_id,
+                                         SpdyHeaderBlock headers) {
+  if (perspective() == Perspective::IS_CLIENT) {
+    QUIC_BUG << "Client shouldn't send PUSH_PROMISE";
+    return 0;
+  }
+
+  SpdyPushPromiseIR push_promise(original_stream_id, promised_stream_id,
+                                 std::move(headers));
+  // PUSH_PROMISE must not be the last frame sent out, at least followed by
+  // response headers.
+  push_promise.set_fin(false);
+
+  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(push_promise));
+  headers_stream_->WriteOrBufferData(
+      QuicStringPiece(frame.data(), frame.size()), false, nullptr);
+  return frame.size();
+}
+
+size_t QuicSpdySession::SendMaxHeaderListSize(size_t value) {
+  SpdySettingsIR settings_frame;
+  settings_frame.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE, value);
+
+  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(settings_frame));
+  headers_stream_->WriteOrBufferData(
+      QuicStringPiece(frame.data(), frame.size()), false, nullptr);
+  return frame.size();
+}
+
+QpackEncoder* QuicSpdySession::qpack_encoder() {
+  DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+  return qpack_encoder_.get();
+}
+
+QpackDecoder* QuicSpdySession::qpack_decoder() {
+  DCHECK(VersionUsesQpack(connection()->transport_version()));
+
+  return qpack_decoder_.get();
+}
+
+QuicSpdyStream* QuicSpdySession::GetSpdyDataStream(
+    const QuicStreamId stream_id) {
+  return static_cast<QuicSpdyStream*>(GetOrCreateDynamicStream(stream_id));
+}
+
+void QuicSpdySession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
+  QuicSession::OnCryptoHandshakeEvent(event);
+  if (event == HANDSHAKE_CONFIRMED && config()->SupportMaxHeaderListSize()) {
+    SendMaxHeaderListSize(max_inbound_header_list_size_);
+  }
+}
+
+// True if there are open HTTP requests.
+bool QuicSpdySession::ShouldKeepConnectionAlive() const {
+  // Change to check if there are open HTTP requests.
+  // When IETF QUIC control and QPACK streams are used, those will need to be
+  // subtracted from this count to ensure only request streams are counted.
+  return GetNumOpenDynamicStreams() > 0;
+}
+
+bool QuicSpdySession::ShouldBufferIncomingStream(QuicStreamId id) const {
+  DCHECK_EQ(QUIC_VERSION_99, connection()->transport_version());
+  return !QuicUtils::IsBidirectionalStreamId(id);
+}
+
+size_t QuicSpdySession::WriteHeadersOnHeadersStreamImpl(
+    QuicStreamId id,
+    spdy::SpdyHeaderBlock headers,
+    bool fin,
+    QuicStreamId parent_stream_id,
+    int weight,
+    bool exclusive,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  SpdyHeadersIR headers_frame(id, std::move(headers));
+  headers_frame.set_fin(fin);
+  if (perspective() == Perspective::IS_CLIENT) {
+    headers_frame.set_has_priority(true);
+    headers_frame.set_parent_stream_id(parent_stream_id);
+    headers_frame.set_weight(weight);
+    headers_frame.set_exclusive(exclusive);
+  }
+  SpdySerializedFrame frame(spdy_framer_.SerializeFrame(headers_frame));
+  headers_stream_->WriteOrBufferData(
+      QuicStringPiece(frame.data(), frame.size()), false,
+      std::move(ack_listener));
+  return frame.size();
+}
+
+void QuicSpdySession::OnPromiseHeaderList(QuicStreamId stream_id,
+                                          QuicStreamId promised_stream_id,
+                                          size_t frame_len,
+                                          const QuicHeaderList& header_list) {
+  QuicString error = "OnPromiseHeaderList should be overridden in client code.";
+  QUIC_BUG << error;
+  connection()->CloseConnection(QUIC_INTERNAL_ERROR, error,
+                                ConnectionCloseBehavior::SILENT_CLOSE);
+}
+
+bool QuicSpdySession::ShouldReleaseHeadersStreamSequencerBuffer() {
+  return false;
+}
+
+void QuicSpdySession::OnHeaders(SpdyStreamId stream_id,
+                                bool has_priority,
+                                SpdyPriority priority,
+                                bool fin) {
+  if (has_priority) {
+    if (perspective() == Perspective::IS_CLIENT) {
+      CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                 "Server must not send priorities.");
+      return;
+    }
+    OnStreamHeadersPriority(stream_id, priority);
+  } else {
+    if (perspective() == Perspective::IS_SERVER) {
+      CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                 "Client must send priorities.");
+      return;
+    }
+  }
+  DCHECK_EQ(QuicUtils::GetInvalidStreamId(connection()->transport_version()),
+            stream_id_);
+  DCHECK_EQ(QuicUtils::GetInvalidStreamId(connection()->transport_version()),
+            promised_stream_id_);
+  stream_id_ = stream_id;
+  fin_ = fin;
+}
+
+void QuicSpdySession::OnPushPromise(SpdyStreamId stream_id,
+                                    SpdyStreamId promised_stream_id,
+                                    bool end) {
+  DCHECK_EQ(QuicUtils::GetInvalidStreamId(connection()->transport_version()),
+            stream_id_);
+  DCHECK_EQ(QuicUtils::GetInvalidStreamId(connection()->transport_version()),
+            promised_stream_id_);
+  stream_id_ = stream_id;
+  promised_stream_id_ = promised_stream_id;
+}
+
+// TODO (wangyix): Why is SpdyStreamId used instead of QuicStreamId?
+// This occurs in many places in this file.
+void QuicSpdySession::OnPriority(SpdyStreamId stream_id,
+                                 SpdyPriority priority) {
+  if (perspective() == Perspective::IS_CLIENT) {
+    CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                               "Server must not send PRIORITY frames.");
+    return;
+  }
+  OnPriorityFrame(stream_id, priority);
+}
+
+void QuicSpdySession::OnHeaderList(const QuicHeaderList& header_list) {
+  QUIC_DVLOG(1) << "Received header list for stream " << stream_id_ << ": "
+                << header_list.DebugString();
+  if (promised_stream_id_ ==
+      QuicUtils::GetInvalidStreamId(connection()->transport_version())) {
+    OnStreamHeaderList(stream_id_, fin_, frame_len_, header_list);
+  } else {
+    OnPromiseHeaderList(stream_id_, promised_stream_id_, frame_len_,
+                        header_list);
+  }
+  // Reset state for the next frame.
+  promised_stream_id_ =
+      QuicUtils::GetInvalidStreamId(connection()->transport_version());
+  stream_id_ = QuicUtils::GetInvalidStreamId(connection()->transport_version());
+  fin_ = false;
+  frame_len_ = 0;
+  uncompressed_frame_len_ = 0;
+}
+
+void QuicSpdySession::OnCompressedFrameSize(size_t frame_len) {
+  frame_len_ += frame_len;
+}
+
+void QuicSpdySession::SetHpackEncoderDebugVisitor(
+    std::unique_ptr<QuicHpackDebugVisitor> visitor) {
+  spdy_framer_.SetEncoderHeaderTableDebugVisitor(
+      std::unique_ptr<HeaderTableDebugVisitor>(new HeaderTableDebugVisitor(
+          connection()->helper()->GetClock(), std::move(visitor))));
+}
+
+void QuicSpdySession::SetHpackDecoderDebugVisitor(
+    std::unique_ptr<QuicHpackDebugVisitor> visitor) {
+  h2_deframer_.SetDecoderHeaderTableDebugVisitor(
+      QuicMakeUnique<HeaderTableDebugVisitor>(
+          connection()->helper()->GetClock(), std::move(visitor)));
+}
+
+void QuicSpdySession::UpdateHeaderEncoderTableSize(uint32_t value) {
+  spdy_framer_.UpdateHeaderEncoderTableSize(value);
+}
+
+void QuicSpdySession::UpdateEnableServerPush(bool value) {
+  set_server_push_enabled(value);
+}
+
+void QuicSpdySession::set_max_uncompressed_header_bytes(
+    size_t set_max_uncompressed_header_bytes) {
+  spdy_framer_visitor_->set_max_uncompressed_header_bytes(
+      set_max_uncompressed_header_bytes);
+}
+
+void QuicSpdySession::CloseConnectionWithDetails(QuicErrorCode error,
+                                                 const QuicString& details) {
+  connection()->CloseConnection(
+      error, details, ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
new file mode 100644
index 0000000..00f241e
--- /dev/null
+++ b/quic/core/http/quic_spdy_session.h
@@ -0,0 +1,283 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SESSION_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SESSION_H_
+
+#include <cstddef>
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_headers_stream.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_decoder_stream_sender.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder.h"
+#include "net/third_party/quiche/src/quic/core/qpack/qpack_encoder_stream_sender.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.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"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+
+namespace quic {
+
+namespace test {
+class QuicSpdySessionPeer;
+}  // namespace test
+
+// QuicHpackDebugVisitor gathers data used for understanding HPACK HoL
+// dynamics.  Specifically, it is to help predict the compression
+// penalty of avoiding HoL by chagning how the dynamic table is used.
+// In chromium, the concrete instance populates an UMA
+// histogram with the data.
+class QUIC_EXPORT_PRIVATE QuicHpackDebugVisitor {
+ public:
+  QuicHpackDebugVisitor();
+  QuicHpackDebugVisitor(const QuicHpackDebugVisitor&) = delete;
+  QuicHpackDebugVisitor& operator=(const QuicHpackDebugVisitor&) = delete;
+
+  virtual ~QuicHpackDebugVisitor();
+
+  // For each HPACK indexed representation processed, |elapsed| is
+  // the time since the corresponding entry was added to the dynamic
+  // table.
+  virtual void OnUseEntry(QuicTime::Delta elapsed) = 0;
+};
+
+// A QUIC session with a headers stream.
+class QUIC_EXPORT_PRIVATE QuicSpdySession
+    : public QuicSession,
+      public QpackEncoder::DecoderStreamErrorDelegate,
+      public QpackEncoderStreamSender::Delegate,
+      public QpackDecoder::EncoderStreamErrorDelegate,
+      public QpackDecoderStreamSender::Delegate {
+ public:
+  // Does not take ownership of |connection| or |visitor|.
+  QuicSpdySession(QuicConnection* connection,
+                  QuicSession::Visitor* visitor,
+                  const QuicConfig& config,
+                  const ParsedQuicVersionVector& supported_versions);
+  QuicSpdySession(const QuicSpdySession&) = delete;
+  QuicSpdySession& operator=(const QuicSpdySession&) = delete;
+
+  ~QuicSpdySession() override;
+
+  void Initialize() override;
+
+  // QpackEncoder::DecoderStreamErrorDelegate implementation.
+  void OnDecoderStreamError(QuicStringPiece error_message) override;
+
+  // QpackEncoderStreamSender::Delegate implemenation.
+  void WriteEncoderStreamData(QuicStringPiece data) override;
+
+  // QpackDecoder::EncoderStreamErrorDelegate implementation.
+  void OnEncoderStreamError(QuicStringPiece error_message) override;
+
+  // QpackDecoderStreamSender::Delegate implementation.
+  void WriteDecoderStreamData(QuicStringPiece data) override;
+
+  // Called by |headers_stream_| when headers with a priority have been
+  // received for a stream.  This method will only be called for server streams.
+  virtual void OnStreamHeadersPriority(QuicStreamId stream_id,
+                                       spdy::SpdyPriority priority);
+
+  // Called by |headers_stream_| when headers have been completely received
+  // for a stream.  |fin| will be true if the fin flag was set in the headers
+  // frame.
+  virtual void OnStreamHeaderList(QuicStreamId stream_id,
+                                  bool fin,
+                                  size_t frame_len,
+                                  const QuicHeaderList& header_list);
+
+  // Called by |headers_stream_| when push promise headers have been
+  // completely received.  |fin| will be true if the fin flag was set
+  // in the headers.
+  virtual void OnPromiseHeaderList(QuicStreamId stream_id,
+                                   QuicStreamId promised_stream_id,
+                                   size_t frame_len,
+                                   const QuicHeaderList& header_list);
+
+  // Called by |headers_stream_| when a PRIORITY frame has been received for a
+  // stream. This method will only be called for server streams.
+  virtual void OnPriorityFrame(QuicStreamId stream_id,
+                               spdy::SpdyPriority priority);
+
+  // Sends contents of |iov| to h2_deframer_, returns number of bytes processed.
+  size_t ProcessHeaderData(const struct iovec& iov);
+
+  // Writes |headers| for the stream |id| to the dedicated headers stream.
+  // If |fin| is true, then no more data will be sent for the stream |id|.
+  // If provided, |ack_notifier_delegate| will be registered to be notified when
+  // we have seen ACKs for all packets resulting from this call.
+  virtual size_t WriteHeadersOnHeadersStream(
+      QuicStreamId id,
+      spdy::SpdyHeaderBlock headers,
+      bool fin,
+      spdy::SpdyPriority priority,
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+  // Writes a PRIORITY frame the to peer. Returns the size in bytes of the
+  // resulting PRIORITY frame for QUIC_VERSION_43 and above. Otherwise, does
+  // nothing and returns 0.
+  size_t WritePriority(QuicStreamId id,
+                       QuicStreamId parent_stream_id,
+                       int weight,
+                       bool exclusive);
+
+  // Write |headers| for |promised_stream_id| on |original_stream_id| in a
+  // PUSH_PROMISE frame to peer.
+  // Return the size, in bytes, of the resulting PUSH_PROMISE frame.
+  virtual size_t WritePushPromise(QuicStreamId original_stream_id,
+                                  QuicStreamId promised_stream_id,
+                                  spdy::SpdyHeaderBlock headers);
+
+  // Sends SETTINGS_MAX_HEADER_LIST_SIZE SETTINGS frame.
+  size_t SendMaxHeaderListSize(size_t value);
+
+  QpackEncoder* qpack_encoder();
+  QpackDecoder* qpack_decoder();
+  QuicHeadersStream* headers_stream() { return headers_stream_.get(); }
+
+  bool server_push_enabled() const { return server_push_enabled_; }
+
+  // Called by |QuicHeadersStream::UpdateEnableServerPush()| with
+  // value from SETTINGS_ENABLE_PUSH.
+  void set_server_push_enabled(bool enable) { server_push_enabled_ = enable; }
+
+  // Return true if this session wants to release headers stream's buffer
+  // aggressively.
+  virtual bool ShouldReleaseHeadersStreamSequencerBuffer();
+
+  void CloseConnectionWithDetails(QuicErrorCode error,
+                                  const QuicString& details);
+
+  void set_max_inbound_header_list_size(size_t max_inbound_header_list_size) {
+    max_inbound_header_list_size_ = max_inbound_header_list_size;
+  }
+
+ protected:
+  // Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and
+  // CreateOutgoingUnidirectionalStream() with QuicSpdyStream return type to
+  // make sure that all data streams are QuicSpdyStreams.
+  QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override = 0;
+  QuicSpdyStream* CreateIncomingStream(PendingStream pending) override = 0;
+  virtual QuicSpdyStream* CreateOutgoingBidirectionalStream() = 0;
+  virtual QuicSpdyStream* CreateOutgoingUnidirectionalStream() = 0;
+
+  QuicSpdyStream* GetSpdyDataStream(const QuicStreamId stream_id);
+
+  // If an incoming stream can be created, return true.
+  virtual bool ShouldCreateIncomingStream(QuicStreamId id) = 0;
+
+  // If an outgoing bidirectional/unidirectional stream can be created, return
+  // true.
+  virtual bool ShouldCreateOutgoingBidirectionalStream() = 0;
+  virtual bool ShouldCreateOutgoingUnidirectionalStream() = 0;
+
+  // Returns true if there are open HTTP requests.
+  bool ShouldKeepConnectionAlive() const override;
+
+  // Overridden to buffer incoming streams for version 99.
+  bool ShouldBufferIncomingStream(QuicStreamId id) const override;
+
+  size_t WriteHeadersOnHeadersStreamImpl(
+      QuicStreamId id,
+      spdy::SpdyHeaderBlock headers,
+      bool fin,
+      QuicStreamId parent_stream_id,
+      int weight,
+      bool exclusive,
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+  void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
+
+  bool supports_push_promise() { return supports_push_promise_; }
+
+  // Optional, enables instrumentation related to go/quic-hpack.
+  void SetHpackEncoderDebugVisitor(
+      std::unique_ptr<QuicHpackDebugVisitor> visitor);
+  void SetHpackDecoderDebugVisitor(
+      std::unique_ptr<QuicHpackDebugVisitor> visitor);
+
+  // Sets the maximum size of the header compression table spdy_framer_ is
+  // willing to use to encode header blocks.
+  void UpdateHeaderEncoderTableSize(uint32_t value);
+
+  // Called when SETTINGS_ENABLE_PUSH is received, only supported on
+  // server side.
+  void UpdateEnableServerPush(bool value);
+
+  bool IsConnected() { return connection()->connected(); }
+
+  // Sets how much encoded data the hpack decoder of h2_deframer_ is willing to
+  // buffer.
+  void set_max_decode_buffer_size_bytes(size_t max_decode_buffer_size_bytes) {
+    h2_deframer_.GetHpackDecoder()->set_max_decode_buffer_size_bytes(
+        max_decode_buffer_size_bytes);
+  }
+
+  void set_max_uncompressed_header_bytes(
+      size_t set_max_uncompressed_header_bytes);
+
+ private:
+  friend class test::QuicSpdySessionPeer;
+
+  class SpdyFramerVisitor;
+
+  // The following methods are called by the SimpleVisitor.
+
+  // Called when a HEADERS frame has been received.
+  void OnHeaders(spdy::SpdyStreamId stream_id,
+                 bool has_priority,
+                 spdy::SpdyPriority priority,
+                 bool fin);
+
+  // Called when a PUSH_PROMISE frame has been received.
+  void OnPushPromise(spdy::SpdyStreamId stream_id,
+                     spdy::SpdyStreamId promised_stream_id,
+                     bool end);
+
+  // Called when a PRIORITY frame has been received.
+  void OnPriority(spdy::SpdyStreamId stream_id, spdy::SpdyPriority priority);
+
+  // Called when the complete list of headers is available.
+  void OnHeaderList(const QuicHeaderList& header_list);
+
+  // Called when the size of the compressed frame payload is available.
+  void OnCompressedFrameSize(size_t frame_len);
+
+  std::unique_ptr<QpackEncoder> qpack_encoder_;
+  std::unique_ptr<QpackDecoder> qpack_decoder_;
+
+  // TODO(123528590): Remove this member.
+  std::unique_ptr<QuicHeadersStream> headers_stream_;
+
+  // The maximum size of a header block that will be accepted from the peer,
+  // defined per spec as key + value + overhead per field (uncompressed).
+  size_t max_inbound_header_list_size_;
+
+  // Set during handshake. If true, resources in x-associated-content and link
+  // headers will be pushed.
+  bool server_push_enabled_;
+
+  // Data about the stream whose headers are being processed.
+  QuicStreamId stream_id_;
+  QuicStreamId promised_stream_id_;
+  bool fin_;
+  size_t frame_len_;
+  size_t uncompressed_frame_len_;
+
+  bool supports_push_promise_;
+
+  spdy::SpdyFramer spdy_framer_;
+  http2::Http2DecoderAdapter h2_deframer_;
+  std::unique_ptr<SpdyFramerVisitor> spdy_framer_visitor_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SESSION_H_
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
new file mode 100644
index 0000000..1c53472
--- /dev/null
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -0,0 +1,1803 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+
+#include <cstdint>
+#include <set>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.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_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_map_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.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"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_send_buffer_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+using spdy::kV3HighestPriority;
+using spdy::Spdy3PriorityToHttp2Weight;
+using spdy::SpdyFramer;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyPriority;
+using spdy::SpdyPriorityIR;
+using spdy::SpdySerializedFrame;
+using testing::_;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+class TestCryptoStream : public QuicCryptoStream, public QuicCryptoHandshaker {
+ public:
+  explicit TestCryptoStream(QuicSession* session)
+      : QuicCryptoStream(session),
+        QuicCryptoHandshaker(this, session),
+        encryption_established_(false),
+        handshake_confirmed_(false),
+        params_(new QuicCryptoNegotiatedParameters) {}
+
+  void OnHandshakeMessage(const CryptoHandshakeMessage& /*message*/) override {
+    encryption_established_ = true;
+    handshake_confirmed_ = true;
+    CryptoHandshakeMessage msg;
+    QuicString error_details;
+    session()->config()->SetInitialStreamFlowControlWindowToSend(
+        kInitialStreamFlowControlWindowForTest);
+    session()->config()->SetInitialSessionFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    session()->config()->ToHandshakeMessage(&msg);
+    const QuicErrorCode error =
+        session()->config()->ProcessPeerHello(msg, CLIENT, &error_details);
+    EXPECT_EQ(QUIC_NO_ERROR, error);
+    session()->OnConfigNegotiated();
+    session()->connection()->SetDefaultEncryptionLevel(
+        ENCRYPTION_FORWARD_SECURE);
+    session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+  }
+
+  // QuicCryptoStream implementation
+  bool encryption_established() const override {
+    return encryption_established_;
+  }
+  bool handshake_confirmed() const override { return handshake_confirmed_; }
+  const QuicCryptoNegotiatedParameters& crypto_negotiated_params()
+      const override {
+    return *params_;
+  }
+  CryptoMessageParser* crypto_message_parser() override {
+    return QuicCryptoHandshaker::crypto_message_parser();
+  }
+
+  MOCK_METHOD0(OnCanWrite, void());
+
+  bool HasPendingCryptoRetransmission() override { return false; }
+
+  MOCK_CONST_METHOD0(HasPendingRetransmission, bool());
+
+ private:
+  using QuicCryptoStream::session;
+
+  bool encryption_established_;
+  bool handshake_confirmed_;
+  QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;
+};
+
+class TestHeadersStream : public QuicHeadersStream {
+ public:
+  explicit TestHeadersStream(QuicSpdySession* session)
+      : QuicHeadersStream(session) {}
+
+  MOCK_METHOD0(OnCanWrite, void());
+};
+
+class TestStream : public QuicSpdyStream {
+ public:
+  TestStream(QuicStreamId id, QuicSpdySession* session, StreamType type)
+      : QuicSpdyStream(id, session, type) {}
+
+  TestStream(PendingStream pending, QuicSpdySession* session, StreamType type)
+      : QuicSpdyStream(std::move(pending), session, type) {}
+
+  using QuicStream::CloseWriteSide;
+
+  void OnBodyAvailable() override {}
+
+  MOCK_METHOD0(OnCanWrite, void());
+  MOCK_METHOD3(RetransmitStreamData,
+               bool(QuicStreamOffset, QuicByteCount, bool));
+
+  MOCK_CONST_METHOD0(HasPendingRetransmission, bool());
+};
+
+class TestSession : public QuicSpdySession {
+ public:
+  explicit TestSession(QuicConnection* connection)
+      : QuicSpdySession(connection,
+                        nullptr,
+                        DefaultQuicConfig(),
+                        CurrentSupportedVersions()),
+        crypto_stream_(this),
+        writev_consumes_all_data_(false) {
+    Initialize();
+    this->connection()->SetEncrypter(
+        ENCRYPTION_FORWARD_SECURE,
+        QuicMakeUnique<NullEncrypter>(connection->perspective()));
+  }
+
+  ~TestSession() override { delete connection(); }
+
+  TestCryptoStream* GetMutableCryptoStream() override {
+    return &crypto_stream_;
+  }
+
+  const TestCryptoStream* GetCryptoStream() const override {
+    return &crypto_stream_;
+  }
+
+  TestStream* CreateOutgoingBidirectionalStream() override {
+    TestStream* stream = new TestStream(GetNextOutgoingBidirectionalStreamId(),
+                                        this, BIDIRECTIONAL);
+    ActivateStream(QuicWrapUnique(stream));
+    return stream;
+  }
+
+  TestStream* CreateOutgoingUnidirectionalStream() override {
+    TestStream* stream = new TestStream(GetNextOutgoingUnidirectionalStreamId(),
+                                        this, WRITE_UNIDIRECTIONAL);
+    ActivateStream(QuicWrapUnique(stream));
+    return stream;
+  }
+
+  TestStream* CreateIncomingStream(QuicStreamId id) override {
+    // Enforce the limit on the number of open streams.
+    if (GetNumOpenIncomingStreams() + 1 >
+            max_open_incoming_bidirectional_streams() &&
+        connection()->transport_version() != QUIC_VERSION_99) {
+      connection()->CloseConnection(
+          QUIC_TOO_MANY_OPEN_STREAMS, "Too many streams!",
+          ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+      return nullptr;
+    } else {
+      TestStream* stream = new TestStream(
+          id, this,
+          DetermineStreamType(id, connection()->transport_version(),
+                              perspective(), /*is_incoming=*/true,
+                              BIDIRECTIONAL));
+      ActivateStream(QuicWrapUnique(stream));
+      return stream;
+    }
+  }
+
+  TestStream* CreateIncomingStream(PendingStream pending) override {
+    QuicStreamId id = pending.id();
+    TestStream* stream =
+        new TestStream(std::move(pending), this,
+                       DetermineStreamType(
+                           id, connection()->transport_version(), perspective(),
+                           /*is_incoming=*/true, BIDIRECTIONAL));
+    ActivateStream(QuicWrapUnique(stream));
+    return stream;
+  }
+
+  bool ShouldCreateIncomingStream(QuicStreamId /*id*/) override { return true; }
+
+  bool ShouldCreateOutgoingBidirectionalStream() override { return true; }
+  bool ShouldCreateOutgoingUnidirectionalStream() override { return true; }
+
+  bool IsClosedStream(QuicStreamId id) {
+    return QuicSession::IsClosedStream(id);
+  }
+
+  QuicStream* GetOrCreateDynamicStream(QuicStreamId stream_id) {
+    return QuicSpdySession::GetOrCreateDynamicStream(stream_id);
+  }
+
+  QuicConsumedData WritevData(QuicStream* stream,
+                              QuicStreamId id,
+                              size_t write_length,
+                              QuicStreamOffset offset,
+                              StreamSendingState state) override {
+    bool fin = state != NO_FIN;
+    QuicConsumedData consumed(write_length, fin);
+    if (!writev_consumes_all_data_) {
+      consumed =
+          QuicSession::WritevData(stream, id, write_length, offset, state);
+    }
+    if (fin && consumed.fin_consumed) {
+      stream->set_fin_sent(true);
+    }
+    QuicSessionPeer::GetWriteBlockedStreams(this)->UpdateBytesForStream(
+        id, consumed.bytes_consumed);
+    return consumed;
+  }
+
+  void set_writev_consumes_all_data(bool val) {
+    writev_consumes_all_data_ = val;
+  }
+
+  QuicConsumedData SendStreamData(QuicStream* stream) {
+    struct iovec iov;
+    if (stream->id() !=
+            QuicUtils::GetCryptoStreamId(connection()->transport_version()) &&
+        connection()->encryption_level() != ENCRYPTION_FORWARD_SECURE) {
+      this->connection()->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+    }
+    MakeIOVector("not empty", &iov);
+    QuicStreamPeer::SendBuffer(stream).SaveStreamData(&iov, 1, 0, 9);
+    QuicConsumedData consumed = WritevData(stream, stream->id(), 9, 0, FIN);
+    QuicStreamPeer::SendBuffer(stream).OnStreamDataConsumed(
+        consumed.bytes_consumed);
+    return consumed;
+  }
+
+  bool ClearControlFrame(const QuicFrame& frame) {
+    DeleteFrame(&const_cast<QuicFrame&>(frame));
+    return true;
+  }
+
+  QuicConsumedData SendLargeFakeData(QuicStream* stream, int bytes) {
+    DCHECK(writev_consumes_all_data_);
+    return WritevData(stream, stream->id(), bytes, 0, FIN);
+  }
+
+  using QuicSession::closed_streams;
+  using QuicSession::zombie_streams;
+  using QuicSpdySession::ShouldBufferIncomingStream;
+
+ private:
+  StrictMock<TestCryptoStream> crypto_stream_;
+
+  bool writev_consumes_all_data_;
+};
+
+class QuicSpdySessionTestBase : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+  bool ClearMaxStreamIdControlFrame(const QuicFrame& frame) {
+    if (frame.type == MAX_STREAM_ID_FRAME) {
+      DeleteFrame(&const_cast<QuicFrame&>(frame));
+      return true;
+    }
+    return false;
+  }
+
+ protected:
+  explicit QuicSpdySessionTestBase(Perspective perspective)
+      : connection_(
+            new StrictMock<MockQuicConnection>(&helper_,
+                                               &alarm_factory_,
+                                               perspective,
+                                               SupportedVersions(GetParam()))),
+        session_(connection_) {
+    session_.config()->SetInitialStreamFlowControlWindowToSend(
+        kInitialStreamFlowControlWindowForTest);
+    session_.config()->SetInitialSessionFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    headers_[":host"] = "www.google.com";
+    headers_[":path"] = "/index.hml";
+    headers_[":scheme"] = "http";
+    headers_["cookie"] =
+        "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+        "__utmc=160408618; "
+        "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+        "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+        "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+        "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+        "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+        "1zFMi5vzcns38-8_Sns; "
+        "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+        "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+        "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+        "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+        "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+        "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+        "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+        "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+        "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+        "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+        "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+        "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+        "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+        "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+        "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
+    connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+    EXPECT_CALL(*crypto_stream, HasPendingRetransmission())
+        .Times(testing::AnyNumber());
+  }
+
+  void CheckClosedStreams() {
+    for (QuicStreamId i =
+             QuicUtils::GetCryptoStreamId(connection_->transport_version());
+         i < 100; i++) {
+      if (!QuicContainsKey(closed_streams_, i)) {
+        EXPECT_FALSE(session_.IsClosedStream(i)) << " stream id: " << i;
+      } else {
+        EXPECT_TRUE(session_.IsClosedStream(i)) << " stream id: " << i;
+      }
+    }
+  }
+
+  void CloseStream(QuicStreamId id) {
+    if (!IsVersion99()) {
+      EXPECT_CALL(*connection_, SendControlFrame(_))
+          .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+    } else {
+      // V99 has two frames, RST_STREAM and STOP_SENDING
+      EXPECT_CALL(*connection_, SendControlFrame(_))
+          .Times(2)
+          .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+    }
+    EXPECT_CALL(*connection_, OnStreamReset(id, _));
+    session_.CloseStream(id);
+    closed_streams_.insert(id);
+  }
+
+  QuicTransportVersion transport_version() const {
+    return connection_->transport_version();
+  }
+
+  bool IsVersion99() const { return transport_version() == QUIC_VERSION_99; }
+
+  QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+    return GetNthClientInitiatedBidirectionalStreamId(transport_version(), n);
+  }
+
+  QuicStreamId GetNthServerInitiatedBidirectionalId(int n) {
+    return GetNthServerInitiatedBidirectionalStreamId(
+        connection_->transport_version(), n);
+  }
+
+  QuicStreamId IdDelta() {
+    return QuicUtils::StreamIdDelta(connection_->transport_version());
+  }
+
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnection>* connection_;
+  TestSession session_;
+  std::set<QuicStreamId> closed_streams_;
+  SpdyHeaderBlock headers_;
+};
+
+class QuicSpdySessionTestServer : public QuicSpdySessionTestBase {
+ protected:
+  QuicSpdySessionTestServer()
+      : QuicSpdySessionTestBase(Perspective::IS_SERVER) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+                         QuicSpdySessionTestServer,
+                         ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdySessionTestServer, ShouldBufferIncomingStreamUnidirectional) {
+  if (!IsVersion99()) {
+    return;
+  }
+  EXPECT_TRUE(session_.ShouldBufferIncomingStream(
+      QuicUtils::GetFirstUnidirectionalStreamId(
+          connection_->transport_version(), Perspective::IS_CLIENT)));
+}
+
+TEST_P(QuicSpdySessionTestServer, ShouldBufferIncomingStreamBidirectional) {
+  if (!IsVersion99()) {
+    return;
+  }
+  EXPECT_FALSE(session_.ShouldBufferIncomingStream(
+      QuicUtils::GetFirstBidirectionalStreamId(connection_->transport_version(),
+                                               Perspective::IS_CLIENT)));
+}
+
+TEST_P(QuicSpdySessionTestServer, PeerAddress) {
+  EXPECT_EQ(QuicSocketAddress(QuicIpAddress::Loopback4(), kTestPort),
+            session_.peer_address());
+}
+
+TEST_P(QuicSpdySessionTestServer, SelfAddress) {
+  EXPECT_TRUE(session_.self_address().IsInitialized());
+}
+
+TEST_P(QuicSpdySessionTestServer, IsCryptoHandshakeConfirmed) {
+  EXPECT_FALSE(session_.IsCryptoHandshakeConfirmed());
+  CryptoHandshakeMessage message;
+  session_.GetMutableCryptoStream()->OnHandshakeMessage(message);
+  EXPECT_TRUE(session_.IsCryptoHandshakeConfirmed());
+}
+
+TEST_P(QuicSpdySessionTestServer, IsClosedStreamDefault) {
+  // Ensure that no streams are initially closed.
+  for (QuicStreamId i =
+           QuicUtils::GetCryptoStreamId(connection_->transport_version());
+       i < 100; i++) {
+    EXPECT_FALSE(session_.IsClosedStream(i)) << "stream id: " << i;
+  }
+}
+
+TEST_P(QuicSpdySessionTestServer, AvailableStreams) {
+  ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+                  GetNthClientInitiatedBidirectionalId(2)) != nullptr);
+  // Both client initiated streams with smaller stream IDs are available.
+  EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(
+      &session_, GetNthClientInitiatedBidirectionalId(0)));
+  EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(
+      &session_, GetNthClientInitiatedBidirectionalId(1)));
+  ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+                  GetNthClientInitiatedBidirectionalId(1)) != nullptr);
+  ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+                  GetNthClientInitiatedBidirectionalId(0)) != nullptr);
+}
+
+TEST_P(QuicSpdySessionTestServer, IsClosedStreamLocallyCreated) {
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  EXPECT_EQ(GetNthServerInitiatedBidirectionalId(0), stream2->id());
+  QuicSpdyStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+  EXPECT_EQ(GetNthServerInitiatedBidirectionalId(1), stream4->id());
+
+  CheckClosedStreams();
+  CloseStream(GetNthServerInitiatedBidirectionalId(0));
+  CheckClosedStreams();
+  CloseStream(GetNthServerInitiatedBidirectionalId(1));
+  CheckClosedStreams();
+}
+
+TEST_P(QuicSpdySessionTestServer, IsClosedStreamPeerCreated) {
+  QuicStreamId stream_id1 = GetNthClientInitiatedBidirectionalId(0);
+  QuicStreamId stream_id2 = GetNthClientInitiatedBidirectionalId(1);
+  session_.GetOrCreateDynamicStream(stream_id1);
+  session_.GetOrCreateDynamicStream(stream_id2);
+
+  CheckClosedStreams();
+  CloseStream(stream_id1);
+  CheckClosedStreams();
+  CloseStream(stream_id2);
+  // Create a stream, and make another available.
+  QuicStream* stream3 = session_.GetOrCreateDynamicStream(stream_id2 + 4);
+  CheckClosedStreams();
+  // Close one, but make sure the other is still not closed
+  CloseStream(stream3->id());
+  CheckClosedStreams();
+}
+
+TEST_P(QuicSpdySessionTestServer, MaximumAvailableOpenedStreams) {
+  if (IsVersion99()) {
+    // For IETF QUIC, we should be able to obtain the max allowed
+    // stream ID, the next ID should fail. Since the actual limit
+    // is not the number of open streams, we allocate the max and the max+2.
+    // Get the max allowed stream ID, this should succeed.
+    EXPECT_NE(nullptr,
+              session_.GetOrCreateDynamicStream(
+                  QuicSessionPeer::v99_streamid_manager(
+                      dynamic_cast<QuicSession*>(&session_))
+                      ->actual_max_allowed_incoming_bidirectional_stream_id()));
+    EXPECT_NE(
+        nullptr,
+        session_.GetOrCreateDynamicStream(
+            QuicSessionPeer::v99_streamid_manager(
+                dynamic_cast<QuicSession*>(&session_))
+                ->actual_max_allowed_incoming_unidirectional_stream_id()));
+    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+    // Get the (max allowed stream ID)++, this should fail.
+    EXPECT_EQ(nullptr,
+              session_.GetOrCreateDynamicStream(
+                  QuicSessionPeer::v99_streamid_manager(
+                      dynamic_cast<QuicSession*>(&session_))
+                      ->actual_max_allowed_incoming_bidirectional_stream_id() +
+                  IdDelta()));
+    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
+    EXPECT_EQ(nullptr,
+              session_.GetOrCreateDynamicStream(
+                  QuicSessionPeer::v99_streamid_manager(
+                      dynamic_cast<QuicSession*>(&session_))
+                      ->actual_max_allowed_incoming_unidirectional_stream_id() +
+                  IdDelta()));
+  } else {
+    QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+    session_.GetOrCreateDynamicStream(stream_id);
+    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+    EXPECT_NE(
+        nullptr,
+        session_.GetOrCreateDynamicStream(
+            stream_id +
+            IdDelta() *
+                (session_.max_open_incoming_bidirectional_streams() - 1)));
+  }
+}
+
+TEST_P(QuicSpdySessionTestServer, TooManyAvailableStreams) {
+  QuicStreamId stream_id1 = GetNthClientInitiatedBidirectionalId(0);
+  QuicStreamId stream_id2;
+  EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id1));
+  // A stream ID which is too large to create.
+  stream_id2 = GetNthClientInitiatedBidirectionalId(
+      2 * session_.MaxAvailableBidirectionalStreams() + 4);
+  if (IsVersion99()) {
+    EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+  } else {
+    EXPECT_CALL(*connection_,
+                CloseConnection(QUIC_TOO_MANY_AVAILABLE_STREAMS, _, _));
+  }
+  EXPECT_EQ(nullptr, session_.GetOrCreateDynamicStream(stream_id2));
+}
+
+TEST_P(QuicSpdySessionTestServer, ManyAvailableStreams) {
+  // When max_open_streams_ is 200, should be able to create 200 streams
+  // out-of-order, that is, creating the one with the largest stream ID first.
+  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, 200);
+  QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+  // Create one stream.
+  session_.GetOrCreateDynamicStream(stream_id);
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
+  // Stream count is 200, GetNth... starts counting at 0, so the 200'th stream
+  // is 199.
+  EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(
+                         GetNthClientInitiatedBidirectionalId(199)));
+}
+
+TEST_P(QuicSpdySessionTestServer,
+       DebugDFatalIfMarkingClosedStreamWriteBlocked) {
+  // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+  if (GetParam() != AllSupportedVersions()[0]) {
+    return;
+  }
+
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  QuicStreamId closed_stream_id = stream2->id();
+  // Close the stream.
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(closed_stream_id, _));
+  stream2->Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+  QuicString msg =
+      QuicStrCat("Marking unknown stream ", closed_stream_id, " blocked.");
+  EXPECT_QUIC_BUG(session_.MarkConnectionLevelWriteBlocked(closed_stream_id),
+                  msg);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWrite) {
+  session_.set_writev_consumes_all_data(true);
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+  session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  session_.MarkConnectionLevelWriteBlocked(stream6->id());
+  session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+  InSequence s;
+
+  // Reregister, to test the loop limit.
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendStreamData(stream2);
+    session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  }));
+  // 2 will get called a second time as it didn't finish its block
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendStreamData(stream2);
+  }));
+  EXPECT_CALL(*stream6, OnCanWrite()).WillOnce(Invoke([this, stream6]() {
+    session_.SendStreamData(stream6);
+  }));
+  // 4 will not get called, as we exceeded the loop limit.
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, TestBatchedWrites) {
+  session_.set_writev_consumes_all_data(true);
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+  session_.set_writev_consumes_all_data(true);
+  session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+  // With two sessions blocked, we should get two write calls.  They should both
+  // go to the first stream as it will only write 6k and mark itself blocked
+  // again.
+  InSequence s;
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendLargeFakeData(stream2, 6000);
+    session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  }));
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendLargeFakeData(stream2, 6000);
+    session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  }));
+  session_.OnCanWrite();
+
+  // We should get one more call for stream2, at which point it has used its
+  // write quota and we move over to stream 4.
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendLargeFakeData(stream2, 6000);
+    session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  }));
+  EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+    session_.SendLargeFakeData(stream4, 6000);
+    session_.MarkConnectionLevelWriteBlocked(stream4->id());
+  }));
+  session_.OnCanWrite();
+
+  // Now let stream 4 do the 2nd of its 3 writes, but add a block for a high
+  // priority stream 6.  4 should be preempted.  6 will write but *not* block so
+  // will cede back to 4.
+  stream6->SetPriority(kV3HighestPriority);
+  EXPECT_CALL(*stream4, OnCanWrite())
+      .WillOnce(Invoke([this, stream4, stream6]() {
+        session_.SendLargeFakeData(stream4, 6000);
+        session_.MarkConnectionLevelWriteBlocked(stream4->id());
+        session_.MarkConnectionLevelWriteBlocked(stream6->id());
+      }));
+  EXPECT_CALL(*stream6, OnCanWrite())
+      .WillOnce(Invoke([this, stream4, stream6]() {
+        session_.SendStreamData(stream6);
+        session_.SendLargeFakeData(stream4, 6000);
+      }));
+  session_.OnCanWrite();
+
+  // Stream4 alread did 6k worth of writes, so after doing another 12k it should
+  // cede and 2 should resume.
+  EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+    session_.SendLargeFakeData(stream4, 12000);
+    session_.MarkConnectionLevelWriteBlocked(stream4->id());
+  }));
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendLargeFakeData(stream2, 6000);
+    session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  }));
+  session_.OnCanWrite();
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWriteBundlesStreams) {
+  if (IsVersion99()) {
+    EXPECT_CALL(*connection_, SendControlFrame(_))
+        .WillRepeatedly(Invoke(
+            this, &QuicSpdySessionTestServer::ClearMaxStreamIdControlFrame));
+  }
+  // Encryption needs to be established before data can be sent.
+  CryptoHandshakeMessage msg;
+  MockPacketWriter* writer = static_cast<MockPacketWriter*>(
+      QuicConnectionPeer::GetWriter(session_.connection()));
+  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _))
+      .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
+  session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+
+  // Drive congestion control manually.
+  MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+  QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+  session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  session_.MarkConnectionLevelWriteBlocked(stream6->id());
+  session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(true));
+  EXPECT_CALL(*send_algorithm, GetCongestionWindow())
+      .WillRepeatedly(Return(kMaxPacketSize * 10));
+  EXPECT_CALL(*send_algorithm, InRecovery()).WillRepeatedly(Return(false));
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendStreamData(stream2);
+  }));
+  EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+    session_.SendStreamData(stream4);
+  }));
+  EXPECT_CALL(*stream6, OnCanWrite()).WillOnce(Invoke([this, stream6]() {
+    session_.SendStreamData(stream6);
+  }));
+
+  // Expect that we only send one packet, the writes from different streams
+  // should be bundled together.
+  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _))
+      .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
+  EXPECT_CALL(*send_algorithm, OnPacketSent(_, _, _, _, _));
+  EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+  session_.OnCanWrite();
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWriteCongestionControlBlocks) {
+  session_.set_writev_consumes_all_data(true);
+  InSequence s;
+
+  // Drive congestion control manually.
+  MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+  QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+  session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  session_.MarkConnectionLevelWriteBlocked(stream6->id());
+  session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendStreamData(stream2);
+  }));
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+  EXPECT_CALL(*stream6, OnCanWrite()).WillOnce(Invoke([this, stream6]() {
+    session_.SendStreamData(stream6);
+  }));
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(false));
+  // stream4->OnCanWrite is not called.
+
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+  // Still congestion-control blocked.
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(false));
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+  // stream4->OnCanWrite is called once the connection stops being
+  // congestion-control blocked.
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+  EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+    session_.SendStreamData(stream4);
+  }));
+  EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+  session_.OnCanWrite();
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWriteWriterBlocks) {
+  // Drive congestion control manually in order to ensure that
+  // application-limited signaling is handled correctly.
+  MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+  QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(true));
+
+  // Drive packet writer manually.
+  MockPacketWriter* writer = static_cast<MockPacketWriter*>(
+      QuicConnectionPeer::GetWriter(session_.connection()));
+  EXPECT_CALL(*writer, IsWriteBlocked()).WillRepeatedly(Return(true));
+  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _)).Times(0);
+
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+
+  session_.MarkConnectionLevelWriteBlocked(stream2->id());
+
+  EXPECT_CALL(*stream2, OnCanWrite()).Times(0);
+  EXPECT_CALL(*send_algorithm, OnApplicationLimited(_)).Times(0);
+
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, BufferedHandshake) {
+  session_.set_writev_consumes_all_data(true);
+  EXPECT_FALSE(session_.HasPendingHandshake());  // Default value.
+
+  // Test that blocking other streams does not change our status.
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  EXPECT_FALSE(session_.HasPendingHandshake());
+
+  TestStream* stream3 = session_.CreateOutgoingBidirectionalStream();
+  session_.MarkConnectionLevelWriteBlocked(stream3->id());
+  EXPECT_FALSE(session_.HasPendingHandshake());
+
+  // Blocking (due to buffering of) the Crypto stream is detected.
+  session_.MarkConnectionLevelWriteBlocked(
+      QuicUtils::GetCryptoStreamId(connection_->transport_version()));
+  EXPECT_TRUE(session_.HasPendingHandshake());
+
+  TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+  session_.MarkConnectionLevelWriteBlocked(stream4->id());
+  EXPECT_TRUE(session_.HasPendingHandshake());
+
+  InSequence s;
+  // Force most streams to re-register, which is common scenario when we block
+  // the Crypto stream, and only the crypto stream can "really" write.
+
+  // Due to prioritization, we *should* be asked to write the crypto stream
+  // first.
+  // Don't re-register the crypto stream (which signals complete writing).
+  TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+  EXPECT_CALL(*crypto_stream, OnCanWrite());
+
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendStreamData(stream2);
+  }));
+  EXPECT_CALL(*stream3, OnCanWrite()).WillOnce(Invoke([this, stream3]() {
+    session_.SendStreamData(stream3);
+  }));
+  EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+    session_.SendStreamData(stream4);
+    session_.MarkConnectionLevelWriteBlocked(stream4->id());
+  }));
+
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+  EXPECT_FALSE(session_.HasPendingHandshake());  // Crypto stream wrote.
+}
+
+TEST_P(QuicSpdySessionTestServer, OnCanWriteWithClosedStream) {
+  session_.set_writev_consumes_all_data(true);
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+  session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  session_.MarkConnectionLevelWriteBlocked(stream6->id());
+  session_.MarkConnectionLevelWriteBlocked(stream4->id());
+  CloseStream(stream6->id());
+
+  InSequence s;
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+  EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(Invoke([this, stream2]() {
+    session_.SendStreamData(stream2);
+  }));
+  EXPECT_CALL(*stream4, OnCanWrite()).WillOnce(Invoke([this, stream4]() {
+    session_.SendStreamData(stream4);
+  }));
+  session_.OnCanWrite();
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+       OnCanWriteLimitsNumWritesIfFlowControlBlocked) {
+  // Drive congestion control manually in order to ensure that
+  // application-limited signaling is handled correctly.
+  MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+  QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(true));
+
+  // Ensure connection level flow control blockage.
+  QuicFlowControllerPeer::SetSendWindowOffset(session_.flow_controller(), 0);
+  EXPECT_TRUE(session_.flow_controller()->IsBlocked());
+  EXPECT_TRUE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+
+  // Mark the crypto and headers streams as write blocked, we expect them to be
+  // allowed to write later.
+  session_.MarkConnectionLevelWriteBlocked(
+      QuicUtils::GetCryptoStreamId(connection_->transport_version()));
+
+  // Create a data stream, and although it is write blocked we never expect it
+  // to be allowed to write as we are connection level flow control blocked.
+  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+  session_.MarkConnectionLevelWriteBlocked(stream->id());
+  EXPECT_CALL(*stream, OnCanWrite()).Times(0);
+
+  // The crypto and headers streams should be called even though we are
+  // connection flow control blocked.
+  TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+  EXPECT_CALL(*crypto_stream, OnCanWrite());
+  QuicSpdySessionPeer::SetHeadersStream(&session_, nullptr);
+  TestHeadersStream* headers_stream = new TestHeadersStream(&session_);
+  QuicSpdySessionPeer::SetHeadersStream(&session_, headers_stream);
+  session_.MarkConnectionLevelWriteBlocked(
+      QuicUtils::GetHeadersStreamId(connection_->transport_version()));
+  EXPECT_CALL(*headers_stream, OnCanWrite());
+
+  // After the crypto and header streams perform a write, the connection will be
+  // blocked by the flow control, hence it should become application-limited.
+  EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+
+  session_.OnCanWrite();
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, SendGoAway) {
+  if (IsVersion99()) {
+    // GoAway frames are not in version 99
+    return;
+  }
+  MockPacketWriter* writer = static_cast<MockPacketWriter*>(
+      QuicConnectionPeer::GetWriter(session_.connection()));
+  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _))
+      .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
+
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .WillOnce(
+          Invoke(connection_, &MockQuicConnection::ReallySendControlFrame));
+  session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+  EXPECT_TRUE(session_.goaway_sent());
+
+  const QuicStreamId kTestStreamId = 5u;
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  EXPECT_CALL(*connection_,
+              OnStreamReset(kTestStreamId, QUIC_STREAM_PEER_GOING_AWAY))
+      .Times(0);
+  EXPECT_TRUE(session_.GetOrCreateDynamicStream(kTestStreamId));
+}
+
+TEST_P(QuicSpdySessionTestServer, DoNotSendGoAwayTwice) {
+  if (IsVersion99()) {
+    // TODO(b/118808809): Enable this test for version 99 when GOAWAY is
+    // supported.
+    return;
+  }
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+  session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+  EXPECT_TRUE(session_.goaway_sent());
+  session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+}
+
+TEST_P(QuicSpdySessionTestServer, InvalidGoAway) {
+  if (IsVersion99()) {
+    // TODO(b/118808809): Enable this test for version 99 when GOAWAY is
+    // supported.
+    return;
+  }
+  QuicGoAwayFrame go_away(kInvalidControlFrameId, QUIC_PEER_GOING_AWAY,
+                          session_.next_outgoing_bidirectional_stream_id(), "");
+  session_.OnGoAway(go_away);
+}
+
+// Test that server session will send a connectivity probe in response to a
+// connectivity probe on the same path.
+TEST_P(QuicSpdySessionTestServer, ServerReplyToConnecitivityProbe) {
+  QuicSocketAddress old_peer_address =
+      QuicSocketAddress(QuicIpAddress::Loopback4(), kTestPort);
+  EXPECT_EQ(old_peer_address, session_.peer_address());
+
+  QuicSocketAddress new_peer_address =
+      QuicSocketAddress(QuicIpAddress::Loopback4(), kTestPort + 1);
+
+  EXPECT_CALL(*connection_,
+              SendConnectivityProbingResponsePacket(new_peer_address));
+  if (IsVersion99()) {
+    // Need to explicitly do this to emulate the reception of a PathChallenge,
+    // which stores its payload for use in generating the response.
+    connection_->OnPathChallengeFrame(
+        QuicPathChallengeFrame(0, {{0, 1, 2, 3, 4, 5, 6, 7}}));
+  }
+  session_.OnConnectivityProbeReceived(session_.self_address(),
+                                       new_peer_address);
+  EXPECT_EQ(old_peer_address, session_.peer_address());
+}
+
+TEST_P(QuicSpdySessionTestServer, IncreasedTimeoutAfterCryptoHandshake) {
+  EXPECT_EQ(kInitialIdleTimeoutSecs + 3,
+            QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+  CryptoHandshakeMessage msg;
+  session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+  EXPECT_EQ(kMaximumIdleTimeoutSecs + 3,
+            QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+}
+
+TEST_P(QuicSpdySessionTestServer, RstStreamBeforeHeadersDecompressed) {
+  // Send two bytes of payload.
+  QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece("HT"));
+  session_.OnStreamFrame(data1);
+  EXPECT_EQ(1u, session_.GetNumOpenIncomingStreams());
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  if (!IsVersion99()) {
+    // For version99, OnStreamReset gets called because of the STOP_SENDING,
+    // below. EXPECT the call there.
+    EXPECT_CALL(*connection_,
+                OnStreamReset(GetNthClientInitiatedBidirectionalId(0), _));
+  }
+  QuicRstStreamFrame rst1(kInvalidControlFrameId,
+                          GetNthClientInitiatedBidirectionalId(0),
+                          QUIC_ERROR_PROCESSING_STREAM, 0);
+  session_.OnRstStream(rst1);
+
+  // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+  // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes a
+  // one-way close.
+  if (IsVersion99()) {
+    // Only needed for version 99/IETF QUIC.
+    QuicStopSendingFrame stop_sending(
+        kInvalidControlFrameId, GetNthClientInitiatedBidirectionalId(0),
+        static_cast<QuicApplicationErrorCode>(QUIC_ERROR_PROCESSING_STREAM));
+    // Expect the RESET_STREAM that is generated in response to receiving a
+    // STOP_SENDING.
+    EXPECT_CALL(*connection_,
+                OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+                              QUIC_ERROR_PROCESSING_STREAM));
+    session_.OnStopSendingFrame(stop_sending);
+  }
+
+  EXPECT_EQ(0u, session_.GetNumOpenIncomingStreams());
+  // Connection should remain alive.
+  EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnStreamFrameFinStaticStreamId) {
+  // Send two bytes of payload.
+  QuicStreamFrame data1(
+      QuicUtils::GetCryptoStreamId(connection_->transport_version()), true, 0,
+      QuicStringPiece("HT"));
+  EXPECT_CALL(*connection_,
+              CloseConnection(
+                  QUIC_INVALID_STREAM_ID, "Attempt to close a static stream",
+                  ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
+  session_.OnStreamFrame(data1);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnRstStreamStaticStreamId) {
+  // Send two bytes of payload.
+  QuicRstStreamFrame rst1(
+      kInvalidControlFrameId,
+      QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+      QUIC_ERROR_PROCESSING_STREAM, 0);
+  EXPECT_CALL(*connection_,
+              CloseConnection(
+                  QUIC_INVALID_STREAM_ID, "Attempt to reset a static stream",
+                  ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
+  session_.OnRstStream(rst1);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnStreamFrameInvalidStreamId) {
+  // Send two bytes of payload.
+  QuicStreamFrame data1(
+      QuicUtils::GetInvalidStreamId(connection_->transport_version()), true, 0,
+      QuicStringPiece("HT"));
+  EXPECT_CALL(*connection_,
+              CloseConnection(
+                  QUIC_INVALID_STREAM_ID, "Recevied data for an invalid stream",
+                  ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
+  session_.OnStreamFrame(data1);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnRstStreamInvalidStreamId) {
+  // Send two bytes of payload.
+  QuicRstStreamFrame rst1(
+      kInvalidControlFrameId,
+      QuicUtils::GetInvalidStreamId(connection_->transport_version()),
+      QUIC_ERROR_PROCESSING_STREAM, 0);
+  EXPECT_CALL(*connection_,
+              CloseConnection(
+                  QUIC_INVALID_STREAM_ID, "Recevied data for an invalid stream",
+                  ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
+  session_.OnRstStream(rst1);
+}
+
+TEST_P(QuicSpdySessionTestServer, HandshakeUnblocksFlowControlBlockedStream) {
+  // Test that if a stream is flow control blocked, then on receipt of the SHLO
+  // containing a suitable send window offset, the stream becomes unblocked.
+
+  // Ensure that Writev consumes all the data it is given (simulate no socket
+  // blocking).
+  session_.set_writev_consumes_all_data(true);
+
+  // Create a stream, and send enough data to make it flow control blocked.
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  QuicString body(kMinimumFlowControlSendWindow, '.');
+  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AtLeast(1));
+  stream2->WriteOrBufferBody(body, false);
+  EXPECT_TRUE(stream2->flow_controller()->IsBlocked());
+  EXPECT_TRUE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
+
+  // Now complete the crypto handshake, resulting in an increased flow control
+  // send window.
+  CryptoHandshakeMessage msg;
+  session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+  EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(&session_, stream2->id()));
+  // Stream is now unblocked.
+  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+       HandshakeUnblocksFlowControlBlockedCryptoStream) {
+  if (GetParam().transport_version >= QUIC_VERSION_47) {
+    // QUIC version 47 onwards uses CRYPTO frames for the handshake, so this
+    // test doesn't make sense for those versions.
+    return;
+  }
+  // Test that if the crypto stream is flow control blocked, then if the SHLO
+  // contains a larger send window offset, the stream becomes unblocked.
+  session_.set_writev_consumes_all_data(true);
+  TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+  QuicHeadersStream* headers_stream =
+      QuicSpdySessionPeer::GetHeadersStream(&session_);
+  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+  if (IsVersion99()) {
+    EXPECT_CALL(*connection_, SendControlFrame(_))
+        .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+  } else {
+    EXPECT_CALL(*connection_, SendControlFrame(_))
+        .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+  }
+  for (QuicStreamId i = 0;
+       !crypto_stream->flow_controller()->IsBlocked() && i < 1000u; i++) {
+    EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+    EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+    QuicStreamOffset offset = crypto_stream->stream_bytes_written();
+    QuicConfig config;
+    CryptoHandshakeMessage crypto_message;
+    config.ToHandshakeMessage(&crypto_message);
+    crypto_stream->SendHandshakeMessage(crypto_message);
+    char buf[1000];
+    QuicDataWriter writer(1000, buf, NETWORK_BYTE_ORDER);
+    crypto_stream->WriteStreamData(offset, crypto_message.size(), &writer);
+  }
+  EXPECT_TRUE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
+  EXPECT_FALSE(session_.HasDataToWrite());
+  EXPECT_TRUE(crypto_stream->HasBufferedData());
+
+  // Now complete the crypto handshake, resulting in an increased flow control
+  // send window.
+  CryptoHandshakeMessage msg;
+  session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+  EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(
+      &session_,
+      QuicUtils::GetCryptoStreamId(connection_->transport_version())));
+  // Stream is now unblocked and will no longer have buffered data.
+  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+}
+
+#if !defined(OS_IOS)
+// This test is failing flakily for iOS bots.
+// http://crbug.com/425050
+// NOTE: It's not possible to use the standard MAYBE_ convention to disable
+// this test on iOS because when this test gets instantiated it ends up with
+// various names that are dependent on the parameters passed.
+TEST_P(QuicSpdySessionTestServer,
+       HandshakeUnblocksFlowControlBlockedHeadersStream) {
+  // Test that if the header stream is flow control blocked, then if the SHLO
+  // contains a larger send window offset, the stream becomes unblocked.
+  session_.set_writev_consumes_all_data(true);
+  TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+  QuicHeadersStream* headers_stream =
+      QuicSpdySessionPeer::GetHeadersStream(&session_);
+  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+  QuicStreamId stream_id = 5;
+  // Write until the header stream is flow control blocked.
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+  SpdyHeaderBlock headers;
+  SimpleRandom random;
+  while (!headers_stream->flow_controller()->IsBlocked() && stream_id < 2000) {
+    EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+    EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+    headers["header"] = QuicStrCat(random.RandUint64(), random.RandUint64(),
+                                   random.RandUint64());
+    session_.WriteHeadersOnHeadersStream(stream_id, headers.Clone(), true, 0,
+                                         nullptr);
+    stream_id += IdDelta();
+  }
+  // Write once more to ensure that the headers stream has buffered data. The
+  // random headers may have exactly filled the flow control window.
+  session_.WriteHeadersOnHeadersStream(stream_id, std::move(headers), true, 0,
+                                       nullptr);
+  EXPECT_TRUE(headers_stream->HasBufferedData());
+
+  EXPECT_TRUE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
+  EXPECT_FALSE(session_.HasDataToWrite());
+
+  // Now complete the crypto handshake, resulting in an increased flow control
+  // send window.
+  CryptoHandshakeMessage msg;
+  session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+
+  // Stream is now unblocked and will no longer have buffered data.
+  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+  EXPECT_TRUE(headers_stream->HasBufferedData());
+  EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(
+      &session_,
+      QuicUtils::GetHeadersStreamId(connection_->transport_version())));
+}
+#endif  // !defined(OS_IOS)
+
+TEST_P(QuicSpdySessionTestServer,
+       ConnectionFlowControlAccountingRstOutOfOrder) {
+  // Test that when we receive an out of order stream RST we correctly adjust
+  // our connection level flow control receive window.
+  // On close, the stream should mark as consumed all bytes between the highest
+  // byte consumed so far and the final byte offset from the RST frame.
+  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+
+  const QuicStreamOffset kByteOffset =
+      1 + kInitialSessionFlowControlWindowForTest / 2;
+
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .Times(2)
+      .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+  if (!IsVersion99()) {
+    // For version99 the call to OnStreamReset happens as a result of receiving
+    // the STOP_SENDING, so set up the EXPECT there.
+    EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+  }
+  QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream->id(),
+                               QUIC_STREAM_CANCELLED, kByteOffset);
+  session_.OnRstStream(rst_frame);
+  // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+  // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes a
+  // one-way close.
+  if (IsVersion99()) {
+    // Only needed for version 99/IETF QUIC.
+    QuicStopSendingFrame stop_sending(
+        kInvalidControlFrameId, stream->id(),
+        static_cast<QuicApplicationErrorCode>(QUIC_STREAM_CANCELLED));
+    // Expect the RESET_STREAM that is generated in response to receiving a
+    // STOP_SENDING.
+    EXPECT_CALL(*connection_,
+                OnStreamReset(stream->id(), QUIC_STREAM_CANCELLED));
+    session_.OnStopSendingFrame(stop_sending);
+  }
+
+  EXPECT_EQ(kByteOffset, session_.flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+       ConnectionFlowControlAccountingFinAndLocalReset) {
+  // Test the situation where we receive a FIN on a stream, and before we fully
+  // consume all the data from the sequencer buffer we locally RST the stream.
+  // The bytes between highest consumed byte, and the final byte offset that we
+  // determined when the FIN arrived, should be marked as consumed at the
+  // connection level flow controller when the stream is reset.
+  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+
+  const QuicStreamOffset kByteOffset =
+      kInitialSessionFlowControlWindowForTest / 2 - 1;
+  QuicStreamFrame frame(stream->id(), true, kByteOffset, ".");
+  session_.OnStreamFrame(frame);
+  EXPECT_TRUE(connection_->connected());
+
+  EXPECT_EQ(0u, stream->flow_controller()->bytes_consumed());
+  EXPECT_EQ(kByteOffset + frame.data_length,
+            stream->flow_controller()->highest_received_byte_offset());
+
+  // Reset stream locally.
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+  stream->Reset(QUIC_STREAM_CANCELLED);
+  EXPECT_EQ(kByteOffset + frame.data_length,
+            session_.flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSpdySessionTestServer, ConnectionFlowControlAccountingFinAfterRst) {
+  // Test that when we RST the stream (and tear down stream state), and then
+  // receive a FIN from the peer, we correctly adjust our connection level flow
+  // control receive window.
+
+  // Connection starts with some non-zero highest received byte offset,
+  // due to other active streams.
+  const uint64_t kInitialConnectionBytesConsumed = 567;
+  const uint64_t kInitialConnectionHighestReceivedOffset = 1234;
+  EXPECT_LT(kInitialConnectionBytesConsumed,
+            kInitialConnectionHighestReceivedOffset);
+  session_.flow_controller()->UpdateHighestReceivedOffset(
+      kInitialConnectionHighestReceivedOffset);
+  session_.flow_controller()->AddBytesConsumed(kInitialConnectionBytesConsumed);
+
+  // Reset our stream: this results in the stream being closed locally.
+  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+  stream->Reset(QUIC_STREAM_CANCELLED);
+
+  // Now receive a response from the peer with a FIN. We should handle this by
+  // adjusting the connection level flow control receive window to take into
+  // account the total number of bytes sent by the peer.
+  const QuicStreamOffset kByteOffset = 5678;
+  QuicString body = "hello";
+  QuicStreamFrame frame(stream->id(), true, kByteOffset, QuicStringPiece(body));
+  session_.OnStreamFrame(frame);
+
+  QuicStreamOffset total_stream_bytes_sent_by_peer =
+      kByteOffset + body.length();
+  EXPECT_EQ(kInitialConnectionBytesConsumed + total_stream_bytes_sent_by_peer,
+            session_.flow_controller()->bytes_consumed());
+  EXPECT_EQ(
+      kInitialConnectionHighestReceivedOffset + total_stream_bytes_sent_by_peer,
+      session_.flow_controller()->highest_received_byte_offset());
+}
+
+TEST_P(QuicSpdySessionTestServer, ConnectionFlowControlAccountingRstAfterRst) {
+  // Test that when we RST the stream (and tear down stream state), and then
+  // receive a RST from the peer, we correctly adjust our connection level flow
+  // control receive window.
+
+  // Connection starts with some non-zero highest received byte offset,
+  // due to other active streams.
+  const uint64_t kInitialConnectionBytesConsumed = 567;
+  const uint64_t kInitialConnectionHighestReceivedOffset = 1234;
+  EXPECT_LT(kInitialConnectionBytesConsumed,
+            kInitialConnectionHighestReceivedOffset);
+  session_.flow_controller()->UpdateHighestReceivedOffset(
+      kInitialConnectionHighestReceivedOffset);
+  session_.flow_controller()->AddBytesConsumed(kInitialConnectionBytesConsumed);
+
+  // Reset our stream: this results in the stream being closed locally.
+  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+  stream->Reset(QUIC_STREAM_CANCELLED);
+  EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream));
+
+  // Now receive a RST from the peer. We should handle this by adjusting the
+  // connection level flow control receive window to take into account the total
+  // number of bytes sent by the peer.
+  const QuicStreamOffset kByteOffset = 5678;
+  QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream->id(),
+                               QUIC_STREAM_CANCELLED, kByteOffset);
+  session_.OnRstStream(rst_frame);
+
+  EXPECT_EQ(kInitialConnectionBytesConsumed + kByteOffset,
+            session_.flow_controller()->bytes_consumed());
+  EXPECT_EQ(kInitialConnectionHighestReceivedOffset + kByteOffset,
+            session_.flow_controller()->highest_received_byte_offset());
+}
+
+TEST_P(QuicSpdySessionTestServer, InvalidStreamFlowControlWindowInHandshake) {
+  // Test that receipt of an invalid (< default) stream flow control window from
+  // the peer results in the connection being torn down.
+  const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1;
+  QuicConfigPeer::SetReceivedInitialStreamFlowControlWindow(session_.config(),
+                                                            kInvalidWindow);
+
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
+  session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSpdySessionTestServer, InvalidSessionFlowControlWindowInHandshake) {
+  // Test that receipt of an invalid (< default) session flow control window
+  // from the peer results in the connection being torn down.
+  const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1;
+  QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(),
+                                                             kInvalidWindow);
+
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
+  session_.OnConfigNegotiated();
+}
+
+// Test negotiation of custom server initial flow control window.
+TEST_P(QuicSpdySessionTestServer, CustomFlowControlWindow) {
+  QuicTagVector copt;
+  copt.push_back(kIFW7);
+  QuicConfigPeer::SetReceivedConnectionOptions(session_.config(), copt);
+
+  session_.OnConfigNegotiated();
+  EXPECT_EQ(192 * 1024u, QuicFlowControllerPeer::ReceiveWindowSize(
+                             session_.flow_controller()));
+}
+
+TEST_P(QuicSpdySessionTestServer, FlowControlWithInvalidFinalOffset) {
+  // Test that if we receive a stream RST with a highest byte offset that
+  // violates flow control, that we close the connection.
+  const uint64_t kLargeOffset = kInitialSessionFlowControlWindowForTest + 1;
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA, _, _))
+      .Times(2);
+
+  // Check that stream frame + FIN results in connection close.
+  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+  stream->Reset(QUIC_STREAM_CANCELLED);
+  QuicStreamFrame frame(stream->id(), true, kLargeOffset, QuicStringPiece());
+  session_.OnStreamFrame(frame);
+
+  // Check that RST results in connection close.
+  QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream->id(),
+                               QUIC_STREAM_CANCELLED, kLargeOffset);
+  session_.OnRstStream(rst_frame);
+}
+
+TEST_P(QuicSpdySessionTestServer, WindowUpdateUnblocksHeadersStream) {
+  // Test that a flow control blocked headers stream gets unblocked on recipt of
+  // a WINDOW_UPDATE frame.
+
+  // Set the headers stream to be flow control blocked.
+  QuicHeadersStream* headers_stream =
+      QuicSpdySessionPeer::GetHeadersStream(&session_);
+  QuicFlowControllerPeer::SetSendWindowOffset(headers_stream->flow_controller(),
+                                              0);
+  EXPECT_TRUE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
+
+  // Unblock the headers stream by supplying a WINDOW_UPDATE.
+  QuicWindowUpdateFrame window_update_frame(kInvalidControlFrameId,
+                                            headers_stream->id(),
+                                            2 * kMinimumFlowControlSendWindow);
+  session_.OnWindowUpdateFrame(window_update_frame);
+  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+       TooManyUnfinishedStreamsCauseServerRejectStream) {
+  // If a buggy/malicious peer creates too many streams that are not ended
+  // with a FIN or RST then we send an RST to refuse streams for versions other
+  // than version 99. In version 99 the connection gets closed.
+  const QuicStreamId kMaxStreams = 5;
+  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  const QuicStreamId kFirstStreamId = GetNthClientInitiatedBidirectionalId(0);
+  const QuicStreamId kFinalStreamId =
+      GetNthClientInitiatedBidirectionalId(kMaxStreams);
+  // Create kMaxStreams data streams, and close them all without receiving a
+  // FIN or a RST_STREAM from the client.
+  const QuicStreamId kNextId =
+      QuicUtils::StreamIdDelta(connection_->transport_version());
+  for (QuicStreamId i = kFirstStreamId; i < kFinalStreamId; i += kNextId) {
+    QuicStreamFrame data1(i, false, 0, QuicStringPiece("HT"));
+    session_.OnStreamFrame(data1);
+    // EXPECT_EQ(1u, session_.GetNumOpenStreams());
+    if (!IsVersion99()) {
+      EXPECT_CALL(*connection_, SendControlFrame(_))
+          .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+    } else {
+      // V99 has two frames, RST_STREAM and STOP_SENDING
+      EXPECT_CALL(*connection_, SendControlFrame(_))
+          .Times(2)
+          .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+    }
+    // Close the stream only if not version 99. If we are version 99
+    // then closing the stream opens up the available stream id space,
+    // so we never bump into the limit.
+    EXPECT_CALL(*connection_, OnStreamReset(i, _));
+    session_.CloseStream(i);
+  }
+  // Try and open a stream that exceeds the limit.
+  if (!IsVersion99()) {
+    // On versions other than 99, opening such a stream results in a
+    // RST_STREAM.
+    EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+    EXPECT_CALL(*connection_,
+                OnStreamReset(kFinalStreamId, QUIC_REFUSED_STREAM))
+        .Times(1);
+  } else {
+    // On version 99 opening such a stream results in a connection close.
+    EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID,
+                                              "Stream id 28 above 24", _));
+  }
+  // Create one more data streams to exceed limit of open stream.
+  QuicStreamFrame data1(kFinalStreamId, false, 0, QuicStringPiece("HT"));
+  session_.OnStreamFrame(data1);
+}
+
+TEST_P(QuicSpdySessionTestServer, DrainingStreamsDoNotCountAsOpened) {
+  // Verify that a draining stream (which has received a FIN but not consumed
+  // it) does not count against the open quota (because it is closed from the
+  // protocol point of view).
+  if (IsVersion99()) {
+    // Version 99 will result in a MAX_STREAM_ID frame as streams are consumed
+    // (via the OnStreamFrame call) and then released (via
+    // StreamDraining). Eventually this node will believe that the peer is
+    // running low on available stream ids and then send a MAX_STREAM_ID frame,
+    // caught by this EXPECT_CALL.
+    EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+  } else {
+    EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  }
+  EXPECT_CALL(*connection_, OnStreamReset(_, QUIC_REFUSED_STREAM)).Times(0);
+  const QuicStreamId kMaxStreams = 5;
+  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+
+  // Create kMaxStreams + 1 data streams, and mark them draining.
+  const QuicStreamId kFirstStreamId = GetNthClientInitiatedBidirectionalId(0);
+  const QuicStreamId kFinalStreamId =
+      GetNthClientInitiatedBidirectionalId(kMaxStreams + 1);
+  for (QuicStreamId i = kFirstStreamId; i < kFinalStreamId; i += IdDelta()) {
+    QuicStreamFrame data1(i, true, 0, QuicStringPiece("HT"));
+    session_.OnStreamFrame(data1);
+    EXPECT_EQ(1u, session_.GetNumOpenIncomingStreams());
+    session_.StreamDraining(i);
+    EXPECT_EQ(0u, session_.GetNumOpenIncomingStreams());
+  }
+}
+
+class QuicSpdySessionTestClient : public QuicSpdySessionTestBase {
+ protected:
+  QuicSpdySessionTestClient()
+      : QuicSpdySessionTestBase(Perspective::IS_CLIENT) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+                         QuicSpdySessionTestClient,
+                         ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdySessionTestClient, AvailableStreamsClient) {
+  ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+                  GetNthServerInitiatedBidirectionalId(2)) != nullptr);
+  // Both server initiated streams with smaller stream IDs should be available.
+  EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(
+      &session_, GetNthServerInitiatedBidirectionalId(0)));
+  EXPECT_TRUE(QuicSessionPeer::IsStreamAvailable(
+      &session_, GetNthServerInitiatedBidirectionalId(1)));
+  ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+                  GetNthServerInitiatedBidirectionalId(0)) != nullptr);
+  ASSERT_TRUE(session_.GetOrCreateDynamicStream(
+                  GetNthServerInitiatedBidirectionalId(1)) != nullptr);
+  // And client initiated stream ID should be not available.
+  EXPECT_FALSE(QuicSessionPeer::IsStreamAvailable(
+      &session_, GetNthClientInitiatedBidirectionalId(0)));
+}
+
+TEST_P(QuicSpdySessionTestClient, RecordFinAfterReadSideClosed) {
+  // Verify that an incoming FIN is recorded in a stream object even if the read
+  // side has been closed.  This prevents an entry from being made in
+  // locally_closed_streams_highest_offset_ (which will never be deleted).
+  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
+  QuicStreamId stream_id = stream->id();
+
+  // Close the read side manually.
+  QuicStreamPeer::CloseReadSide(stream);
+
+  // Receive a stream data frame with FIN.
+  QuicStreamFrame frame(stream_id, true, 0, QuicStringPiece());
+  session_.OnStreamFrame(frame);
+  EXPECT_TRUE(stream->fin_received());
+
+  // Reset stream locally.
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
+  stream->Reset(QUIC_STREAM_CANCELLED);
+  EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream));
+
+  EXPECT_TRUE(connection_->connected());
+  EXPECT_TRUE(QuicSessionPeer::IsStreamClosed(&session_, stream_id));
+  EXPECT_FALSE(QuicSessionPeer::IsStreamCreated(&session_, stream_id));
+
+  // The stream is not waiting for the arrival of the peer's final offset as it
+  // was received with the FIN earlier.
+  EXPECT_EQ(
+      0u,
+      QuicSessionPeer::GetLocallyClosedStreamsHighestOffset(&session_).size());
+}
+
+TEST_P(QuicSpdySessionTestClient, WritePriority) {
+  QuicSpdySessionPeer::SetHeadersStream(&session_, nullptr);
+  TestHeadersStream* headers_stream = new TestHeadersStream(&session_);
+  QuicSpdySessionPeer::SetHeadersStream(&session_, headers_stream);
+
+  // Make packet writer blocked so |headers_stream| will buffer its write data.
+  MockPacketWriter* writer = static_cast<MockPacketWriter*>(
+      QuicConnectionPeer::GetWriter(session_.connection()));
+  EXPECT_CALL(*writer, IsWriteBlocked()).WillRepeatedly(Return(true));
+
+  const QuicStreamId id = 4;
+  const QuicStreamId parent_stream_id = 9;
+  const SpdyPriority priority = kV3HighestPriority;
+  const bool exclusive = true;
+  session_.WritePriority(id, parent_stream_id,
+                         Spdy3PriorityToHttp2Weight(priority), exclusive);
+
+  QuicStreamSendBuffer& send_buffer =
+      QuicStreamPeer::SendBuffer(headers_stream);
+  if (transport_version() > QUIC_VERSION_39) {
+    ASSERT_EQ(1u, send_buffer.size());
+
+    SpdyPriorityIR priority_frame(
+        id, parent_stream_id, Spdy3PriorityToHttp2Weight(priority), exclusive);
+    SpdyFramer spdy_framer(SpdyFramer::ENABLE_COMPRESSION);
+    SpdySerializedFrame frame = spdy_framer.SerializeFrame(priority_frame);
+
+    const QuicMemSlice& slice =
+        QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer)->slice;
+    EXPECT_EQ(QuicStringPiece(frame.data(), frame.size()),
+              QuicStringPiece(slice.data(), slice.length()));
+  } else {
+    EXPECT_EQ(0u, send_buffer.size());
+  }
+}
+
+TEST_P(QuicSpdySessionTestServer, ZombieStreams) {
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  QuicStreamPeer::SetStreamBytesWritten(3, stream2);
+  EXPECT_TRUE(stream2->IsWaitingForAcks());
+
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(stream2->id(), _));
+  session_.CloseStream(stream2->id());
+  EXPECT_FALSE(QuicContainsKey(session_.zombie_streams(), stream2->id()));
+  ASSERT_EQ(1u, session_.closed_streams()->size());
+  EXPECT_EQ(stream2->id(), session_.closed_streams()->front()->id());
+  session_.OnStreamDoneWaitingForAcks(2);
+  EXPECT_FALSE(QuicContainsKey(session_.zombie_streams(), stream2->id()));
+  EXPECT_EQ(1u, session_.closed_streams()->size());
+  EXPECT_EQ(stream2->id(), session_.closed_streams()->front()->id());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnStreamFrameLost) {
+  QuicConnectionPeer::SetSessionDecidesWhatToWrite(connection_);
+  InSequence s;
+
+  // Drive congestion control manually.
+  MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+  QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+
+  TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+
+  QuicStreamFrame frame1(
+      QuicUtils::GetCryptoStreamId(connection_->transport_version()), false, 0,
+      1300);
+  QuicStreamFrame frame2(stream2->id(), false, 0, 9);
+  QuicStreamFrame frame3(stream4->id(), false, 0, 9);
+
+  // Lost data on cryption stream, streams 2 and 4.
+  EXPECT_CALL(*stream4, HasPendingRetransmission()).WillOnce(Return(true));
+  if (connection_->transport_version() < QUIC_VERSION_47) {
+    EXPECT_CALL(*crypto_stream, HasPendingRetransmission())
+        .WillOnce(Return(true));
+  }
+  EXPECT_CALL(*stream2, HasPendingRetransmission()).WillOnce(Return(true));
+  session_.OnFrameLost(QuicFrame(frame3));
+  if (connection_->transport_version() < QUIC_VERSION_47) {
+    session_.OnFrameLost(QuicFrame(frame1));
+  } else {
+    QuicCryptoFrame crypto_frame(ENCRYPTION_NONE, 0, 1300);
+    session_.OnFrameLost(QuicFrame(&crypto_frame));
+  }
+  session_.OnFrameLost(QuicFrame(frame2));
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+  // Mark streams 2 and 4 write blocked.
+  session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  session_.MarkConnectionLevelWriteBlocked(stream4->id());
+
+  // Lost data is retransmitted before new data, and retransmissions for crypto
+  // stream go first.
+  // Do not check congestion window when crypto stream has lost data.
+  EXPECT_CALL(*send_algorithm, CanSend(_)).Times(0);
+  if (connection_->transport_version() < QUIC_VERSION_47) {
+    EXPECT_CALL(*crypto_stream, OnCanWrite());
+    EXPECT_CALL(*crypto_stream, HasPendingRetransmission())
+        .WillOnce(Return(false));
+  }
+  // Check congestion window for non crypto streams.
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+  EXPECT_CALL(*stream4, OnCanWrite());
+  EXPECT_CALL(*stream4, HasPendingRetransmission()).WillOnce(Return(false));
+  // Connection is blocked.
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(false));
+
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+  // Unblock connection.
+  // Stream 2 retransmits lost data.
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+  EXPECT_CALL(*stream2, OnCanWrite());
+  EXPECT_CALL(*stream2, HasPendingRetransmission()).WillOnce(Return(false));
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+  // Stream 2 sends new data.
+  EXPECT_CALL(*stream2, OnCanWrite());
+  EXPECT_CALL(*send_algorithm, CanSend(_)).WillOnce(Return(true));
+  EXPECT_CALL(*stream4, OnCanWrite());
+  EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+
+  session_.OnCanWrite();
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSpdySessionTestServer, DonotRetransmitDataOfClosedStreams) {
+  QuicConnectionPeer::SetSessionDecidesWhatToWrite(connection_);
+  InSequence s;
+
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+
+  QuicStreamFrame frame1(stream2->id(), false, 0, 9);
+  QuicStreamFrame frame2(stream4->id(), false, 0, 9);
+  QuicStreamFrame frame3(stream6->id(), false, 0, 9);
+
+  EXPECT_CALL(*stream6, HasPendingRetransmission()).WillOnce(Return(true));
+  EXPECT_CALL(*stream4, HasPendingRetransmission()).WillOnce(Return(true));
+  EXPECT_CALL(*stream2, HasPendingRetransmission()).WillOnce(Return(true));
+  session_.OnFrameLost(QuicFrame(frame3));
+  session_.OnFrameLost(QuicFrame(frame2));
+  session_.OnFrameLost(QuicFrame(frame1));
+
+  session_.MarkConnectionLevelWriteBlocked(stream2->id());
+  session_.MarkConnectionLevelWriteBlocked(stream4->id());
+  session_.MarkConnectionLevelWriteBlocked(stream6->id());
+
+  // Reset stream 4 locally.
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*connection_, OnStreamReset(stream4->id(), _));
+  stream4->Reset(QUIC_STREAM_CANCELLED);
+
+  // Verify stream 4 is removed from streams with lost data list.
+  EXPECT_CALL(*stream6, OnCanWrite());
+  EXPECT_CALL(*stream6, HasPendingRetransmission()).WillOnce(Return(false));
+  EXPECT_CALL(*stream2, OnCanWrite());
+  EXPECT_CALL(*stream2, HasPendingRetransmission()).WillOnce(Return(false));
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .WillRepeatedly(Invoke(&session_, &TestSession::ClearControlFrame));
+  EXPECT_CALL(*stream2, OnCanWrite());
+  EXPECT_CALL(*stream6, OnCanWrite());
+  session_.OnCanWrite();
+}
+
+TEST_P(QuicSpdySessionTestServer, RetransmitFrames) {
+  QuicConnectionPeer::SetSessionDecidesWhatToWrite(connection_);
+  MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+  QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+  InSequence s;
+
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
+  TestStream* stream6 = session_.CreateOutgoingBidirectionalStream();
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+  session_.SendWindowUpdate(stream2->id(), 9);
+
+  QuicStreamFrame frame1(stream2->id(), false, 0, 9);
+  QuicStreamFrame frame2(stream4->id(), false, 0, 9);
+  QuicStreamFrame frame3(stream6->id(), false, 0, 9);
+  QuicWindowUpdateFrame window_update(1, stream2->id(), 9);
+  QuicFrames frames;
+  frames.push_back(QuicFrame(frame1));
+  frames.push_back(QuicFrame(&window_update));
+  frames.push_back(QuicFrame(frame2));
+  frames.push_back(QuicFrame(frame3));
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+
+  EXPECT_CALL(*stream2, RetransmitStreamData(_, _, _)).WillOnce(Return(true));
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .WillOnce(Invoke(&session_, &TestSession::ClearControlFrame));
+  EXPECT_CALL(*stream4, RetransmitStreamData(_, _, _)).WillOnce(Return(true));
+  EXPECT_CALL(*stream6, RetransmitStreamData(_, _, _)).WillOnce(Return(true));
+  EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
+  session_.RetransmitFrames(frames, TLP_RETRANSMISSION);
+}
+
+TEST_P(QuicSpdySessionTestServer, OnPriorityFrame) {
+  QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+  TestStream* stream = session_.CreateIncomingStream(stream_id);
+  session_.OnPriorityFrame(stream_id, kV3HighestPriority);
+  EXPECT_EQ(kV3HighestPriority, stream->priority());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
new file mode 100644
index 0000000..9e9f1b7
--- /dev/null
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -0,0 +1,623 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_mem_slice_storage.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"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyPriority;
+
+namespace quic {
+
+// Visitor of HttpDecoder that passes data frame to QuicSpdyStream and closes
+// the connection on unexpected frames.
+class QuicSpdyStream::HttpDecoderVisitor : public HttpDecoder::Visitor {
+ public:
+  explicit HttpDecoderVisitor(QuicSpdyStream* stream) : stream_(stream) {}
+  HttpDecoderVisitor(const HttpDecoderVisitor&) = delete;
+  HttpDecoderVisitor& operator=(const HttpDecoderVisitor&) = delete;
+
+  void OnError(HttpDecoder* decoder) override {
+    stream_->session()->connection()->CloseConnection(
+        QUIC_HTTP_DECODER_ERROR, "Http decoder internal error",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  }
+
+  void OnPriorityFrame(const PriorityFrame& frame) override {
+    CloseConnectionOnWrongFrame("Priority");
+  }
+
+  void OnCancelPushFrame(const CancelPushFrame& frame) override {
+    CloseConnectionOnWrongFrame("Cancel Push");
+  }
+
+  void OnMaxPushIdFrame(const MaxPushIdFrame& frame) override {
+    CloseConnectionOnWrongFrame("Max Push Id");
+  }
+
+  void OnGoAwayFrame(const GoAwayFrame& frame) override {
+    CloseConnectionOnWrongFrame("Goaway");
+  }
+
+  void OnSettingsFrame(const SettingsFrame& frame) override {
+    CloseConnectionOnWrongFrame("Settings");
+  }
+
+  void OnDuplicatePushFrame(const DuplicatePushFrame& frame) override {
+    CloseConnectionOnWrongFrame("Duplicate Push");
+  }
+
+  void OnDataFrameStart(Http3FrameLengths frame_lengths) override {
+    stream_->OnDataFrameStart(frame_lengths);
+  }
+
+  void OnDataFramePayload(QuicStringPiece payload) override {
+    stream_->OnDataFramePayload(payload);
+  }
+
+  void OnDataFrameEnd() override { stream_->OnDataFrameEnd(); }
+
+  void OnHeadersFrameStart() override {
+    CloseConnectionOnWrongFrame("Headers");
+  }
+
+  void OnHeadersFramePayload(QuicStringPiece payload) override {
+    CloseConnectionOnWrongFrame("Headers");
+  }
+
+  void OnHeadersFrameEnd(QuicByteCount frame_len) override {
+    CloseConnectionOnWrongFrame("Headers");
+  }
+
+  void OnPushPromiseFrameStart(PushId push_id) override {
+    CloseConnectionOnWrongFrame("Push Promise");
+  }
+
+  void OnPushPromiseFramePayload(QuicStringPiece payload) override {
+    CloseConnectionOnWrongFrame("Push Promise");
+  }
+
+  void OnPushPromiseFrameEnd() override {
+    CloseConnectionOnWrongFrame("Push Promise");
+  }
+
+ private:
+  void CloseConnectionOnWrongFrame(QuicString frame_type) {
+    stream_->session()->connection()->CloseConnection(
+        QUIC_HTTP_DECODER_ERROR, frame_type + " frame received on data stream",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  }
+
+  QuicSpdyStream* stream_;
+};
+
+#define ENDPOINT                                                   \
+  (session()->perspective() == Perspective::IS_SERVER ? "Server: " \
+                                                      : "Client:"  \
+                                                        " ")
+
+QuicSpdyStream::QuicSpdyStream(QuicStreamId id,
+                               QuicSpdySession* spdy_session,
+                               StreamType type)
+    : QuicStream(id, spdy_session, /*is_static=*/false, type),
+      spdy_session_(spdy_session),
+      visitor_(nullptr),
+      headers_decompressed_(false),
+      trailers_decompressed_(false),
+      trailers_consumed_(false),
+      http_decoder_visitor_(new HttpDecoderVisitor(this)),
+      body_buffer_(sequencer()),
+      ack_listener_(nullptr) {
+  DCHECK_NE(QuicUtils::GetCryptoStreamId(
+                spdy_session->connection()->transport_version()),
+            id);
+  // Don't receive any callbacks from the sequencer until headers
+  // are complete.
+  sequencer()->SetBlockedUntilFlush();
+
+  if (VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    sequencer()->set_level_triggered(true);
+  }
+  decoder_.set_visitor(http_decoder_visitor_.get());
+}
+
+QuicSpdyStream::QuicSpdyStream(PendingStream pending,
+                               QuicSpdySession* spdy_session,
+                               StreamType type)
+    : QuicStream(std::move(pending), type),
+      spdy_session_(spdy_session),
+      visitor_(nullptr),
+      headers_decompressed_(false),
+      trailers_decompressed_(false),
+      trailers_consumed_(false),
+      http_decoder_visitor_(new HttpDecoderVisitor(this)),
+      body_buffer_(sequencer()),
+      ack_listener_(nullptr) {
+  DCHECK_NE(QuicUtils::GetCryptoStreamId(
+                spdy_session->connection()->transport_version()),
+            id());
+  // Don't receive any callbacks from the sequencer until headers
+  // are complete.
+  sequencer()->SetBlockedUntilFlush();
+
+  if (VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    sequencer()->set_level_triggered(true);
+  }
+  decoder_.set_visitor(http_decoder_visitor_.get());
+}
+
+QuicSpdyStream::~QuicSpdyStream() {}
+
+size_t QuicSpdyStream::WriteHeaders(
+    SpdyHeaderBlock header_block,
+    bool fin,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  size_t bytes_written =
+      WriteHeadersImpl(std::move(header_block), fin, std::move(ack_listener));
+  if (fin) {
+    // TODO(rch): Add test to ensure fin_sent_ is set whenever a fin is sent.
+    set_fin_sent(true);
+    CloseWriteSide();
+  }
+  return bytes_written;
+}
+
+void QuicSpdyStream::WriteOrBufferBody(QuicStringPiece data, bool fin) {
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version()) ||
+      data.length() == 0) {
+    WriteOrBufferData(data, fin, nullptr);
+    return;
+  }
+  QuicConnection::ScopedPacketFlusher flusher(
+      spdy_session_->connection(), QuicConnection::SEND_ACK_IF_PENDING);
+
+  // Write frame header.
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(data.length(), &buffer);
+  unacked_frame_headers_offsets_.Add(
+      send_buffer().stream_offset(),
+      send_buffer().stream_offset() + header_length);
+  QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
+                  << header_length;
+  WriteOrBufferData(QuicStringPiece(buffer.get(), header_length), false,
+                    nullptr);
+
+  // Write body.
+  QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
+                  << data.length();
+  WriteOrBufferData(data, fin, nullptr);
+}
+
+size_t QuicSpdyStream::WriteTrailers(
+    SpdyHeaderBlock trailer_block,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  if (fin_sent()) {
+    QUIC_BUG << "Trailers cannot be sent after a FIN, on stream " << id();
+    return 0;
+  }
+
+  // The header block must contain the final offset for this stream, as the
+  // trailers may be processed out of order at the peer.
+  QUIC_DLOG(INFO) << "Inserting trailer: (" << kFinalOffsetHeaderKey << ", "
+                  << stream_bytes_written() + BufferedDataBytes() << ")";
+  trailer_block.insert(
+      std::make_pair(kFinalOffsetHeaderKey,
+                     QuicTextUtils::Uint64ToString(stream_bytes_written() +
+                                                   BufferedDataBytes())));
+
+  // Write the trailing headers with a FIN, and close stream for writing:
+  // trailers are the last thing to be sent on a stream.
+  const bool kFin = true;
+  size_t bytes_written =
+      WriteHeadersImpl(std::move(trailer_block), kFin, std::move(ack_listener));
+  set_fin_sent(kFin);
+
+  // Trailers are the last thing to be sent on a stream, but if there is still
+  // queued data then CloseWriteSide() will cause it never to be sent.
+  if (BufferedDataBytes() == 0) {
+    CloseWriteSide();
+  }
+
+  return bytes_written;
+}
+
+QuicConsumedData QuicSpdyStream::WritevBody(const struct iovec* iov,
+                                            int count,
+                                            bool fin) {
+  if (!GetQuicReloadableFlag(quic_call_write_mem_slices)) {
+    return WritevData(iov, count, fin);
+  }
+  QUIC_RELOADABLE_FLAG_COUNT(quic_call_write_mem_slices);
+  QuicMemSliceStorage storage(
+      iov, count,
+      session()->connection()->helper()->GetStreamSendBufferAllocator(),
+      GetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size));
+  return WriteBodySlices(storage.ToSpan(), fin);
+}
+
+QuicConsumedData QuicSpdyStream::WriteBodySlices(QuicMemSliceSpan slices,
+                                                 bool fin) {
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version()) ||
+      slices.empty()) {
+    return WriteMemSlices(slices, fin);
+  }
+
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(slices.total_length(), &buffer);
+  if (!CanWriteNewDataAfterData(header_length)) {
+    return {0, false};
+  }
+
+  QuicConnection::ScopedPacketFlusher flusher(
+      spdy_session_->connection(), QuicConnection::SEND_ACK_IF_PENDING);
+
+  // Write frame header.
+  struct iovec header_iov = {static_cast<void*>(buffer.get()), header_length};
+  QuicMemSliceStorage storage(
+      &header_iov, 1,
+      spdy_session_->connection()->helper()->GetStreamSendBufferAllocator(),
+      GetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size));
+  unacked_frame_headers_offsets_.Add(
+      send_buffer().stream_offset(),
+      send_buffer().stream_offset() + header_length);
+  QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
+                  << header_length;
+  WriteMemSlices(storage.ToSpan(), false);
+
+  // Write body.
+  QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
+                  << slices.total_length();
+  return WriteMemSlices(slices, fin);
+}
+
+size_t QuicSpdyStream::Readv(const struct iovec* iov, size_t iov_len) {
+  DCHECK(FinishedReadingHeaders());
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    return sequencer()->Readv(iov, iov_len);
+  }
+  return body_buffer_.ReadBody(iov, iov_len);
+}
+
+int QuicSpdyStream::GetReadableRegions(iovec* iov, size_t iov_len) const {
+  DCHECK(FinishedReadingHeaders());
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    return sequencer()->GetReadableRegions(iov, iov_len);
+  }
+  return body_buffer_.PeekBody(iov, iov_len);
+}
+
+void QuicSpdyStream::MarkConsumed(size_t num_bytes) {
+  DCHECK(FinishedReadingHeaders());
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    sequencer()->MarkConsumed(num_bytes);
+    return;
+  }
+  body_buffer_.MarkBodyConsumed(num_bytes);
+}
+
+bool QuicSpdyStream::IsDoneReading() const {
+  bool done_reading_headers = FinishedReadingHeaders();
+  bool done_reading_body = sequencer()->IsClosed();
+  bool done_reading_trailers = FinishedReadingTrailers();
+  return done_reading_headers && done_reading_body && done_reading_trailers;
+}
+
+bool QuicSpdyStream::HasBytesToRead() const {
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    return sequencer()->HasBytesToRead();
+  }
+  return body_buffer_.HasBytesToRead();
+}
+
+void QuicSpdyStream::MarkTrailersConsumed() {
+  trailers_consumed_ = true;
+}
+
+uint64_t QuicSpdyStream::total_body_bytes_read() const {
+  if (VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    return body_buffer_.total_body_bytes_received();
+  }
+  return sequencer()->NumBytesConsumed();
+}
+
+void QuicSpdyStream::ConsumeHeaderList() {
+  header_list_.Clear();
+  if (FinishedReadingHeaders()) {
+    sequencer()->SetUnblocked();
+  }
+}
+
+void QuicSpdyStream::OnStreamHeadersPriority(SpdyPriority priority) {
+  DCHECK_EQ(Perspective::IS_SERVER, session()->connection()->perspective());
+  SetPriority(priority);
+}
+
+void QuicSpdyStream::OnStreamHeaderList(bool fin,
+                                        size_t frame_len,
+                                        const QuicHeaderList& header_list) {
+  // The headers list avoid infinite buffering by clearing the headers list
+  // if the current headers are too large. So if the list is empty here
+  // then the headers list must have been too large, and the stream should
+  // be reset.
+  // TODO(rch): Use an explicit "headers too large" signal. An empty header list
+  // might be acceptable if it corresponds to a trailing header frame.
+  if (header_list.empty()) {
+    OnHeadersTooLarge();
+    if (IsDoneReading()) {
+      return;
+    }
+  }
+  if (!headers_decompressed_) {
+    OnInitialHeadersComplete(fin, frame_len, header_list);
+  } else {
+    OnTrailingHeadersComplete(fin, frame_len, header_list);
+  }
+}
+
+void QuicSpdyStream::OnHeadersTooLarge() {
+  Reset(QUIC_HEADERS_TOO_LARGE);
+}
+
+void QuicSpdyStream::OnInitialHeadersComplete(
+    bool fin,
+    size_t /*frame_len*/,
+    const QuicHeaderList& header_list) {
+  headers_decompressed_ = true;
+  header_list_ = header_list;
+  if (fin) {
+    OnStreamFrame(QuicStreamFrame(id(), fin, 0, QuicStringPiece()));
+  }
+  if (FinishedReadingHeaders()) {
+    sequencer()->SetUnblocked();
+  }
+}
+
+void QuicSpdyStream::OnPromiseHeaderList(
+    QuicStreamId /* promised_id */,
+    size_t /* frame_len */,
+    const QuicHeaderList& /*header_list */) {
+  // To be overridden in QuicSpdyClientStream.  Not supported on
+  // server side.
+  session()->connection()->CloseConnection(
+      QUIC_INVALID_HEADERS_STREAM_DATA, "Promise headers received by server",
+      ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void QuicSpdyStream::OnTrailingHeadersComplete(
+    bool fin,
+    size_t /*frame_len*/,
+    const QuicHeaderList& header_list) {
+  DCHECK(!trailers_decompressed_);
+  if (fin_received()) {
+    QUIC_DLOG(ERROR) << "Received Trailers after FIN, on stream: " << id();
+    session()->connection()->CloseConnection(
+        QUIC_INVALID_HEADERS_STREAM_DATA, "Trailers after fin",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  if (!fin) {
+    QUIC_DLOG(ERROR) << "Trailers must have FIN set, on stream: " << id();
+    session()->connection()->CloseConnection(
+        QUIC_INVALID_HEADERS_STREAM_DATA, "Fin missing from trailers",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+
+  size_t final_byte_offset = 0;
+  if (!SpdyUtils::CopyAndValidateTrailers(header_list, &final_byte_offset,
+                                          &received_trailers_)) {
+    QUIC_DLOG(ERROR) << "Trailers for stream " << id() << " are malformed.";
+    session()->connection()->CloseConnection(
+        QUIC_INVALID_HEADERS_STREAM_DATA, "Trailers are malformed",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  trailers_decompressed_ = true;
+  OnStreamFrame(
+      QuicStreamFrame(id(), fin, final_byte_offset, QuicStringPiece()));
+}
+
+size_t QuicSpdyStream::WriteHeadersImpl(
+    spdy::SpdyHeaderBlock header_block,
+    bool fin,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  return spdy_session_->WriteHeadersOnHeadersStream(
+      id(), std::move(header_block), fin, priority(), std::move(ack_listener));
+}
+
+void QuicSpdyStream::OnPriorityFrame(SpdyPriority priority) {
+  DCHECK_EQ(Perspective::IS_SERVER, session()->connection()->perspective());
+  SetPriority(priority);
+}
+
+void QuicSpdyStream::OnStreamReset(const QuicRstStreamFrame& frame) {
+  if (frame.error_code != QUIC_STREAM_NO_ERROR) {
+    QuicStream::OnStreamReset(frame);
+    return;
+  }
+  QUIC_DVLOG(1) << "Received QUIC_STREAM_NO_ERROR, not discarding response";
+  set_rst_received(true);
+  MaybeIncreaseHighestReceivedOffset(frame.byte_offset);
+  set_stream_error(frame.error_code);
+  CloseWriteSide();
+}
+
+void QuicSpdyStream::OnDataAvailable() {
+  if (!VersionHasDataFrameHeader(
+          session()->connection()->transport_version())) {
+    OnBodyAvailable();
+    return;
+  }
+
+  iovec iov;
+  bool has_payload = false;
+  while (sequencer()->PrefetchNextRegion(&iov)) {
+    decoder_.ProcessInput(reinterpret_cast<const char*>(iov.iov_base),
+                          iov.iov_len);
+    if (decoder_.has_payload()) {
+      has_payload = true;
+    }
+  }
+
+  if (has_payload) {
+    OnBodyAvailable();
+    return;
+  }
+
+  if (sequencer()->IsClosed()) {
+    OnBodyAvailable();
+    return;
+  }
+}
+
+void QuicSpdyStream::OnClose() {
+  QuicStream::OnClose();
+
+  if (visitor_) {
+    Visitor* visitor = visitor_;
+    // Calling Visitor::OnClose() may result the destruction of the visitor,
+    // so we need to ensure we don't call it again.
+    visitor_ = nullptr;
+    visitor->OnClose(this);
+  }
+}
+
+void QuicSpdyStream::OnCanWrite() {
+  QuicStream::OnCanWrite();
+
+  // Trailers (and hence a FIN) may have been sent ahead of queued body bytes.
+  if (!HasBufferedData() && fin_sent()) {
+    CloseWriteSide();
+  }
+}
+
+bool QuicSpdyStream::FinishedReadingHeaders() const {
+  return headers_decompressed_ && header_list_.empty();
+}
+
+bool QuicSpdyStream::ParseHeaderStatusCode(const SpdyHeaderBlock& header,
+                                           int* status_code) const {
+  SpdyHeaderBlock::const_iterator it = header.find(spdy::kHttp2StatusHeader);
+  if (it == header.end()) {
+    return false;
+  }
+  const QuicStringPiece status(it->second);
+  if (status.size() != 3) {
+    return false;
+  }
+  // First character must be an integer in range [1,5].
+  if (status[0] < '1' || status[0] > '5') {
+    return false;
+  }
+  // The remaining two characters must be integers.
+  if (!isdigit(status[1]) || !isdigit(status[2])) {
+    return false;
+  }
+  return QuicTextUtils::StringToInt(status, status_code);
+}
+
+bool QuicSpdyStream::FinishedReadingTrailers() const {
+  // If no further trailing headers are expected, and the decompressed trailers
+  // (if any) have been consumed, then reading of trailers is finished.
+  if (!fin_received()) {
+    return false;
+  } else if (!trailers_decompressed_) {
+    return true;
+  } else {
+    return trailers_consumed_;
+  }
+}
+
+void QuicSpdyStream::ClearSession() {
+  spdy_session_ = nullptr;
+}
+
+void QuicSpdyStream::OnDataFrameStart(Http3FrameLengths frame_lengths) {
+  body_buffer_.OnDataHeader(frame_lengths);
+}
+
+void QuicSpdyStream::OnDataFramePayload(QuicStringPiece payload) {
+  body_buffer_.OnDataPayload(payload);
+}
+
+void QuicSpdyStream::OnDataFrameEnd() {
+  DVLOG(1) << "Reaches the end of a data frame. Total bytes received are "
+           << body_buffer_.total_body_bytes_received();
+}
+
+bool QuicSpdyStream::OnStreamFrameAcked(QuicStreamOffset offset,
+                                        QuicByteCount data_length,
+                                        bool fin_acked,
+                                        QuicTime::Delta ack_delay_time,
+                                        QuicByteCount* newly_acked_length) {
+  const bool new_data_acked = QuicStream::OnStreamFrameAcked(
+      offset, data_length, fin_acked, ack_delay_time, newly_acked_length);
+
+  const QuicByteCount newly_acked_header_length =
+      GetNumFrameHeadersInInterval(offset, data_length);
+  DCHECK_LE(newly_acked_header_length, *newly_acked_length);
+  unacked_frame_headers_offsets_.Difference(offset, offset + data_length);
+  if (ack_listener_ != nullptr && new_data_acked) {
+    ack_listener_->OnPacketAcked(
+        *newly_acked_length - newly_acked_header_length, ack_delay_time);
+  }
+  return new_data_acked;
+}
+
+void QuicSpdyStream::OnStreamFrameRetransmitted(QuicStreamOffset offset,
+                                                QuicByteCount data_length,
+                                                bool fin_retransmitted) {
+  QuicStream::OnStreamFrameRetransmitted(offset, data_length,
+                                         fin_retransmitted);
+
+  const QuicByteCount retransmitted_header_length =
+      GetNumFrameHeadersInInterval(offset, data_length);
+  DCHECK_LE(retransmitted_header_length, data_length);
+
+  if (ack_listener_ != nullptr) {
+    ack_listener_->OnPacketRetransmitted(data_length -
+                                         retransmitted_header_length);
+  }
+}
+
+QuicByteCount QuicSpdyStream::GetNumFrameHeadersInInterval(
+    QuicStreamOffset offset,
+    QuicByteCount data_length) const {
+  QuicByteCount header_acked_length = 0;
+  QuicIntervalSet<QuicStreamOffset> newly_acked(offset, offset + data_length);
+  newly_acked.Intersection(unacked_frame_headers_offsets_);
+  for (const auto& interval : newly_acked) {
+    header_acked_length += interval.Length();
+  }
+  return header_acked_length;
+}
+
+#undef ENDPOINT  // undef for jumbo builds
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h
new file mode 100644
index 0000000..2bad21a
--- /dev/null
+++ b/quic/core/http/quic_spdy_stream.h
@@ -0,0 +1,284 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The base class for streams which deliver data to/from an application.
+// In each direction, the data on such a stream first contains compressed
+// headers then body data.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_H_
+
+#include <sys/types.h>
+
+#include <cstddef>
+#include <list>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.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/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+namespace test {
+class QuicSpdyStreamPeer;
+class QuicStreamPeer;
+}  // namespace test
+
+class QuicSpdySession;
+
+// A QUIC stream that can send and receive HTTP2 (SPDY) headers.
+class QUIC_EXPORT_PRIVATE QuicSpdyStream : public QuicStream {
+ public:
+  // Visitor receives callbacks from the stream.
+  class QUIC_EXPORT_PRIVATE Visitor {
+   public:
+    Visitor() {}
+    Visitor(const Visitor&) = delete;
+    Visitor& operator=(const Visitor&) = delete;
+
+    // Called when the stream is closed.
+    virtual void OnClose(QuicSpdyStream* stream) = 0;
+
+    // Allows subclasses to override and do work.
+    virtual void OnPromiseHeadersComplete(QuicStreamId promised_id,
+                                          size_t frame_len) {}
+
+   protected:
+    virtual ~Visitor() {}
+  };
+
+  QuicSpdyStream(QuicStreamId id,
+                 QuicSpdySession* spdy_session,
+                 StreamType type);
+  QuicSpdyStream(PendingStream pending,
+                 QuicSpdySession* spdy_session,
+                 StreamType type);
+  QuicSpdyStream(const QuicSpdyStream&) = delete;
+  QuicSpdyStream& operator=(const QuicSpdyStream&) = delete;
+  ~QuicSpdyStream() override;
+
+  // QuicStream implementation
+  void OnClose() override;
+
+  // Override to maybe close the write side after writing.
+  void OnCanWrite() override;
+
+  // Called by the session when headers with a priority have been received
+  // for this stream.  This method will only be called for server streams.
+  virtual void OnStreamHeadersPriority(spdy::SpdyPriority priority);
+
+  // Called by the session when decompressed headers have been completely
+  // delivered to this stream.  If |fin| is true, then this stream
+  // should be closed; no more data will be sent by the peer.
+  virtual void OnStreamHeaderList(bool fin,
+                                  size_t frame_len,
+                                  const QuicHeaderList& header_list);
+
+  // Called when the received headers are too large. By default this will
+  // reset the stream.
+  virtual void OnHeadersTooLarge();
+
+  // Called by the session when decompressed push promise headers have
+  // been completely delivered to this stream.
+  virtual void OnPromiseHeaderList(QuicStreamId promised_id,
+                                   size_t frame_len,
+                                   const QuicHeaderList& header_list);
+
+  // Called by the session when a PRIORITY frame has been been received for this
+  // stream. This method will only be called for server streams.
+  void OnPriorityFrame(spdy::SpdyPriority priority);
+
+  // Override the base class to not discard response when receiving
+  // QUIC_STREAM_NO_ERROR.
+  void OnStreamReset(const QuicRstStreamFrame& frame) override;
+
+  // Called by the sequencer when new data is available. Decodes the data and
+  // calls OnBodyAvailable() to pass to the upper layer.
+  void OnDataAvailable() override;
+
+  // Called in OnDataAvailable() after it finishes the decoding job.
+  virtual void OnBodyAvailable() = 0;
+
+  // Writes the headers contained in |header_block| to the dedicated
+  // headers stream.
+  virtual size_t WriteHeaders(
+      spdy::SpdyHeaderBlock header_block,
+      bool fin,
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+  // Sends |data| to the peer, or buffers if it can't be sent immediately.
+  void WriteOrBufferBody(QuicStringPiece data, bool fin);
+
+  // Writes the trailers contained in |trailer_block| to the dedicated
+  // headers stream. Trailers will always have the FIN set.
+  virtual size_t WriteTrailers(
+      spdy::SpdyHeaderBlock trailer_block,
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+  // Override to report newly acked bytes via ack_listener_.
+  bool OnStreamFrameAcked(QuicStreamOffset offset,
+                          QuicByteCount data_length,
+                          bool fin_acked,
+                          QuicTime::Delta ack_delay_time,
+                          QuicByteCount* newly_acked_length) override;
+
+  // Override to report bytes retransmitted via ack_listener_.
+  void OnStreamFrameRetransmitted(QuicStreamOffset offset,
+                                  QuicByteCount data_length,
+                                  bool fin_retransmitted) override;
+
+  // Does the same thing as WriteOrBufferBody except this method takes iovec
+  // as the data input. Right now it only calls WritevData.
+  // TODO(renjietang): Write data frame header before writing body.
+  QuicConsumedData WritevBody(const struct iovec* iov, int count, bool fin);
+
+  // Does the same thing as WriteOrBufferBody except this method takes
+  // memslicespan as the data input. Right now it only calls WriteMemSlices.
+  // TODO(renjietang): Write data frame header before writing body.
+  QuicConsumedData WriteBodySlices(QuicMemSliceSpan slices, bool fin);
+
+  // Marks the trailers as consumed. This applies to the case where this object
+  // receives headers and trailers as QuicHeaderLists via calls to
+  // OnStreamHeaderList().
+  void MarkTrailersConsumed();
+
+  // Clears |header_list_|.
+  void ConsumeHeaderList();
+
+  // This block of functions wraps the sequencer's functions of the same
+  // name.  These methods return uncompressed data until that has
+  // been fully processed.  Then they simply delegate to the sequencer.
+  virtual size_t Readv(const struct iovec* iov, size_t iov_len);
+  virtual int GetReadableRegions(iovec* iov, size_t iov_len) const;
+  void MarkConsumed(size_t num_bytes);
+
+  // Returns true if header contains a valid 3-digit status and parse the status
+  // code to |status_code|.
+  bool ParseHeaderStatusCode(const spdy::SpdyHeaderBlock& header,
+                             int* status_code) const;
+
+  // Returns true when all data has been read from the peer, including the fin.
+  bool IsDoneReading() const;
+  bool HasBytesToRead() const;
+
+  void set_visitor(Visitor* visitor) { visitor_ = visitor; }
+
+  bool headers_decompressed() const { return headers_decompressed_; }
+
+  // Returns total amount of body bytes that have been read.
+  uint64_t total_body_bytes_read() const;
+
+  const QuicHeaderList& header_list() const { return header_list_; }
+
+  bool trailers_decompressed() const { return trailers_decompressed_; }
+
+  // Returns whatever trailers have been received for this stream.
+  const spdy::SpdyHeaderBlock& received_trailers() const {
+    return received_trailers_;
+  }
+
+  // Returns true if headers have been fully read and consumed.
+  bool FinishedReadingHeaders() const;
+
+  // Returns true if trailers have been fully read and consumed, or FIN has
+  // been received and there are no trailers.
+  bool FinishedReadingTrailers() const;
+
+  // Called when owning session is getting deleted to avoid subsequent
+  // use of the spdy_session_ member.
+  void ClearSession();
+
+  // Returns true if the sequencer has delivered the FIN, and no more body bytes
+  // will be available.
+  bool IsClosed() { return sequencer()->IsClosed(); }
+
+  void OnDataFrameStart(Http3FrameLengths frame_lengths);
+  void OnDataFramePayload(QuicStringPiece payload);
+  void OnDataFrameEnd();
+
+  using QuicStream::CloseWriteSide;
+
+ protected:
+  virtual void OnInitialHeadersComplete(bool fin,
+                                        size_t frame_len,
+                                        const QuicHeaderList& header_list);
+  virtual void OnTrailingHeadersComplete(bool fin,
+                                         size_t frame_len,
+                                         const QuicHeaderList& header_list);
+  virtual size_t WriteHeadersImpl(
+      spdy::SpdyHeaderBlock header_block,
+      bool fin,
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+
+  QuicSpdySession* spdy_session() const { return spdy_session_; }
+  Visitor* visitor() { return visitor_; }
+
+  void set_headers_decompressed(bool val) { headers_decompressed_ = val; }
+
+  void set_ack_listener(
+      QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+    ack_listener_ = std::move(ack_listener);
+  }
+
+  const QuicIntervalSet<QuicStreamOffset>& unacked_frame_headers_offsets() {
+    return unacked_frame_headers_offsets_;
+  }
+
+ private:
+  friend class test::QuicSpdyStreamPeer;
+  friend class test::QuicStreamPeer;
+  friend class QuicStreamUtils;
+  class HttpDecoderVisitor;
+
+  // Given the interval marked by [|offset|, |offset| + |data_length|), return
+  // the number of frame header bytes contained in it.
+  QuicByteCount GetNumFrameHeadersInInterval(QuicStreamOffset offset,
+                                             QuicByteCount data_length) const;
+
+  QuicSpdySession* spdy_session_;
+
+  Visitor* visitor_;
+  // True if the headers have been completely decompressed.
+  bool headers_decompressed_;
+  // Contains a copy of the decompressed header (name, value) pairs until they
+  // are consumed via Readv.
+  QuicHeaderList header_list_;
+
+  // True if the trailers have been completely decompressed.
+  bool trailers_decompressed_;
+  // True if the trailers have been consumed.
+  bool trailers_consumed_;
+  // The parsed trailers received from the peer.
+  spdy::SpdyHeaderBlock received_trailers_;
+
+  // Http encoder for writing streams.
+  HttpEncoder encoder_;
+  // Http decoder for processing raw incoming stream frames.
+  HttpDecoder decoder_;
+  // Visitor of the HttpDecoder.
+  std::unique_ptr<HttpDecoderVisitor> http_decoder_visitor_;
+  // Buffer that contains decoded data of the stream.
+  QuicSpdyStreamBodyBuffer body_buffer_;
+
+  // Ack listener of this stream, and it is notified when any of written bytes
+  // are acked or retransmitted.
+  QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener_;
+
+  // Offset of unacked frame headers.
+  QuicIntervalSet<QuicStreamOffset> unacked_frame_headers_offsets_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_H_
diff --git a/quic/core/http/quic_spdy_stream_body_buffer.cc b/quic/core/http/quic_spdy_stream_body_buffer.cc
new file mode 100644
index 0000000..dcb8030
--- /dev/null
+++ b/quic/core/http/quic_spdy_stream_body_buffer.cc
@@ -0,0 +1,127 @@
+// 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/core/http/quic_spdy_stream_body_buffer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+
+namespace quic {
+
+QuicSpdyStreamBodyBuffer::QuicSpdyStreamBodyBuffer(
+    QuicStreamSequencer* sequencer)
+    : bytes_remaining_(0),
+      total_body_bytes_readable_(0),
+      total_body_bytes_received_(0),
+      total_payload_lengths_(0),
+      sequencer_(sequencer) {}
+
+QuicSpdyStreamBodyBuffer::~QuicSpdyStreamBodyBuffer() {}
+
+void QuicSpdyStreamBodyBuffer::OnDataHeader(Http3FrameLengths frame_lengths) {
+  frame_meta_.push_back(frame_lengths);
+  total_payload_lengths_ += frame_lengths.payload_length;
+}
+
+void QuicSpdyStreamBodyBuffer::OnDataPayload(QuicStringPiece payload) {
+  bodies_.push_back(payload);
+  total_body_bytes_received_ += payload.length();
+  total_body_bytes_readable_ += payload.length();
+  DCHECK_LE(total_body_bytes_received_, total_payload_lengths_);
+}
+
+void QuicSpdyStreamBodyBuffer::MarkBodyConsumed(size_t num_bytes) {
+  // Check if the stream has enough decoded data.
+  if (num_bytes > total_body_bytes_readable_) {
+    QUIC_BUG << "Invalid argument to MarkBodyConsumed."
+             << " expect to consume: " << num_bytes
+             << ", but not enough bytes available. "
+             << "Total bytes readable are: " << total_body_bytes_readable_;
+    return;
+  }
+  // Discard references in the stream before the sequencer marks them consumed.
+  size_t remaining = num_bytes;
+  while (remaining > 0) {
+    if (bodies_.empty()) {
+      QUIC_BUG << "Failed to consume because body buffer is empty.";
+      return;
+    }
+    auto body = bodies_.front();
+    bodies_.pop_front();
+    if (body.length() <= remaining) {
+      remaining -= body.length();
+    } else {
+      body = body.substr(remaining, body.length() - remaining);
+      bodies_.push_front(body);
+      remaining = 0;
+    }
+  }
+  // Consume headers.
+  while (bytes_remaining_ < num_bytes) {
+    if (frame_meta_.empty()) {
+      QUIC_BUG << "Faild to consume because frame header buffer is empty.";
+      return;
+    }
+    auto meta = frame_meta_.front();
+    frame_meta_.pop_front();
+    bytes_remaining_ += meta.payload_length;
+    sequencer_->MarkConsumed(meta.header_length);
+  }
+  sequencer_->MarkConsumed(num_bytes);
+  // Update accountings.
+  bytes_remaining_ -= num_bytes;
+  total_body_bytes_readable_ -= num_bytes;
+}
+
+int QuicSpdyStreamBodyBuffer::PeekBody(iovec* iov, size_t iov_len) const {
+  DCHECK(iov != nullptr);
+  DCHECK_GT(iov_len, 0u);
+
+  if (bodies_.empty()) {
+    iov[0].iov_base = nullptr;
+    iov[0].iov_len = 0;
+    return 0;
+  }
+  // Fill iovs with references from the stream.
+  size_t iov_filled = 0;
+  while (iov_filled < bodies_.size() && iov_filled < iov_len) {
+    QuicStringPiece body = bodies_[iov_filled];
+    iov[iov_filled].iov_base = const_cast<char*>(body.data());
+    iov[iov_filled].iov_len = body.size();
+    iov_filled++;
+  }
+  return iov_filled;
+}
+
+size_t QuicSpdyStreamBodyBuffer::ReadBody(const struct iovec* iov,
+                                          size_t iov_len) {
+  size_t total_data_read = 0;
+  QuicByteCount total_remaining = total_body_bytes_readable_;
+  size_t index = 0;
+  size_t src_offset = 0;
+  for (size_t i = 0; i < iov_len && total_remaining > 0; ++i) {
+    char* dest = reinterpret_cast<char*>(iov[i].iov_base);
+    size_t dest_remaining = iov[i].iov_len;
+    while (dest_remaining > 0 && total_remaining > 0) {
+      auto body = bodies_[index];
+      size_t bytes_to_copy =
+          std::min<size_t>(body.length() - src_offset, dest_remaining);
+      memcpy(dest, body.substr(src_offset, bytes_to_copy).data(),
+             bytes_to_copy);
+      dest += bytes_to_copy;
+      dest_remaining -= bytes_to_copy;
+      total_data_read += bytes_to_copy;
+      total_remaining -= bytes_to_copy;
+      if (bytes_to_copy < body.length() - src_offset) {
+        src_offset += bytes_to_copy;
+      } else {
+        index++;
+        src_offset = 0;
+      }
+    }
+  }
+
+  MarkBodyConsumed(total_data_read);
+  return total_data_read;
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_stream_body_buffer.h b/quic/core/http/quic_spdy_stream_body_buffer.h
new file mode 100644
index 0000000..2485aac
--- /dev/null
+++ b/quic/core/http/quic_spdy_stream_body_buffer.h
@@ -0,0 +1,75 @@
+// 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_CORE_HTTP_QUIC_SPDY_STREAM_BODY_BUFFER_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_BODY_BUFFER_H_
+
+#include "net/third_party/quiche/src/quic/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+
+namespace quic {
+
+// Buffer decoded body for QuicSpdyStream. It also talks to the sequencer to
+// consume data.
+class QUIC_EXPORT_PRIVATE QuicSpdyStreamBodyBuffer {
+ public:
+  // QuicSpdyStreamBodyBuffer doesn't own the sequencer and the sequencer can
+  // outlive the buffer.
+  explicit QuicSpdyStreamBodyBuffer(QuicStreamSequencer* sequencer);
+
+  ~QuicSpdyStreamBodyBuffer();
+
+  // Add metadata of the frame to accountings.
+  // Called when QuicSpdyStream receives data frame header.
+  void OnDataHeader(Http3FrameLengths frame_lengths);
+
+  // Add new data payload to buffer.
+  // Called when QuicSpdyStream received data payload.
+  // Data pointed by payload must be alive until consumed by
+  // QuicStreamSequencer::MarkConsumed().
+  void OnDataPayload(QuicStringPiece payload);
+
+  // Take |num_bytes| as the body size, calculate header sizes accordingly, and
+  // consume the right amount of data in the stream sequencer.
+  void MarkBodyConsumed(size_t num_bytes);
+
+  // Fill up to |iov_len| with bodies available in buffer. No data is consumed.
+  // |iov|.iov_base will point to data in the buffer, and |iov|.iov_len will
+  // be set to the underlying data length accordingly.
+  // Returns the number of iov used.
+  int PeekBody(iovec* iov, size_t iov_len) const;
+
+  // Copies from buffer into |iov| up to |iov_len|, and consume data in
+  // sequencer. |iov.iov_base| and |iov.iov_len| are preassigned and will not be
+  // changed.
+  // Returns the number of bytes read.
+  size_t ReadBody(const struct iovec* iov, size_t iov_len);
+
+  bool HasBytesToRead() const { return !bodies_.empty(); }
+
+  uint64_t total_body_bytes_received() const {
+    return total_body_bytes_received_;
+  }
+
+ private:
+  // Storage for decoded data.
+  QuicDeque<QuicStringPiece> bodies_;
+  // Storage for header lengths.
+  QuicDeque<Http3FrameLengths> frame_meta_;
+  // Bytes in the first available data frame that are not consumed yet.
+  QuicByteCount bytes_remaining_;
+  // Total available body data in the stream.
+  QuicByteCount total_body_bytes_readable_;
+  // Total bytes read from the stream excluding headers.
+  QuicByteCount total_body_bytes_received_;
+  // Total length of payloads tracked by frame_meta_.
+  QuicByteCount total_payload_lengths_;
+  // Stream sequencer that directly manages data in the stream.
+  QuicStreamSequencer* sequencer_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_BODY_BUFFER_H_
diff --git a/quic/core/http/quic_spdy_stream_body_buffer_test.cc b/quic/core/http/quic_spdy_stream_body_buffer_test.cc
new file mode 100644
index 0000000..7ea776f
--- /dev/null
+++ b/quic/core/http/quic_spdy_stream_body_buffer_test.cc
@@ -0,0 +1,240 @@
+// 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/core/http/quic_spdy_stream_body_buffer.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.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/test_tools/quic_test_utils.h"
+
+namespace quic {
+
+namespace test {
+
+namespace {
+
+class MockStream : public QuicStreamSequencer::StreamInterface {
+ public:
+  MOCK_METHOD0(OnFinRead, void());
+  MOCK_METHOD0(OnDataAvailable, void());
+  MOCK_METHOD2(CloseConnectionWithDetails,
+               void(QuicErrorCode error, const QuicString& details));
+  MOCK_METHOD1(Reset, void(QuicRstStreamErrorCode error));
+  MOCK_METHOD0(OnCanWrite, void());
+  MOCK_METHOD1(AddBytesConsumed, void(QuicByteCount bytes));
+
+  QuicStreamId id() const override { return 1; }
+
+  const QuicSocketAddress& PeerAddressOfLatestPacket() const override {
+    return peer_address_;
+  }
+
+ protected:
+  QuicSocketAddress peer_address_ =
+      QuicSocketAddress(QuicIpAddress::Any4(), 65535);
+};
+
+class MockSequencer : public QuicStreamSequencer {
+ public:
+  explicit MockSequencer(MockStream* stream) : QuicStreamSequencer(stream) {}
+  virtual ~MockSequencer() = default;
+  MOCK_METHOD1(MarkConsumed, void(size_t num_bytes_consumed));
+};
+
+class QuicSpdyStreamBodyBufferTest : public QuicTest {
+ public:
+  QuicSpdyStreamBodyBufferTest()
+      : sequencer_(&stream_), body_buffer_(&sequencer_) {}
+
+ protected:
+  MockStream stream_;
+  MockSequencer sequencer_;
+  QuicSpdyStreamBodyBuffer body_buffer_;
+  HttpEncoder encoder_;
+};
+
+TEST_F(QuicSpdyStreamBodyBufferTest, ReceiveBodies) {
+  QuicString body(1024, 'a');
+  EXPECT_FALSE(body_buffer_.HasBytesToRead());
+  body_buffer_.OnDataHeader(Http3FrameLengths(3, 1024));
+  body_buffer_.OnDataPayload(QuicStringPiece(body));
+  EXPECT_EQ(1024u, body_buffer_.total_body_bytes_received());
+  EXPECT_TRUE(body_buffer_.HasBytesToRead());
+}
+
+TEST_F(QuicSpdyStreamBodyBufferTest, PeekBody) {
+  QuicString body(1024, 'a');
+  body_buffer_.OnDataHeader(Http3FrameLengths(3, 1024));
+  body_buffer_.OnDataPayload(QuicStringPiece(body));
+  EXPECT_EQ(1024u, body_buffer_.total_body_bytes_received());
+  iovec vec;
+  EXPECT_EQ(1, body_buffer_.PeekBody(&vec, 1));
+  EXPECT_EQ(1024u, vec.iov_len);
+  EXPECT_EQ(body,
+            QuicStringPiece(static_cast<const char*>(vec.iov_base), 1024));
+}
+
+// Buffer only receives 1 frame. Stream consumes less or equal than a frame.
+TEST_F(QuicSpdyStreamBodyBufferTest, MarkConsumedPartialSingleFrame) {
+  testing::InSequence seq;
+  QuicString body(1024, 'a');
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  Http3FrameLengths lengths(header_length, 1024);
+  QuicString data = header + body;
+  QuicStreamFrame frame(1, false, 0, data);
+  sequencer_.OnStreamFrame(frame);
+  body_buffer_.OnDataHeader(lengths);
+  body_buffer_.OnDataPayload(QuicStringPiece(body));
+  EXPECT_CALL(stream_, AddBytesConsumed(header_length));
+  EXPECT_CALL(stream_, AddBytesConsumed(1024));
+  body_buffer_.MarkBodyConsumed(1024);
+}
+
+// Buffer received 2 frames. Stream consumes multiple times.
+TEST_F(QuicSpdyStreamBodyBufferTest, MarkConsumedMultipleFrames) {
+  testing::InSequence seq;
+  // 1st frame.
+  QuicString body1(1024, 'a');
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length1 =
+      encoder_.SerializeDataFrameHeader(body1.length(), &buffer);
+  QuicString header1 = QuicString(buffer.get(), header_length1);
+  Http3FrameLengths lengths1(header_length1, 1024);
+  QuicString data1 = header1 + body1;
+  QuicStreamFrame frame1(1, false, 0, data1);
+  sequencer_.OnStreamFrame(frame1);
+  body_buffer_.OnDataHeader(lengths1);
+  body_buffer_.OnDataPayload(QuicStringPiece(body1));
+
+  // 2nd frame.
+  QuicString body2(2048, 'b');
+  QuicByteCount header_length2 =
+      encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+  QuicString header2 = QuicString(buffer.get(), header_length2);
+  Http3FrameLengths lengths2(header_length2, 2048);
+  QuicString data2 = header2 + body2;
+  QuicStreamFrame frame2(1, false, data1.length(), data2);
+  sequencer_.OnStreamFrame(frame2);
+  body_buffer_.OnDataHeader(lengths2);
+  body_buffer_.OnDataPayload(QuicStringPiece(body2));
+
+  EXPECT_CALL(stream_, AddBytesConsumed(header_length1));
+  EXPECT_CALL(stream_, AddBytesConsumed(512));
+  body_buffer_.MarkBodyConsumed(512);
+  EXPECT_CALL(stream_, AddBytesConsumed(header_length2));
+  EXPECT_CALL(stream_, AddBytesConsumed(2048));
+  body_buffer_.MarkBodyConsumed(2048);
+  EXPECT_CALL(stream_, AddBytesConsumed(512));
+  body_buffer_.MarkBodyConsumed(512);
+}
+
+TEST_F(QuicSpdyStreamBodyBufferTest, MarkConsumedMoreThanBuffered) {
+  QuicString body(1024, 'a');
+  Http3FrameLengths lengths(3, 1024);
+  body_buffer_.OnDataHeader(lengths);
+  body_buffer_.OnDataPayload(body);
+  EXPECT_QUIC_BUG(
+      body_buffer_.MarkBodyConsumed(2048),
+      "Invalid argument to MarkBodyConsumed. expect to consume: 2048, but not "
+      "enough bytes available. Total bytes readable are: 1024");
+}
+
+// Buffer receives 1 frame. Stream read from the buffer.
+TEST_F(QuicSpdyStreamBodyBufferTest, ReadSingleBody) {
+  testing::InSequence seq;
+  QuicString body(1024, 'a');
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  Http3FrameLengths lengths(header_length, 1024);
+  QuicString data = header + body;
+  QuicStreamFrame frame(1, false, 0, data);
+  sequencer_.OnStreamFrame(frame);
+  body_buffer_.OnDataHeader(lengths);
+  body_buffer_.OnDataPayload(QuicStringPiece(body));
+
+  EXPECT_CALL(stream_, AddBytesConsumed(header_length));
+  EXPECT_CALL(stream_, AddBytesConsumed(1024));
+
+  char base[1024];
+  iovec iov = {&base[0], 1024};
+  EXPECT_EQ(1024u, body_buffer_.ReadBody(&iov, 1));
+  EXPECT_EQ(1024u, iov.iov_len);
+  EXPECT_EQ(body,
+            QuicStringPiece(static_cast<const char*>(iov.iov_base), 1024));
+}
+
+// Buffer receives 2 frames, stream read from the buffer multiple times.
+TEST_F(QuicSpdyStreamBodyBufferTest, ReadMultipleBody) {
+  testing::InSequence seq;
+  // 1st frame.
+  QuicString body1(1024, 'a');
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length1 =
+      encoder_.SerializeDataFrameHeader(body1.length(), &buffer);
+  QuicString header1 = QuicString(buffer.get(), header_length1);
+  Http3FrameLengths lengths1(header_length1, 1024);
+  QuicString data1 = header1 + body1;
+  QuicStreamFrame frame1(1, false, 0, data1);
+  sequencer_.OnStreamFrame(frame1);
+  body_buffer_.OnDataHeader(lengths1);
+  body_buffer_.OnDataPayload(QuicStringPiece(body1));
+
+  // 2nd frame.
+  QuicString body2(2048, 'b');
+  QuicByteCount header_length2 =
+      encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+  QuicString header2 = QuicString(buffer.get(), header_length2);
+  Http3FrameLengths lengths2(header_length2, 2048);
+  QuicString data2 = header2 + body2;
+  QuicStreamFrame frame2(1, false, data1.length(), data2);
+  sequencer_.OnStreamFrame(frame2);
+  body_buffer_.OnDataHeader(lengths2);
+  body_buffer_.OnDataPayload(QuicStringPiece(body2));
+
+  // First read of 512 bytes.
+  EXPECT_CALL(stream_, AddBytesConsumed(header_length1));
+  EXPECT_CALL(stream_, AddBytesConsumed(512));
+  char base[512];
+  iovec iov = {&base[0], 512};
+  EXPECT_EQ(512u, body_buffer_.ReadBody(&iov, 1));
+  EXPECT_EQ(512u, iov.iov_len);
+  EXPECT_EQ(body1.substr(0, 512),
+            QuicStringPiece(static_cast<const char*>(iov.iov_base), 512));
+
+  // Second read of 2048 bytes.
+  EXPECT_CALL(stream_, AddBytesConsumed(header_length2));
+  EXPECT_CALL(stream_, AddBytesConsumed(2048));
+  char base2[2048];
+  iovec iov2 = {&base2[0], 2048};
+  EXPECT_EQ(2048u, body_buffer_.ReadBody(&iov2, 1));
+  EXPECT_EQ(2048u, iov2.iov_len);
+  EXPECT_EQ(body1.substr(512, 512) + body2.substr(0, 1536),
+            QuicStringPiece(static_cast<const char*>(iov2.iov_base), 2048));
+
+  // Third read of the rest 512 bytes.
+  EXPECT_CALL(stream_, AddBytesConsumed(512));
+  char base3[512];
+  iovec iov3 = {&base3[0], 512};
+  EXPECT_EQ(512u, body_buffer_.ReadBody(&iov3, 1));
+  EXPECT_EQ(512u, iov3.iov_len);
+  EXPECT_EQ(body2.substr(1536, 512),
+            QuicStringPiece(static_cast<const char*>(iov3.iov_base), 512));
+}
+
+}  // anonymous namespace
+
+}  // namespace test
+
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
new file mode 100644
index 0000000..1aabe0e
--- /dev/null
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -0,0 +1,1518 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h"
+
+#include <memory>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer_buffer.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_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_map_util.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.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_stream_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+using spdy::kV3HighestPriority;
+using spdy::kV3LowestPriority;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyPriority;
+using testing::_;
+using testing::AtLeast;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+
+const bool kShouldProcessData = true;
+
+class TestStream : public QuicSpdyStream {
+ public:
+  TestStream(QuicStreamId id,
+             QuicSpdySession* session,
+             bool should_process_data)
+      : QuicSpdyStream(id, session, BIDIRECTIONAL),
+        should_process_data_(should_process_data) {}
+  ~TestStream() override = default;
+
+  using QuicSpdyStream::set_ack_listener;
+  using QuicStream::CloseWriteSide;
+  using QuicStream::WriteOrBufferData;
+
+  void OnBodyAvailable() override {
+    if (!should_process_data_) {
+      return;
+    }
+    char buffer[2048];
+    struct iovec vec;
+    vec.iov_base = buffer;
+    vec.iov_len = QUIC_ARRAYSIZE(buffer);
+    size_t bytes_read = Readv(&vec, 1);
+    data_ += QuicString(buffer, bytes_read);
+  }
+
+  MOCK_METHOD1(WriteHeadersMock, void(bool fin));
+
+  size_t WriteHeadersImpl(spdy::SpdyHeaderBlock header_block,
+                          bool fin,
+                          QuicReferenceCountedPointer<QuicAckListenerInterface>
+                              ack_listener) override {
+    saved_headers_ = std::move(header_block);
+    WriteHeadersMock(fin);
+    return 0;
+  }
+
+  const QuicString& data() const { return data_; }
+  const spdy::SpdyHeaderBlock& saved_headers() const { return saved_headers_; }
+
+ private:
+  bool should_process_data_;
+  spdy::SpdyHeaderBlock saved_headers_;
+  QuicString data_;
+};
+
+class TestMockUpdateStreamSession : public MockQuicSpdySession {
+ public:
+  explicit TestMockUpdateStreamSession(QuicConnection* connection)
+      : MockQuicSpdySession(connection) {}
+
+  void UpdateStreamPriority(QuicStreamId id, SpdyPriority priority) override {
+    EXPECT_EQ(id, expected_stream_->id());
+    EXPECT_EQ(expected_priority_, priority);
+    EXPECT_EQ(expected_priority_, expected_stream_->priority());
+  }
+
+  void SetExpectedStream(QuicSpdyStream* stream) { expected_stream_ = stream; }
+  void SetExpectedPriority(SpdyPriority priority) {
+    expected_priority_ = priority;
+  }
+
+ private:
+  QuicSpdyStream* expected_stream_;
+  SpdyPriority expected_priority_;
+};
+
+class QuicSpdyStreamTest : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+  QuicSpdyStreamTest() {
+    headers_[":host"] = "www.google.com";
+    headers_[":path"] = "/index.hml";
+    headers_[":scheme"] = "https";
+    headers_["cookie"] =
+        "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+        "__utmc=160408618; "
+        "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+        "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+        "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+        "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+        "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+        "1zFMi5vzcns38-8_Sns; "
+        "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+        "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+        "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+        "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+        "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+        "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+        "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+        "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+        "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+        "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+        "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+        "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+        "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+        "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+        "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
+  }
+
+  void Initialize(bool stream_should_process_data) {
+    connection_ = new StrictMock<MockQuicConnection>(
+        &helper_, &alarm_factory_, Perspective::IS_SERVER,
+        SupportedVersions(GetParam()));
+    session_ = QuicMakeUnique<StrictMock<MockQuicSpdySession>>(connection_);
+    session_->Initialize();
+    ON_CALL(*session_, WritevData(_, _, _, _, _))
+        .WillByDefault(Invoke(MockQuicSession::ConsumeData));
+
+    stream_ =
+        new StrictMock<TestStream>(GetNthClientInitiatedBidirectionalId(0),
+                                   session_.get(), stream_should_process_data);
+    session_->ActivateStream(QuicWrapUnique(stream_));
+    stream2_ =
+        new StrictMock<TestStream>(GetNthClientInitiatedBidirectionalId(1),
+                                   session_.get(), stream_should_process_data);
+    session_->ActivateStream(QuicWrapUnique(stream2_));
+  }
+
+  QuicHeaderList ProcessHeaders(bool fin, const SpdyHeaderBlock& headers) {
+    QuicHeaderList h = AsHeaderList(headers);
+    stream_->OnStreamHeaderList(fin, h.uncompressed_header_bytes(), h);
+    return h;
+  }
+
+  QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+    return GetNthClientInitiatedBidirectionalStreamId(
+        connection_->transport_version(), n);
+  }
+
+  bool HasFrameHeader() const {
+    return VersionHasDataFrameHeader(connection_->transport_version());
+  }
+
+ protected:
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  MockQuicConnection* connection_;
+  std::unique_ptr<MockQuicSpdySession> session_;
+
+  // Owned by the |session_|.
+  TestStream* stream_;
+  TestStream* stream2_;
+
+  SpdyHeaderBlock headers_;
+
+  HttpEncoder encoder_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests, QuicSpdyStreamTest,
+                         ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSpdyStreamTest, ProcessHeaderList) {
+  Initialize(kShouldProcessData);
+
+  stream_->OnStreamHeadersPriority(kV3HighestPriority);
+  ProcessHeaders(false, headers_);
+  EXPECT_EQ("", stream_->data());
+  EXPECT_FALSE(stream_->header_list().empty());
+  EXPECT_FALSE(stream_->IsDoneReading());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessTooLargeHeaderList) {
+  Initialize(kShouldProcessData);
+
+  QuicHeaderList headers;
+  stream_->OnStreamHeadersPriority(kV3HighestPriority);
+
+  EXPECT_CALL(*session_,
+              SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0));
+  stream_->OnStreamHeaderList(false, 1 << 20, headers);
+  EXPECT_EQ(QUIC_HEADERS_TOO_LARGE, stream_->stream_error());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeaderListWithFin) {
+  Initialize(kShouldProcessData);
+
+  size_t total_bytes = 0;
+  QuicHeaderList headers;
+  for (auto p : headers_) {
+    headers.OnHeader(p.first, p.second);
+    total_bytes += p.first.size() + p.second.size();
+  }
+  stream_->OnStreamHeadersPriority(kV3HighestPriority);
+  stream_->OnStreamHeaderList(true, total_bytes, headers);
+  EXPECT_EQ("", stream_->data());
+  EXPECT_FALSE(stream_->header_list().empty());
+  EXPECT_FALSE(stream_->IsDoneReading());
+  EXPECT_TRUE(stream_->HasFinalReceivedByteOffset());
+}
+
+TEST_P(QuicSpdyStreamTest, ParseHeaderStatusCode) {
+  // A valid status code should be 3-digit integer. The first digit should be in
+  // the range of [1, 5]. All the others are invalid.
+  Initialize(kShouldProcessData);
+  int status_code = 0;
+
+  // Valid status codes.
+  headers_[":status"] = "404";
+  EXPECT_TRUE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+  EXPECT_EQ(404, status_code);
+
+  headers_[":status"] = "100";
+  EXPECT_TRUE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+  EXPECT_EQ(100, status_code);
+
+  headers_[":status"] = "599";
+  EXPECT_TRUE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+  EXPECT_EQ(599, status_code);
+
+  // Invalid status codes.
+  headers_[":status"] = "010";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = "600";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = "200 ok";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = "2000";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = "+200";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = "+20";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = "-10";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = "-100";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  // Leading or trailing spaces are also invalid.
+  headers_[":status"] = " 200";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = "200 ";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = " 200 ";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+
+  headers_[":status"] = "  ";
+  EXPECT_FALSE(stream_->ParseHeaderStatusCode(headers_, &status_code));
+}
+
+TEST_P(QuicSpdyStreamTest, MarkHeadersConsumed) {
+  Initialize(kShouldProcessData);
+
+  QuicString body = "this is the body";
+  QuicHeaderList headers = ProcessHeaders(false, headers_);
+  EXPECT_EQ(headers, stream_->header_list());
+
+  stream_->ConsumeHeaderList();
+  EXPECT_EQ(QuicHeaderList(), stream_->header_list());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBody) {
+  Initialize(kShouldProcessData);
+
+  QuicString body = "this is the body";
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  EXPECT_EQ("", stream_->data());
+  QuicHeaderList headers = ProcessHeaders(false, headers_);
+  EXPECT_EQ(headers, stream_->header_list());
+  stream_->ConsumeHeaderList();
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece(data));
+  stream_->OnStreamFrame(frame);
+  EXPECT_EQ(QuicHeaderList(), stream_->header_list());
+  EXPECT_EQ(body, stream_->data());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyFragments) {
+  Initialize(kShouldProcessData);
+  QuicString body = "this is the body";
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  for (size_t fragment_size = 1; fragment_size < data.size(); ++fragment_size) {
+    Initialize(kShouldProcessData);
+    QuicHeaderList headers = ProcessHeaders(false, headers_);
+    ASSERT_EQ(headers, stream_->header_list());
+    stream_->ConsumeHeaderList();
+    for (size_t offset = 0; offset < data.size(); offset += fragment_size) {
+      size_t remaining_data = data.size() - offset;
+      QuicStringPiece fragment(data.data() + offset,
+                               std::min(fragment_size, remaining_data));
+      QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false,
+                            offset, QuicStringPiece(fragment));
+      stream_->OnStreamFrame(frame);
+    }
+    ASSERT_EQ(body, stream_->data()) << "fragment_size: " << fragment_size;
+  }
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyFragmentsSplit) {
+  Initialize(kShouldProcessData);
+  QuicString body = "this is the body";
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  for (size_t split_point = 1; split_point < data.size() - 1; ++split_point) {
+    Initialize(kShouldProcessData);
+    QuicHeaderList headers = ProcessHeaders(false, headers_);
+    ASSERT_EQ(headers, stream_->header_list());
+    stream_->ConsumeHeaderList();
+
+    QuicStringPiece fragment1(data.data(), split_point);
+    QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                           QuicStringPiece(fragment1));
+    stream_->OnStreamFrame(frame1);
+
+    QuicStringPiece fragment2(data.data() + split_point,
+                              data.size() - split_point);
+    QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(0), false,
+                           split_point, QuicStringPiece(fragment2));
+    stream_->OnStreamFrame(frame2);
+
+    ASSERT_EQ(body, stream_->data()) << "split_point: " << split_point;
+  }
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyReadv) {
+  Initialize(!kShouldProcessData);
+
+  QuicString body = "this is the body";
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  ProcessHeaders(false, headers_);
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece(data));
+  stream_->OnStreamFrame(frame);
+  stream_->ConsumeHeaderList();
+
+  char buffer[2048];
+  ASSERT_LT(data.length(), QUIC_ARRAYSIZE(buffer));
+  struct iovec vec;
+  vec.iov_base = buffer;
+  vec.iov_len = QUIC_ARRAYSIZE(buffer);
+
+  size_t bytes_read = stream_->Readv(&vec, 1);
+  EXPECT_EQ(body.length(), bytes_read);
+  EXPECT_EQ(body, QuicString(buffer, bytes_read));
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndLargeBodySmallReadv) {
+  Initialize(kShouldProcessData);
+  QuicString body(12 * 1024, 'a');
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+  ProcessHeaders(false, headers_);
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece(data));
+  stream_->OnStreamFrame(frame);
+  stream_->ConsumeHeaderList();
+  char buffer[2048];
+  char buffer2[2048];
+  struct iovec vec[2];
+  vec[0].iov_base = buffer;
+  vec[0].iov_len = QUIC_ARRAYSIZE(buffer);
+  vec[1].iov_base = buffer2;
+  vec[1].iov_len = QUIC_ARRAYSIZE(buffer2);
+  size_t bytes_read = stream_->Readv(vec, 2);
+  EXPECT_EQ(2048u * 2, bytes_read);
+  EXPECT_EQ(body.substr(0, 2048), QuicString(buffer, 2048));
+  EXPECT_EQ(body.substr(2048, 2048), QuicString(buffer2, 2048));
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyMarkConsumed) {
+  Initialize(!kShouldProcessData);
+
+  QuicString body = "this is the body";
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  ProcessHeaders(false, headers_);
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece(data));
+  stream_->OnStreamFrame(frame);
+  stream_->ConsumeHeaderList();
+
+  struct iovec vec;
+
+  EXPECT_EQ(1, stream_->GetReadableRegions(&vec, 1));
+  EXPECT_EQ(body.length(), vec.iov_len);
+  EXPECT_EQ(body, QuicString(static_cast<char*>(vec.iov_base), vec.iov_len));
+
+  stream_->MarkConsumed(body.length());
+  EXPECT_EQ(data.length(), stream_->flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndConsumeMultipleBody) {
+  Initialize(!kShouldProcessData);
+  QuicString body1 = "this is body 1";
+  QuicString body2 = "body 2";
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body1.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data1 = HasFrameHeader() ? header + body1 : body1;
+  header_length = encoder_.SerializeDataFrameHeader(body2.length(), &buf);
+  QuicString data2 = HasFrameHeader() ? header + body2 : body2;
+
+  ProcessHeaders(false, headers_);
+  QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                         QuicStringPiece(data1));
+  QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(0), false,
+                         data1.length(), QuicStringPiece(data2));
+  stream_->OnStreamFrame(frame1);
+  stream_->OnStreamFrame(frame2);
+  stream_->ConsumeHeaderList();
+
+  stream_->MarkConsumed(body1.length() + body2.length());
+  EXPECT_EQ(data1.length() + data2.length(),
+            stream_->flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyIncrementalReadv) {
+  Initialize(!kShouldProcessData);
+
+  QuicString body = "this is the body";
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  ProcessHeaders(false, headers_);
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece(data));
+  stream_->OnStreamFrame(frame);
+  stream_->ConsumeHeaderList();
+
+  char buffer[1];
+  struct iovec vec;
+  vec.iov_base = buffer;
+  vec.iov_len = QUIC_ARRAYSIZE(buffer);
+
+  for (size_t i = 0; i < body.length(); ++i) {
+    size_t bytes_read = stream_->Readv(&vec, 1);
+    ASSERT_EQ(1u, bytes_read);
+    EXPECT_EQ(body.data()[i], buffer[0]);
+  }
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) {
+  Initialize(!kShouldProcessData);
+
+  QuicString body = "this is the body";
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  ProcessHeaders(false, headers_);
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece(data));
+  stream_->OnStreamFrame(frame);
+  stream_->ConsumeHeaderList();
+
+  char buffer1[1];
+  char buffer2[1];
+  struct iovec vec[2];
+  vec[0].iov_base = buffer1;
+  vec[0].iov_len = QUIC_ARRAYSIZE(buffer1);
+  vec[1].iov_base = buffer2;
+  vec[1].iov_len = QUIC_ARRAYSIZE(buffer2);
+
+  for (size_t i = 0; i < body.length(); i += 2) {
+    size_t bytes_read = stream_->Readv(vec, 2);
+    ASSERT_EQ(2u, bytes_read) << i;
+    ASSERT_EQ(body.data()[i], buffer1[0]) << i;
+    ASSERT_EQ(body.data()[i + 1], buffer2[0]) << i;
+  }
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlBlocked) {
+  testing::InSequence seq;
+  // Tests that we send a BLOCKED frame to the peer when we attempt to write,
+  // but are flow control blocked.
+  Initialize(kShouldProcessData);
+
+  // Set a small flow control limit.
+  const uint64_t kWindow = 36;
+  QuicFlowControllerPeer::SetSendWindowOffset(stream_->flow_controller(),
+                                              kWindow);
+  EXPECT_EQ(kWindow, QuicFlowControllerPeer::SendWindowOffset(
+                         stream_->flow_controller()));
+
+  // Try to send more data than the flow control limit allows.
+  const uint64_t kOverflow = 15;
+  QuicString body(kWindow + kOverflow, 'a');
+
+  const uint64_t kHeaderLength = HasFrameHeader() ? 2 : 0;
+  if (HasFrameHeader()) {
+    EXPECT_CALL(*session_, WritevData(_, _, kHeaderLength, _, NO_FIN));
+  }
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(kWindow - kHeaderLength, true)));
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  stream_->WriteOrBufferBody(body, false);
+
+  // Should have sent as much as possible, resulting in no send window left.
+  EXPECT_EQ(0u,
+            QuicFlowControllerPeer::SendWindowSize(stream_->flow_controller()));
+
+  // And we should have queued the overflowed data.
+  EXPECT_EQ(kOverflow + kHeaderLength,
+            QuicStreamPeer::SizeOfQueuedData(stream_));
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlNoWindowUpdateIfNotConsumed) {
+  // The flow control receive window decreases whenever we add new bytes to the
+  // sequencer, whether they are consumed immediately or buffered. However we
+  // only send WINDOW_UPDATE frames based on increasing number of bytes
+  // consumed.
+
+  // Don't process data - it will be buffered instead.
+  Initialize(!kShouldProcessData);
+
+  // Expect no WINDOW_UPDATE frames to be sent.
+  EXPECT_CALL(*connection_, SendWindowUpdate(_, _)).Times(0);
+
+  // Set a small flow control receive window.
+  const uint64_t kWindow = 36;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
+                                              kWindow);
+  EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowOffset(
+                         stream_->flow_controller()));
+
+  // Stream receives enough data to fill a fraction of the receive window.
+  QuicString body(kWindow / 3, 'a');
+  QuicByteCount header_length = 0;
+  QuicString data;
+
+  if (HasFrameHeader()) {
+    std::unique_ptr<char[]> buffer;
+    header_length = encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+    QuicString header = QuicString(buffer.get(), header_length);
+    data = header + body;
+  } else {
+    data = body;
+  }
+
+  ProcessHeaders(false, headers_);
+
+  QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                         QuicStringPiece(data));
+  stream_->OnStreamFrame(frame1);
+  EXPECT_EQ(
+      kWindow - (kWindow / 3) - header_length,
+      QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+
+  // Now receive another frame which results in the receive window being over
+  // half full. This should all be buffered, decreasing the receive window but
+  // not sending WINDOW_UPDATE.
+  QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(0), false,
+                         kWindow / 3 + header_length, QuicStringPiece(data));
+  stream_->OnStreamFrame(frame2);
+  EXPECT_EQ(
+      kWindow - (2 * kWindow / 3) - 2 * header_length,
+      QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlWindowUpdate) {
+  // Tests that on receipt of data, the stream updates its receive window offset
+  // appropriately, and sends WINDOW_UPDATE frames when its receive window drops
+  // too low.
+  Initialize(kShouldProcessData);
+
+  // Set a small flow control limit.
+  const uint64_t kWindow = 36;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
+                                              kWindow);
+  EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowOffset(
+                         stream_->flow_controller()));
+
+  // Stream receives enough data to fill a fraction of the receive window.
+  QuicString body(kWindow / 3, 'a');
+  QuicByteCount header_length = 0;
+  QuicString data;
+
+  if (HasFrameHeader()) {
+    std::unique_ptr<char[]> buffer;
+    header_length = encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+    QuicString header = QuicString(buffer.get(), header_length);
+    data = header + body;
+  } else {
+    data = body;
+  }
+
+  ProcessHeaders(false, headers_);
+  stream_->ConsumeHeaderList();
+
+  QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                         QuicStringPiece(data));
+  stream_->OnStreamFrame(frame1);
+  EXPECT_EQ(
+      kWindow - (kWindow / 3) - header_length,
+      QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+
+  // Now receive another frame which results in the receive window being over
+  // half full.  This will trigger the stream to increase its receive window
+  // offset and send a WINDOW_UPDATE. The result will be again an available
+  // window of kWindow bytes.
+  QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(0), false,
+                         kWindow / 3 + header_length, QuicStringPiece(data));
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  stream_->OnStreamFrame(frame2);
+  EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowSize(
+                         stream_->flow_controller()));
+}
+
+TEST_P(QuicSpdyStreamTest, ConnectionFlowControlWindowUpdate) {
+  // Tests that on receipt of data, the connection updates its receive window
+  // offset appropriately, and sends WINDOW_UPDATE frames when its receive
+  // window drops too low.
+  Initialize(kShouldProcessData);
+
+  // Set a small flow control limit for streams and connection.
+  const uint64_t kWindow = 36;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
+                                              kWindow);
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream2_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(stream2_->flow_controller(),
+                                              kWindow);
+  QuicFlowControllerPeer::SetReceiveWindowOffset(session_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(session_->flow_controller(),
+                                              kWindow);
+
+  // Supply headers to both streams so that they are happy to receive data.
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                              headers);
+  stream_->ConsumeHeaderList();
+  stream2_->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
+                               headers);
+  stream2_->ConsumeHeaderList();
+
+  // Each stream gets a quarter window of data. This should not trigger a
+  // WINDOW_UPDATE for either stream, nor for the connection.
+  QuicByteCount header_length = 0;
+  QuicString body;
+  QuicString data;
+  QuicString data2;
+  QuicString body2(1, 'a');
+
+  if (HasFrameHeader()) {
+    body = QuicString(kWindow / 4 - 2, 'a');
+    std::unique_ptr<char[]> buffer;
+    header_length = encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+    QuicString header = QuicString(buffer.get(), header_length);
+    data = header + body;
+    std::unique_ptr<char[]> buffer2;
+    QuicByteCount header_length2 =
+        encoder_.SerializeDataFrameHeader(body2.length(), &buffer2);
+    QuicString header2 = QuicString(buffer2.get(), header_length2);
+    data2 = header2 + body2;
+  } else {
+    body = QuicString(kWindow / 4, 'a');
+    data = body;
+    data2 = body2;
+  }
+
+  QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                         QuicStringPiece(data));
+  stream_->OnStreamFrame(frame1);
+  QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+                         QuicStringPiece(data));
+  stream2_->OnStreamFrame(frame2);
+
+  // Now receive a further single byte on one stream - again this does not
+  // trigger a stream WINDOW_UPDATE, but now the connection flow control window
+  // is over half full and thus a connection WINDOW_UPDATE is sent.
+  EXPECT_CALL(*connection_, SendControlFrame(_));
+  QuicStreamFrame frame3(GetNthClientInitiatedBidirectionalId(0), false,
+                         body.length() + header_length, QuicStringPiece(data2));
+  stream_->OnStreamFrame(frame3);
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlViolation) {
+  // Tests that on if the peer sends too much data (i.e. violates the flow
+  // control protocol), then we terminate the connection.
+
+  // Stream should not process data, so that data gets buffered in the
+  // sequencer, triggering flow control limits.
+  Initialize(!kShouldProcessData);
+
+  // Set a small flow control limit.
+  const uint64_t kWindow = 50;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kWindow);
+
+  ProcessHeaders(false, headers_);
+
+  // Receive data to overflow the window, violating flow control.
+  QuicString body(kWindow + 1, 'a');
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece(data));
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA, _, _));
+  stream_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicSpdyStreamTest, TestHandlingQuicRstStreamNoError) {
+  Initialize(kShouldProcessData);
+  ProcessHeaders(false, headers_);
+
+  stream_->OnStreamReset(QuicRstStreamFrame(
+      kInvalidControlFrameId, stream_->id(), QUIC_STREAM_NO_ERROR, 0));
+  EXPECT_TRUE(stream_->write_side_closed());
+  EXPECT_FALSE(stream_->reading_stopped());
+}
+
+TEST_P(QuicSpdyStreamTest, ConnectionFlowControlViolation) {
+  // Tests that on if the peer sends too much data (i.e. violates the flow
+  // control protocol), at the connection level (rather than the stream level)
+  // then we terminate the connection.
+
+  // Stream should not process data, so that data gets buffered in the
+  // sequencer, triggering flow control limits.
+  Initialize(!kShouldProcessData);
+
+  // Set a small flow control window on streams, and connection.
+  const uint64_t kStreamWindow = 50;
+  const uint64_t kConnectionWindow = 10;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kStreamWindow);
+  QuicFlowControllerPeer::SetReceiveWindowOffset(session_->flow_controller(),
+                                                 kConnectionWindow);
+
+  ProcessHeaders(false, headers_);
+
+  // Send enough data to overflow the connection level flow control window.
+  QuicString body(kConnectionWindow + 1, 'a');
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  EXPECT_LT(data.size(), kStreamWindow);
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), false, 0,
+                        QuicStringPiece(data));
+
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA, _, _));
+  stream_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicSpdyStreamTest, StreamFlowControlFinNotBlocked) {
+  // An attempt to write a FIN with no data should not be flow control blocked,
+  // even if the send window is 0.
+
+  Initialize(kShouldProcessData);
+
+  // Set a flow control limit of zero.
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(), 0);
+  EXPECT_EQ(0u, QuicFlowControllerPeer::ReceiveWindowOffset(
+                    stream_->flow_controller()));
+
+  // Send a frame with a FIN but no data. This should not be blocked.
+  QuicString body = "";
+  bool fin = true;
+
+  EXPECT_CALL(*connection_,
+              SendBlocked(GetNthClientInitiatedBidirectionalId(0)))
+      .Times(0);
+  EXPECT_CALL(*session_, WritevData(_, _, 0, _, FIN));
+
+  stream_->WriteOrBufferBody(body, fin);
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersViaHeaderList) {
+  // Test that receiving trailing headers from the peer via
+  // OnStreamHeaderList() works, and can be read from the stream and consumed.
+  Initialize(kShouldProcessData);
+
+  // Receive initial headers.
+  size_t total_bytes = 0;
+  QuicHeaderList headers;
+  for (const auto& p : headers_) {
+    headers.OnHeader(p.first, p.second);
+    total_bytes += p.first.size() + p.second.size();
+  }
+
+  stream_->OnStreamHeadersPriority(kV3HighestPriority);
+  stream_->OnStreamHeaderList(/*fin=*/false, total_bytes, headers);
+  stream_->ConsumeHeaderList();
+
+  // Receive trailing headers.
+  SpdyHeaderBlock trailers_block;
+  trailers_block["key1"] = "value1";
+  trailers_block["key2"] = "value2";
+  trailers_block["key3"] = "value3";
+  SpdyHeaderBlock trailers_block_with_final_offset = trailers_block.Clone();
+  trailers_block_with_final_offset[kFinalOffsetHeaderKey] = "0";
+  total_bytes = 0;
+  QuicHeaderList trailers;
+  for (const auto& p : trailers_block_with_final_offset) {
+    trailers.OnHeader(p.first, p.second);
+    total_bytes += p.first.size() + p.second.size();
+  }
+  stream_->OnStreamHeaderList(/*fin=*/true, total_bytes, trailers);
+
+  // The trailers should be decompressed, and readable from the stream.
+  EXPECT_TRUE(stream_->trailers_decompressed());
+  EXPECT_EQ(trailers_block, stream_->received_trailers());
+
+  // IsDoneReading() returns false until trailers marked consumed.
+  EXPECT_FALSE(stream_->IsDoneReading());
+  stream_->MarkTrailersConsumed();
+  EXPECT_TRUE(stream_->IsDoneReading());
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersWithOffset) {
+  // Test that when receiving trailing headers with an offset before response
+  // body, stream is closed at the right offset.
+  Initialize(kShouldProcessData);
+
+  // Receive initial headers.
+  QuicHeaderList headers = ProcessHeaders(false, headers_);
+  stream_->ConsumeHeaderList();
+
+  const QuicString body = "this is the body";
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  // Receive trailing headers.
+  SpdyHeaderBlock trailers_block;
+  trailers_block["key1"] = "value1";
+  trailers_block["key2"] = "value2";
+  trailers_block["key3"] = "value3";
+  trailers_block[kFinalOffsetHeaderKey] =
+      QuicTextUtils::Uint64ToString(data.size());
+
+  QuicHeaderList trailers = ProcessHeaders(true, trailers_block);
+
+  // The trailers should be decompressed, and readable from the stream.
+  EXPECT_TRUE(stream_->trailers_decompressed());
+
+  // The final offset trailer will be consumed by QUIC.
+  trailers_block.erase(kFinalOffsetHeaderKey);
+  EXPECT_EQ(trailers_block, stream_->received_trailers());
+
+  // Consuming the trailers erases them from the stream.
+  stream_->MarkTrailersConsumed();
+  EXPECT_TRUE(stream_->FinishedReadingTrailers());
+
+  EXPECT_FALSE(stream_->IsDoneReading());
+  // Receive and consume body.
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), /*fin=*/false,
+                        0, data);
+  stream_->OnStreamFrame(frame);
+  EXPECT_EQ(body, stream_->data());
+  EXPECT_TRUE(stream_->IsDoneReading());
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersWithoutOffset) {
+  // Test that receiving trailers without a final offset field is an error.
+  Initialize(kShouldProcessData);
+
+  // Receive initial headers.
+  ProcessHeaders(false, headers_);
+  stream_->ConsumeHeaderList();
+
+  // Receive trailing headers, without kFinalOffsetHeaderKey.
+  SpdyHeaderBlock trailers_block;
+  trailers_block["key1"] = "value1";
+  trailers_block["key2"] = "value2";
+  trailers_block["key3"] = "value3";
+  auto trailers = AsHeaderList(trailers_block);
+
+  // Verify that the trailers block didn't contain a final offset.
+  EXPECT_EQ("", trailers_block[kFinalOffsetHeaderKey].as_string());
+
+  // Receipt of the malformed trailers will close the connection.
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+      .Times(1);
+  stream_->OnStreamHeaderList(/*fin=*/true,
+                              trailers.uncompressed_header_bytes(), trailers);
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersWithoutFin) {
+  // Test that received Trailers must always have the FIN set.
+  Initialize(kShouldProcessData);
+
+  // Receive initial headers.
+  auto headers = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(/*fin=*/false,
+                              headers.uncompressed_header_bytes(), headers);
+  stream_->ConsumeHeaderList();
+
+  // Receive trailing headers with FIN deliberately set to false.
+  SpdyHeaderBlock trailers_block;
+  trailers_block["foo"] = "bar";
+  auto trailers = AsHeaderList(trailers_block);
+
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+      .Times(1);
+  stream_->OnStreamHeaderList(/*fin=*/false,
+                              trailers.uncompressed_header_bytes(), trailers);
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersAfterHeadersWithFin) {
+  // If headers are received with a FIN, no trailers should then arrive.
+  Initialize(kShouldProcessData);
+
+  // Receive initial headers with FIN set.
+  ProcessHeaders(true, headers_);
+  stream_->ConsumeHeaderList();
+
+  // Receive trailing headers after FIN already received.
+  SpdyHeaderBlock trailers_block;
+  trailers_block["foo"] = "bar";
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+      .Times(1);
+  ProcessHeaders(true, trailers_block);
+}
+
+TEST_P(QuicSpdyStreamTest, ReceivingTrailersAfterBodyWithFin) {
+  // If body data are received with a FIN, no trailers should then arrive.
+  Initialize(kShouldProcessData);
+
+  // Receive initial headers without FIN set.
+  ProcessHeaders(false, headers_);
+  stream_->ConsumeHeaderList();
+
+  // Receive body data, with FIN.
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), /*fin=*/true,
+                        0, "body");
+  stream_->OnStreamFrame(frame);
+
+  // Receive trailing headers after FIN already received.
+  SpdyHeaderBlock trailers_block;
+  trailers_block["foo"] = "bar";
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _))
+      .Times(1);
+  ProcessHeaders(true, trailers_block);
+}
+
+TEST_P(QuicSpdyStreamTest, ClosingStreamWithNoTrailers) {
+  // Verify that a stream receiving headers, body, and no trailers is correctly
+  // marked as done reading on consumption of headers and body.
+  Initialize(kShouldProcessData);
+
+  // Receive and consume initial headers with FIN not set.
+  auto h = AsHeaderList(headers_);
+  stream_->OnStreamHeaderList(/*fin=*/false, h.uncompressed_header_bytes(), h);
+  stream_->ConsumeHeaderList();
+
+  // Receive and consume body with FIN set, and no trailers.
+  QuicString body(1024, 'x');
+  std::unique_ptr<char[]> buf;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  QuicString header = QuicString(buf.get(), header_length);
+  QuicString data = HasFrameHeader() ? header + body : body;
+
+  QuicStreamFrame frame(GetNthClientInitiatedBidirectionalId(0), /*fin=*/true,
+                        0, data);
+  stream_->OnStreamFrame(frame);
+
+  EXPECT_TRUE(stream_->IsDoneReading());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersSendsAFin) {
+  // Test that writing trailers will send a FIN, as Trailers are the last thing
+  // to be sent on a stream.
+  Initialize(kShouldProcessData);
+
+  // Write the initial headers, without a FIN.
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+  // Writing trailers implicitly sends a FIN.
+  SpdyHeaderBlock trailers;
+  trailers["trailer key"] = "trailer value";
+  EXPECT_CALL(*stream_, WriteHeadersMock(true));
+  stream_->WriteTrailers(std::move(trailers), nullptr);
+  EXPECT_TRUE(stream_->fin_sent());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersFinalOffset) {
+  // Test that when writing trailers, the trailers that are actually sent to the
+  // peer contain the final offset field indicating last byte of data.
+  Initialize(kShouldProcessData);
+
+  // Write the initial headers.
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+  // Write non-zero body data to force a non-zero final offset.
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+  QuicString body(1024, 'x');  // 1 kB
+  QuicByteCount header_length = 0;
+  if (HasFrameHeader()) {
+    std::unique_ptr<char[]> buf;
+    header_length = encoder_.SerializeDataFrameHeader(body.length(), &buf);
+  }
+
+  stream_->WriteOrBufferBody(body, false);
+
+  // The final offset field in the trailing headers is populated with the
+  // number of body bytes written (including queued bytes).
+  SpdyHeaderBlock trailers;
+  trailers["trailer key"] = "trailer value";
+  SpdyHeaderBlock trailers_with_offset(trailers.Clone());
+  trailers_with_offset[kFinalOffsetHeaderKey] =
+      QuicTextUtils::Uint64ToString(body.length() + header_length);
+  EXPECT_CALL(*stream_, WriteHeadersMock(true));
+  stream_->WriteTrailers(std::move(trailers), nullptr);
+  EXPECT_EQ(trailers_with_offset, stream_->saved_headers());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersClosesWriteSide) {
+  // Test that if trailers are written after all other data has been written
+  // (headers and body), that this closes the stream for writing.
+  Initialize(kShouldProcessData);
+
+  // Write the initial headers.
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+  // Write non-zero body data.
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+  const int kBodySize = 1 * 1024;  // 1 kB
+  stream_->WriteOrBufferBody(QuicString(kBodySize, 'x'), false);
+  EXPECT_EQ(0u, stream_->BufferedDataBytes());
+
+  // Headers and body have been fully written, there is no queued data. Writing
+  // trailers marks the end of this stream, and thus the write side is closed.
+  EXPECT_CALL(*stream_, WriteHeadersMock(true));
+  stream_->WriteTrailers(SpdyHeaderBlock(), nullptr);
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersWithQueuedBytes) {
+  // Test that the stream is not closed for writing when trailers are sent
+  // while there are still body bytes queued.
+  testing::InSequence seq;
+  Initialize(kShouldProcessData);
+
+  // Write the initial headers.
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+  // Write non-zero body data, but only consume partially, ensuring queueing.
+  const int kBodySize = 1 * 1024;  // 1 kB
+  if (HasFrameHeader()) {
+    EXPECT_CALL(*session_, WritevData(_, _, 3, _, NO_FIN));
+  }
+  EXPECT_CALL(*session_, WritevData(_, _, kBodySize, _, NO_FIN))
+      .WillOnce(Return(QuicConsumedData(kBodySize - 1, false)));
+  stream_->WriteOrBufferBody(QuicString(kBodySize, 'x'), false);
+  EXPECT_EQ(1u, stream_->BufferedDataBytes());
+
+  // Writing trailers will send a FIN, but not close the write side of the
+  // stream as there are queued bytes.
+  EXPECT_CALL(*stream_, WriteHeadersMock(true));
+  stream_->WriteTrailers(SpdyHeaderBlock(), nullptr);
+  EXPECT_TRUE(stream_->fin_sent());
+  EXPECT_FALSE(stream_->write_side_closed());
+
+  // Writing the queued bytes will close the write side of the stream.
+  EXPECT_CALL(*session_, WritevData(_, _, 1, _, NO_FIN));
+  stream_->OnCanWrite();
+  EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_P(QuicSpdyStreamTest, WritingTrailersAfterFIN) {
+  // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+  if (GetParam() != AllSupportedVersions()[0]) {
+    return;
+  }
+
+  // Test that it is not possible to write Trailers after a FIN has been sent.
+  Initialize(kShouldProcessData);
+
+  // Write the initial headers, with a FIN.
+  EXPECT_CALL(*stream_, WriteHeadersMock(true));
+  stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/true, nullptr);
+  EXPECT_TRUE(stream_->fin_sent());
+
+  // Writing Trailers should fail, as the FIN has already been sent.
+  // populated with the number of body bytes written.
+  EXPECT_QUIC_BUG(stream_->WriteTrailers(SpdyHeaderBlock(), nullptr),
+                  "Trailers cannot be sent after a FIN");
+}
+
+TEST_P(QuicSpdyStreamTest, HeaderStreamNotiferCorrespondingSpdyStream) {
+  Initialize(kShouldProcessData);
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+  testing::InSequence s;
+  QuicReferenceCountedPointer<MockAckListener> ack_listener1(
+      new MockAckListener());
+  QuicReferenceCountedPointer<MockAckListener> ack_listener2(
+      new MockAckListener());
+  stream_->set_ack_listener(ack_listener1);
+  stream2_->set_ack_listener(ack_listener2);
+
+  session_->headers_stream()->WriteOrBufferData("Header1", false,
+                                                ack_listener1);
+  stream_->WriteOrBufferBody("Test1", true);
+
+  session_->headers_stream()->WriteOrBufferData("Header2", false,
+                                                ack_listener2);
+  stream2_->WriteOrBufferBody("Test2", false);
+
+  QuicStreamFrame frame1(
+      QuicUtils::GetHeadersStreamId(connection_->transport_version()), false, 0,
+      "Header1");
+  QuicString header = "";
+  if (HasFrameHeader()) {
+    std::unique_ptr<char[]> buffer;
+    QuicByteCount header_length = encoder_.SerializeDataFrameHeader(5, &buffer);
+    header = QuicString(buffer.get(), header_length);
+  }
+  QuicStreamFrame frame2(stream_->id(), true, 0, header + "Test1");
+  QuicStreamFrame frame3(
+      QuicUtils::GetHeadersStreamId(connection_->transport_version()), false, 7,
+      "Header2");
+  QuicStreamFrame frame4(stream2_->id(), false, 0, header + "Test2");
+
+  EXPECT_CALL(*ack_listener1, OnPacketRetransmitted(7));
+  session_->OnStreamFrameRetransmitted(frame1);
+
+  EXPECT_CALL(*ack_listener1, OnPacketAcked(7, _));
+  EXPECT_TRUE(
+      session_->OnFrameAcked(QuicFrame(frame1), QuicTime::Delta::Zero()));
+  EXPECT_CALL(*ack_listener1, OnPacketAcked(5, _));
+  EXPECT_TRUE(
+      session_->OnFrameAcked(QuicFrame(frame2), QuicTime::Delta::Zero()));
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _));
+  EXPECT_TRUE(
+      session_->OnFrameAcked(QuicFrame(frame3), QuicTime::Delta::Zero()));
+  EXPECT_CALL(*ack_listener2, OnPacketAcked(5, _));
+  EXPECT_TRUE(
+      session_->OnFrameAcked(QuicFrame(frame4), QuicTime::Delta::Zero()));
+}
+
+TEST_P(QuicSpdyStreamTest, StreamBecomesZombieWithWriteThatCloses) {
+  Initialize(kShouldProcessData);
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+  QuicStreamPeer::CloseReadSide(stream_);
+  // This write causes stream to be closed.
+  stream_->WriteOrBufferBody("Test1", true);
+  // stream_ has unacked data and should become zombie.
+  EXPECT_TRUE(QuicContainsKey(QuicSessionPeer::zombie_streams(session_.get()),
+                              stream_->id()));
+  EXPECT_TRUE(QuicSessionPeer::closed_streams(session_.get()).empty());
+}
+
+TEST_P(QuicSpdyStreamTest, OnPriorityFrame) {
+  Initialize(kShouldProcessData);
+  stream_->OnPriorityFrame(kV3HighestPriority);
+  EXPECT_EQ(kV3HighestPriority, stream_->priority());
+}
+
+TEST_P(QuicSpdyStreamTest, OnPriorityFrameAfterSendingData) {
+  testing::InSequence seq;
+  Initialize(kShouldProcessData);
+
+  if (HasFrameHeader()) {
+    EXPECT_CALL(*session_, WritevData(_, _, 2, _, NO_FIN));
+  }
+  EXPECT_CALL(*session_, WritevData(_, _, 4, _, FIN));
+  stream_->WriteOrBufferBody("data", true);
+  stream_->OnPriorityFrame(kV3HighestPriority);
+  EXPECT_EQ(kV3HighestPriority, stream_->priority());
+}
+
+TEST_P(QuicSpdyStreamTest, SetPriorityBeforeUpdateStreamPriority) {
+  MockQuicConnection* connection = new StrictMock<MockQuicConnection>(
+      &helper_, &alarm_factory_, Perspective::IS_SERVER,
+      SupportedVersions(GetParam()));
+  std::unique_ptr<TestMockUpdateStreamSession> session(
+      new StrictMock<TestMockUpdateStreamSession>(connection));
+  auto stream = new StrictMock<TestStream>(
+      GetNthClientInitiatedBidirectionalStreamId(
+          session->connection()->transport_version(), 0),
+      session.get(),
+      /*should_process_data=*/true);
+  session->ActivateStream(QuicWrapUnique(stream));
+
+  // QuicSpdyStream::SetPriority() should eventually call UpdateStreamPriority()
+  // on the session. Make sure stream->priority() returns the updated priority
+  // if called within UpdateStreamPriority(). This expectation is enforced in
+  // TestMockUpdateStreamSession::UpdateStreamPriority().
+  session->SetExpectedStream(stream);
+  session->SetExpectedPriority(kV3HighestPriority);
+  stream->SetPriority(kV3HighestPriority);
+
+  session->SetExpectedPriority(kV3LowestPriority);
+  stream->SetPriority(kV3LowestPriority);
+}
+
+TEST_P(QuicSpdyStreamTest, StreamWaitsForAcks) {
+  Initialize(kShouldProcessData);
+  QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+      new StrictMock<MockAckListener>);
+  stream_->set_ack_listener(mock_ack_listener);
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+  // Stream is not waiting for acks initially.
+  EXPECT_FALSE(stream_->IsWaitingForAcks());
+  EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+
+  // Send kData1.
+  stream_->WriteOrBufferData("FooAndBar", false, nullptr);
+  EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+  EXPECT_TRUE(stream_->IsWaitingForAcks());
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(9, _));
+  QuicByteCount newly_acked_length = 0;
+  EXPECT_TRUE(stream_->OnStreamFrameAcked(0, 9, false, QuicTime::Delta::Zero(),
+                                          &newly_acked_length));
+  // Stream is not waiting for acks as all sent data is acked.
+  EXPECT_FALSE(stream_->IsWaitingForAcks());
+  EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+
+  // Send kData2.
+  stream_->WriteOrBufferData("FooAndBar", false, nullptr);
+  EXPECT_TRUE(stream_->IsWaitingForAcks());
+  EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+  // Send FIN.
+  stream_->WriteOrBufferData("", true, nullptr);
+  // Fin only frame is not stored in send buffer.
+  EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+
+  // kData2 is retransmitted.
+  EXPECT_CALL(*mock_ack_listener, OnPacketRetransmitted(9));
+  stream_->OnStreamFrameRetransmitted(9, 9, false);
+
+  // kData2 is acked.
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(9, _));
+  EXPECT_TRUE(stream_->OnStreamFrameAcked(9, 9, false, QuicTime::Delta::Zero(),
+                                          &newly_acked_length));
+  // Stream is waiting for acks as FIN is not acked.
+  EXPECT_TRUE(stream_->IsWaitingForAcks());
+  EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+
+  // FIN is acked.
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(0, _));
+  EXPECT_TRUE(stream_->OnStreamFrameAcked(18, 0, true, QuicTime::Delta::Zero(),
+                                          &newly_acked_length));
+  EXPECT_FALSE(stream_->IsWaitingForAcks());
+  EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+}
+
+TEST_P(QuicSpdyStreamTest, StreamDataGetAckedMultipleTimes) {
+  Initialize(kShouldProcessData);
+  QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+      new StrictMock<MockAckListener>);
+  stream_->set_ack_listener(mock_ack_listener);
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+  // Send [0, 27) and fin.
+  stream_->WriteOrBufferData("FooAndBar", false, nullptr);
+  stream_->WriteOrBufferData("FooAndBar", false, nullptr);
+  stream_->WriteOrBufferData("FooAndBar", true, nullptr);
+
+  // Ack [0, 9), [5, 22) and [18, 26)
+  // Verify [0, 9) 9 bytes are acked.
+  QuicByteCount newly_acked_length = 0;
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(9, _));
+  EXPECT_TRUE(stream_->OnStreamFrameAcked(0, 9, false, QuicTime::Delta::Zero(),
+                                          &newly_acked_length));
+  EXPECT_EQ(2u, QuicStreamPeer::SendBuffer(stream_).size());
+  // Verify [9, 22) 13 bytes are acked.
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(13, _));
+  EXPECT_TRUE(stream_->OnStreamFrameAcked(5, 17, false, QuicTime::Delta::Zero(),
+                                          &newly_acked_length));
+  EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+  // Verify [22, 26) 4 bytes are acked.
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(4, _));
+  EXPECT_TRUE(stream_->OnStreamFrameAcked(18, 8, false, QuicTime::Delta::Zero(),
+                                          &newly_acked_length));
+  EXPECT_EQ(1u, QuicStreamPeer::SendBuffer(stream_).size());
+  EXPECT_TRUE(stream_->IsWaitingForAcks());
+
+  // Ack [0, 27).
+  // Verify [26, 27) 1 byte is acked.
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(1, _));
+  EXPECT_TRUE(stream_->OnStreamFrameAcked(26, 1, false, QuicTime::Delta::Zero(),
+                                          &newly_acked_length));
+  EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+  EXPECT_TRUE(stream_->IsWaitingForAcks());
+
+  // Ack Fin. Verify OnPacketAcked is called.
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(0, _));
+  EXPECT_TRUE(stream_->OnStreamFrameAcked(27, 0, true, QuicTime::Delta::Zero(),
+                                          &newly_acked_length));
+  EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+  EXPECT_FALSE(stream_->IsWaitingForAcks());
+
+  // Ack [10, 27) and fin.
+  // No new data is acked, verify OnPacketAcked is not called.
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(_, _)).Times(0);
+  EXPECT_FALSE(stream_->OnStreamFrameAcked(
+      10, 17, true, QuicTime::Delta::Zero(), &newly_acked_length));
+  EXPECT_EQ(0u, QuicStreamPeer::SendBuffer(stream_).size());
+  EXPECT_FALSE(stream_->IsWaitingForAcks());
+}
+
+// HTTP/3 only.
+TEST_P(QuicSpdyStreamTest, HeadersAckNotReportedWriteOrBufferBody) {
+  Initialize(kShouldProcessData);
+  if (!HasFrameHeader()) {
+    return;
+  }
+  QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+      new StrictMock<MockAckListener>);
+  stream_->set_ack_listener(mock_ack_listener);
+  QuicString body = "Test1";
+  QuicString body2(100, 'x');
+
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+  stream_->WriteOrBufferBody(body, false);
+  stream_->WriteOrBufferBody(body2, true);
+
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+
+  header_length = encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+  QuicString header2 = QuicString(buffer.get(), header_length);
+
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(body.length(), _));
+  QuicStreamFrame frame(stream_->id(), false, 0, header + body);
+  EXPECT_TRUE(
+      session_->OnFrameAcked(QuicFrame(frame), QuicTime::Delta::Zero()));
+
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(0, _));
+  QuicStreamFrame frame2(stream_->id(), false, (header + body).length(),
+                         header2);
+  EXPECT_TRUE(
+      session_->OnFrameAcked(QuicFrame(frame2), QuicTime::Delta::Zero()));
+
+  EXPECT_CALL(*mock_ack_listener, OnPacketAcked(body2.length(), _));
+  QuicStreamFrame frame3(stream_->id(), true,
+                         (header + body).length() + header2.length(), body2);
+  EXPECT_TRUE(
+      session_->OnFrameAcked(QuicFrame(frame3), QuicTime::Delta::Zero()));
+
+  EXPECT_TRUE(
+      QuicSpdyStreamPeer::unacked_frame_headers_offsets(stream_).Empty());
+}
+
+// HTTP/3 only.
+TEST_P(QuicSpdyStreamTest, HeadersAckNotReportedWriteBodySlices) {
+  Initialize(kShouldProcessData);
+  if (!HasFrameHeader()) {
+    return;
+  }
+  QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+      new StrictMock<MockAckListener>);
+  stream_->set_ack_listener(mock_ack_listener);
+  QuicString body = "Test1";
+  QuicString body2(100, 'x');
+  struct iovec body1_iov = {const_cast<char*>(body.data()), body.length()};
+  struct iovec body2_iov = {const_cast<char*>(body2.data()), body2.length()};
+  QuicMemSliceStorage storage(&body1_iov, 1,
+                              helper_.GetStreamSendBufferAllocator(), 1024);
+  QuicMemSliceStorage storage2(&body2_iov, 1,
+                               helper_.GetStreamSendBufferAllocator(), 1024);
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+  stream_->WriteBodySlices(storage.ToSpan(), false);
+  stream_->WriteBodySlices(storage2.ToSpan(), true);
+
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+
+  header_length = encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+  QuicString header2 = QuicString(buffer.get(), header_length);
+
+  EXPECT_CALL(*mock_ack_listener,
+              OnPacketAcked(body.length() + body2.length(), _));
+  QuicStreamFrame frame(stream_->id(), true, 0,
+                        header + body + header2 + body2);
+  EXPECT_TRUE(
+      session_->OnFrameAcked(QuicFrame(frame), QuicTime::Delta::Zero()));
+
+  EXPECT_TRUE(
+      QuicSpdyStreamPeer::unacked_frame_headers_offsets(stream_).Empty());
+}
+
+// HTTP/3 only.
+TEST_P(QuicSpdyStreamTest, HeaderBytesNotReportedOnRetransmission) {
+  Initialize(kShouldProcessData);
+  if (!HasFrameHeader()) {
+    return;
+  }
+  QuicReferenceCountedPointer<MockAckListener> mock_ack_listener(
+      new StrictMock<MockAckListener>);
+  stream_->set_ack_listener(mock_ack_listener);
+  QuicString body = "Test1";
+  QuicString body2(100, 'x');
+
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _)).Times(AtLeast(1));
+  stream_->WriteOrBufferBody(body, false);
+  stream_->WriteOrBufferBody(body2, true);
+
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(body.length(), &buffer);
+  QuicString header = QuicString(buffer.get(), header_length);
+
+  header_length = encoder_.SerializeDataFrameHeader(body2.length(), &buffer);
+  QuicString header2 = QuicString(buffer.get(), header_length);
+
+  EXPECT_CALL(*mock_ack_listener, OnPacketRetransmitted(body.length()));
+  QuicStreamFrame frame(stream_->id(), false, 0, header + body);
+  session_->OnStreamFrameRetransmitted(frame);
+
+  EXPECT_CALL(*mock_ack_listener, OnPacketRetransmitted(body2.length()));
+  QuicStreamFrame frame2(stream_->id(), true, (header + body).length(),
+                         header2 + body2);
+  session_->OnStreamFrameRetransmitted(frame2);
+
+  EXPECT_FALSE(
+      QuicSpdyStreamPeer::unacked_frame_headers_offsets(stream_).Empty());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/spdy_utils.cc b/quic/core/http/spdy_utils.cc
new file mode 100644
index 0000000..7022ae2
--- /dev/null
+++ b/quic/core/http/spdy_utils.cc
@@ -0,0 +1,353 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+
+#include <memory>
+#include <vector>
+
+#include "url/gurl.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_map_util.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"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+using spdy::SpdyHeaderBlock;
+
+namespace quic {
+
+// static
+bool SpdyUtils::ExtractContentLengthFromHeaders(int64_t* content_length,
+                                                SpdyHeaderBlock* headers) {
+  auto it = headers->find("content-length");
+  if (it == headers->end()) {
+    return false;
+  } else {
+    // Check whether multiple values are consistent.
+    QuicStringPiece content_length_header = it->second;
+    std::vector<QuicStringPiece> values =
+        QuicTextUtils::Split(content_length_header, '\0');
+    for (const QuicStringPiece& value : values) {
+      uint64_t new_value;
+      if (!QuicTextUtils::StringToUint64(value, &new_value)) {
+        QUIC_DLOG(ERROR)
+            << "Content length was either unparseable or negative.";
+        return false;
+      }
+      if (*content_length < 0) {
+        *content_length = new_value;
+        continue;
+      }
+      if (new_value != static_cast<uint64_t>(*content_length)) {
+        QUIC_DLOG(ERROR)
+            << "Parsed content length " << new_value << " is "
+            << "inconsistent with previously detected content length "
+            << *content_length;
+        return false;
+      }
+    }
+    return true;
+  }
+}
+
+bool SpdyUtils::CopyAndValidateHeaders(const QuicHeaderList& header_list,
+                                       int64_t* content_length,
+                                       SpdyHeaderBlock* headers) {
+  for (const auto& p : header_list) {
+    const QuicString& name = p.first;
+    if (name.empty()) {
+      QUIC_DLOG(ERROR) << "Header name must not be empty.";
+      return false;
+    }
+
+    if (QuicTextUtils::ContainsUpperCase(name)) {
+      QUIC_DLOG(ERROR) << "Malformed header: Header name " << name
+                       << " contains upper-case characters.";
+      return false;
+    }
+
+    headers->AppendValueOrAddHeader(name, p.second);
+  }
+
+  if (QuicContainsKey(*headers, "content-length") &&
+      !ExtractContentLengthFromHeaders(content_length, headers)) {
+    return false;
+  }
+
+  QUIC_DVLOG(1) << "Successfully parsed headers: " << headers->DebugString();
+  return true;
+}
+
+bool SpdyUtils::CopyAndValidateTrailers(const QuicHeaderList& header_list,
+                                        size_t* final_byte_offset,
+                                        SpdyHeaderBlock* trailers) {
+  bool found_final_byte_offset = false;
+  for (const auto& p : header_list) {
+    const QuicString& name = p.first;
+
+    // Pull out the final offset pseudo header which indicates the number of
+    // response body bytes expected.
+    if (!found_final_byte_offset && name == kFinalOffsetHeaderKey &&
+        QuicTextUtils::StringToSizeT(p.second, final_byte_offset)) {
+      found_final_byte_offset = true;
+      continue;
+    }
+
+    if (name.empty() || name[0] == ':') {
+      QUIC_DLOG(ERROR)
+          << "Trailers must not be empty, and must not contain pseudo-"
+          << "headers. Found: '" << name << "'";
+      return false;
+    }
+
+    if (QuicTextUtils::ContainsUpperCase(name)) {
+      QUIC_DLOG(ERROR) << "Malformed header: Header name " << name
+                       << " contains upper-case characters.";
+      return false;
+    }
+
+    trailers->AppendValueOrAddHeader(name, p.second);
+  }
+
+  if (!found_final_byte_offset) {
+    QUIC_DLOG(ERROR) << "Required key '" << kFinalOffsetHeaderKey
+                     << "' not present";
+    return false;
+  }
+
+  // TODO(rjshade): Check for other forbidden keys, following the HTTP/2 spec.
+
+  QUIC_DVLOG(1) << "Successfully parsed Trailers: " << trailers->DebugString();
+  return true;
+}
+
+// static
+QuicString SpdyUtils::GetPromisedUrlFromHeaders(
+    const SpdyHeaderBlock& headers) {
+  // RFC 7540, Section 8.1.2.3: All HTTP/2 requests MUST include exactly
+  // one valid value for the ":method", ":scheme", and ":path" pseudo-header
+  // fields, unless it is a CONNECT request.
+
+  // RFC 7540, Section  8.2.1:  The header fields in PUSH_PROMISE and any
+  // subsequent CONTINUATION frames MUST be a valid and complete set of request
+  // header fields (Section 8.1.2.3).  The server MUST include a method in the
+  // ":method" pseudo-header field that is safe and cacheable.
+  //
+  // RFC 7231, Section  4.2.1: Of the request methods defined by this
+  // specification, the GET, HEAD, OPTIONS, and TRACE methods are defined to be
+  // safe.
+  //
+  // RFC 7231, Section  4.2.1: ... this specification defines GET, HEAD, and
+  // POST as cacheable, ...
+  //
+  // So the only methods allowed in a PUSH_PROMISE are GET and HEAD.
+  SpdyHeaderBlock::const_iterator it = headers.find(":method");
+  if (it == headers.end() || (it->second != "GET" && it->second != "HEAD")) {
+    return QuicString();
+  }
+
+  it = headers.find(":scheme");
+  if (it == headers.end() || it->second.empty()) {
+    return QuicString();
+  }
+  QuicStringPiece scheme = it->second;
+
+  // RFC 7540, Section 8.2: The server MUST include a value in the
+  // ":authority" pseudo-header field for which the server is authoritative
+  // (see Section 10.1).
+  it = headers.find(":authority");
+  if (it == headers.end() || it->second.empty()) {
+    return QuicString();
+  }
+  QuicStringPiece authority = it->second;
+
+  // RFC 7540, Section 8.1.2.3 requires that the ":path" pseudo-header MUST
+  // NOT be empty for "http" or "https" URIs;
+  //
+  // However, to ensure the scheme is consistently canonicalized, that check
+  // is deferred to implementations in QuicUrlUtils::GetPushPromiseUrl().
+  it = headers.find(":path");
+  if (it == headers.end()) {
+    return QuicString();
+  }
+  QuicStringPiece path = it->second;
+
+  return GetPushPromiseUrl(scheme, authority, path);
+}
+
+// static
+QuicString SpdyUtils::GetPromisedHostNameFromHeaders(
+    const SpdyHeaderBlock& headers) {
+  // TODO(fayang): Consider just checking out the value of the ":authority" key
+  // in headers.
+  return GURL(GetPromisedUrlFromHeaders(headers)).host();
+}
+
+// static
+bool SpdyUtils::PromisedUrlIsValid(const SpdyHeaderBlock& headers) {
+  QuicString url(GetPromisedUrlFromHeaders(headers));
+  return !url.empty() && GURL(url).is_valid();
+}
+
+// static
+bool SpdyUtils::PopulateHeaderBlockFromUrl(const QuicString url,
+                                           SpdyHeaderBlock* headers) {
+  (*headers)[":method"] = "GET";
+  size_t pos = url.find("://");
+  if (pos == QuicString::npos) {
+    return false;
+  }
+  (*headers)[":scheme"] = url.substr(0, pos);
+  size_t start = pos + 3;
+  pos = url.find("/", start);
+  if (pos == QuicString::npos) {
+    (*headers)[":authority"] = url.substr(start);
+    (*headers)[":path"] = "/";
+    return true;
+  }
+  (*headers)[":authority"] = url.substr(start, pos - start);
+  (*headers)[":path"] = url.substr(pos);
+  return true;
+}
+
+// static
+QuicString SpdyUtils::GetPushPromiseUrl(QuicStringPiece scheme,
+                                        QuicStringPiece authority,
+                                        QuicStringPiece path) {
+  // RFC 7540, Section 8.1.2.3: The ":path" pseudo-header field includes the
+  // path and query parts of the target URI (the "path-absolute" production
+  // and optionally a '?' character followed by the "query" production (see
+  // Sections 3.3 and 3.4 of RFC3986). A request in asterisk form includes the
+  // value '*' for the ":path" pseudo-header field.
+  //
+  // This pseudo-header field MUST NOT be empty for "http" or "https" URIs;
+  // "http" or "https" URIs that do not contain a path MUST include a value of
+  // '/'. The exception to this rule is an OPTIONS request for an "http" or
+  // "https" URI that does not include a path component; these MUST include a
+  // ":path" pseudo-header with a value of '*' (see RFC7230, Section 5.3.4).
+  //
+  // In addition to the above restriction from RFC 7540, note that RFC3986
+  // defines the "path-absolute" construction as starting with "/" but not "//".
+  //
+  // RFC 7540, Section  8.2.1:  The header fields in PUSH_PROMISE and any
+  // subsequent CONTINUATION frames MUST be a valid and complete set of request
+  // header fields (Section 8.1.2.3).  The server MUST include a method in the
+  // ":method" pseudo-header field that is safe and cacheable.
+  //
+  // RFC 7231, Section  4.2.1:
+  // ... this specification defines GET, HEAD, and POST as cacheable, ...
+  //
+  // Since the OPTIONS method is not cacheable, it cannot be the method of a
+  // PUSH_PROMISE. Therefore, the exception mentioned in RFC 7540, Section
+  // 8.1.2.3 about OPTIONS requests does not apply here (i.e. ":path" cannot be
+  // "*").
+  if (path.empty() || path[0] != '/' || (path.size() >= 2 && path[1] == '/')) {
+    return QuicString();
+  }
+
+  // Validate the scheme; this is to ensure a scheme of "foo://bar" is not
+  // parsed as a URL of "foo://bar://baz" when combined with a host of "baz".
+  std::string canonical_scheme;
+  url::StdStringCanonOutput canon_scheme_output(&canonical_scheme);
+  url::Component canon_component;
+  url::Component scheme_component(0, scheme.size());
+
+  if (!url::CanonicalizeScheme(scheme.data(), scheme_component,
+                               &canon_scheme_output, &canon_component) ||
+      !canon_component.is_nonempty() || canon_component.begin != 0) {
+    return QuicString();
+  }
+  canonical_scheme.resize(canon_component.len + 1);
+
+  // Validate the authority; this is to ensure an authority such as
+  // "host/path" is not accepted, as when combined with a scheme like
+  // "http://", could result in a URL of "http://host/path".
+  url::Component auth_component(0, authority.size());
+  url::Component username_component;
+  url::Component password_component;
+  url::Component host_component;
+  url::Component port_component;
+
+  url::ParseAuthority(authority.data(), auth_component, &username_component,
+                      &password_component, &host_component, &port_component);
+
+  // RFC 7540, Section 8.1.2.3: The authority MUST NOT include the deprecated
+  // "userinfo" subcomponent for "http" or "https" schemed URIs.
+  //
+  // Note: Although |canonical_scheme| has not yet been checked for that, as
+  // it is performed later in processing, only "http" and "https" schemed
+  // URIs are supported for PUSH.
+  if (username_component.is_valid() || password_component.is_valid()) {
+    return QuicString();
+  }
+
+  // Failed parsing or no host present. ParseAuthority() will ensure that
+  // host_component + port_component cover the entire string, if
+  // username_component and password_component are not present.
+  if (!host_component.is_nonempty()) {
+    return QuicString();
+  }
+
+  // Validate the port (if present; it's optional).
+  int parsed_port_number = url::PORT_INVALID;
+  if (port_component.is_nonempty()) {
+    parsed_port_number = url::ParsePort(authority.data(), port_component);
+    if (parsed_port_number < 0 && parsed_port_number != url::PORT_UNSPECIFIED) {
+      return QuicString();
+    }
+  }
+
+  // Validate the host by attempting to canonicalize it. Invalid characters
+  // will result in a canonicalization failure (e.g. '/')
+  std::string canon_host;
+  url::StdStringCanonOutput canon_host_output(&canon_host);
+  canon_component.reset();
+  if (!url::CanonicalizeHost(authority.data(), host_component,
+                             &canon_host_output, &canon_component) ||
+      !canon_component.is_nonempty() || canon_component.begin != 0) {
+    return QuicString();
+  }
+
+  // At this point, "authority" has been validated to either be of the form
+  // 'host:port' or 'host', with 'host' being a valid domain or IP address,
+  // and 'port' (if present), being a valid port. Attempt to construct a
+  // URL of just the (scheme, host, port), which should be safe and will not
+  // result in ambiguous parsing.
+  //
+  // This also enforces that all PUSHed URLs are either HTTP or HTTPS-schemed
+  // URIs, consistent with the other restrictions enforced above.
+  //
+  // Note: url::CanonicalizeScheme() will have added the ':' to
+  // |canonical_scheme|.
+  GURL origin_url(canonical_scheme + "//" + std::string(authority));
+  if (!origin_url.is_valid() || !origin_url.SchemeIsHTTPOrHTTPS() ||
+      // The following checks are merely defense in depth.
+      origin_url.has_username() || origin_url.has_password() ||
+      (origin_url.has_path() && origin_url.path_piece() != "/") ||
+      origin_url.has_query() || origin_url.has_ref()) {
+    return QuicString();
+  }
+
+  // Attempt to parse the path.
+  std::string spec = origin_url.GetWithEmptyPath().spec();
+  spec.pop_back();  // Remove the '/', as ":path" must contain it.
+  spec.append(std::string(path));
+
+  // Attempt to parse the full URL, with the path as well. Ensure there is no
+  // fragment to the query.
+  GURL full_url(spec);
+  if (!full_url.is_valid() || full_url.has_ref()) {
+    return QuicString();
+  }
+
+  return full_url.spec();
+}
+
+}  // namespace quic
diff --git a/quic/core/http/spdy_utils.h b/quic/core/http/spdy_utils.h
new file mode 100644
index 0000000..073eeeb
--- /dev/null
+++ b/quic/core/http/spdy_utils.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_SPDY_UTILS_H_
+#define QUICHE_QUIC_CORE_HTTP_SPDY_UTILS_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/http/quic_header_list.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+namespace quic {
+
+class QUIC_EXPORT_PRIVATE SpdyUtils {
+ public:
+  SpdyUtils() = delete;
+
+  // Populate |content length| with the value of the content-length header.
+  // Returns true on success, false if parsing fails or content-length header is
+  // missing.
+  static bool ExtractContentLengthFromHeaders(int64_t* content_length,
+                                              spdy::SpdyHeaderBlock* headers);
+
+  // Copies a list of headers to a SpdyHeaderBlock.
+  static bool CopyAndValidateHeaders(const QuicHeaderList& header_list,
+                                     int64_t* content_length,
+                                     spdy::SpdyHeaderBlock* headers);
+
+  // Copies a list of headers to a SpdyHeaderBlock.
+  static bool CopyAndValidateTrailers(const QuicHeaderList& header_list,
+                                      size_t* final_byte_offset,
+                                      spdy::SpdyHeaderBlock* trailers);
+
+  // Returns a canonicalized URL composed from the :scheme, :authority, and
+  // :path headers of a PUSH_PROMISE. Returns empty string if the headers do not
+  // conform to HTTP/2 spec or if the ":method" header contains a forbidden
+  // method for PUSH_PROMISE.
+  static QuicString GetPromisedUrlFromHeaders(
+      const spdy::SpdyHeaderBlock& headers);
+
+  // Returns hostname, or empty string if missing.
+  static QuicString GetPromisedHostNameFromHeaders(
+      const spdy::SpdyHeaderBlock& headers);
+
+  // Returns true if result of |GetPromisedUrlFromHeaders()| is non-empty
+  // and is a well-formed URL.
+  static bool PromisedUrlIsValid(const spdy::SpdyHeaderBlock& headers);
+
+  // Populates the fields of |headers| to make a GET request of |url|,
+  // which must be fully-qualified.
+  static bool PopulateHeaderBlockFromUrl(const QuicString url,
+                                         spdy::SpdyHeaderBlock* headers);
+
+  // Returns a canonical, valid URL for a PUSH_PROMISE with the specified
+  // ":scheme", ":authority", and ":path" header fields, or an empty
+  // string if the resulting URL is not valid or supported.
+  static QuicString GetPushPromiseUrl(QuicStringPiece scheme,
+                                      QuicStringPiece authority,
+                                      QuicStringPiece path);
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_SPDY_UTILS_H_
diff --git a/quic/core/http/spdy_utils_test.cc b/quic/core/http/spdy_utils_test.cc
new file mode 100644
index 0000000..6cd2f1d
--- /dev/null
+++ b/quic/core/http/spdy_utils_test.cc
@@ -0,0 +1,519 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.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"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+using spdy::SpdyHeaderBlock;
+using testing::Pair;
+using testing::UnorderedElementsAre;
+
+namespace quic {
+namespace test {
+
+static std::unique_ptr<QuicHeaderList> FromList(
+    const QuicHeaderList::ListType& src) {
+  std::unique_ptr<QuicHeaderList> headers(new QuicHeaderList);
+  headers->OnHeaderBlockStart();
+  for (const auto& p : src) {
+    headers->OnHeader(p.first, p.second);
+  }
+  headers->OnHeaderBlockEnd(0, 0);
+  return headers;
+}
+
+using CopyAndValidateHeaders = QuicTest;
+
+TEST_F(CopyAndValidateHeaders, NormalUsage) {
+  auto headers = FromList({// All cookie crumbs are joined.
+                           {"cookie", " part 1"},
+                           {"cookie", "part 2 "},
+                           {"cookie", "part3"},
+
+                           // Already-delimited headers are passed through.
+                           {"passed-through", QuicString("foo\0baz", 7)},
+
+                           // Other headers are joined on \0.
+                           {"joined", "value 1"},
+                           {"joined", "value 2"},
+
+                           // Empty headers remain empty.
+                           {"empty", ""},
+
+                           // Joined empty headers work as expected.
+                           {"empty-joined", ""},
+                           {"empty-joined", "foo"},
+                           {"empty-joined", ""},
+                           {"empty-joined", ""},
+
+                           // Non-continguous cookie crumb.
+                           {"cookie", " fin!"}});
+
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_TRUE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+  EXPECT_THAT(block,
+              UnorderedElementsAre(
+                  Pair("cookie", " part 1; part 2 ; part3;  fin!"),
+                  Pair("passed-through", QuicStringPiece("foo\0baz", 7)),
+                  Pair("joined", QuicStringPiece("value 1\0value 2", 15)),
+                  Pair("empty", ""),
+                  Pair("empty-joined", QuicStringPiece("\0foo\0\0", 6))));
+  EXPECT_EQ(-1, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, EmptyName) {
+  auto headers = FromList({{"foo", "foovalue"}, {"", "barvalue"}, {"baz", ""}});
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_FALSE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+}
+
+TEST_F(CopyAndValidateHeaders, UpperCaseName) {
+  auto headers =
+      FromList({{"foo", "foovalue"}, {"bar", "barvalue"}, {"bAz", ""}});
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_FALSE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+}
+
+TEST_F(CopyAndValidateHeaders, MultipleContentLengths) {
+  auto headers = FromList({{"content-length", "9"},
+                           {"foo", "foovalue"},
+                           {"content-length", "9"},
+                           {"bar", "barvalue"},
+                           {"baz", ""}});
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_TRUE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+  EXPECT_THAT(block, UnorderedElementsAre(
+                         Pair("foo", "foovalue"), Pair("bar", "barvalue"),
+                         Pair("content-length", QuicStringPiece("9\09", 3)),
+                         Pair("baz", "")));
+  EXPECT_EQ(9, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, InconsistentContentLengths) {
+  auto headers = FromList({{"content-length", "9"},
+                           {"foo", "foovalue"},
+                           {"content-length", "8"},
+                           {"bar", "barvalue"},
+                           {"baz", ""}});
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_FALSE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+}
+
+TEST_F(CopyAndValidateHeaders, LargeContentLength) {
+  auto headers = FromList({{"content-length", "9000000000"},
+                           {"foo", "foovalue"},
+                           {"bar", "barvalue"},
+                           {"baz", ""}});
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_TRUE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+  EXPECT_THAT(block, UnorderedElementsAre(
+                         Pair("foo", "foovalue"), Pair("bar", "barvalue"),
+                         Pair("content-length", QuicStringPiece("9000000000")),
+                         Pair("baz", "")));
+  EXPECT_EQ(9000000000, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, MultipleValues) {
+  auto headers = FromList({{"foo", "foovalue"},
+                           {"bar", "barvalue"},
+                           {"baz", ""},
+                           {"foo", "boo"},
+                           {"baz", "buzz"}});
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_TRUE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+  EXPECT_THAT(block, UnorderedElementsAre(
+                         Pair("foo", QuicStringPiece("foovalue\0boo", 12)),
+                         Pair("bar", "barvalue"),
+                         Pair("baz", QuicStringPiece("\0buzz", 5))));
+  EXPECT_EQ(-1, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, MoreThanTwoValues) {
+  auto headers = FromList({{"set-cookie", "value1"},
+                           {"set-cookie", "value2"},
+                           {"set-cookie", "value3"}});
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_TRUE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+  EXPECT_THAT(
+      block, UnorderedElementsAre(Pair(
+                 "set-cookie", QuicStringPiece("value1\0value2\0value3", 20))));
+  EXPECT_EQ(-1, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, Cookie) {
+  auto headers = FromList({{"foo", "foovalue"},
+                           {"bar", "barvalue"},
+                           {"cookie", "value1"},
+                           {"baz", ""}});
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_TRUE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+  EXPECT_THAT(block, UnorderedElementsAre(
+                         Pair("foo", "foovalue"), Pair("bar", "barvalue"),
+                         Pair("cookie", "value1"), Pair("baz", "")));
+  EXPECT_EQ(-1, content_length);
+}
+
+TEST_F(CopyAndValidateHeaders, MultipleCookies) {
+  auto headers = FromList({{"foo", "foovalue"},
+                           {"bar", "barvalue"},
+                           {"cookie", "value1"},
+                           {"baz", ""},
+                           {"cookie", "value2"}});
+  int64_t content_length = -1;
+  SpdyHeaderBlock block;
+  ASSERT_TRUE(
+      SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
+  EXPECT_THAT(block, UnorderedElementsAre(
+                         Pair("foo", "foovalue"), Pair("bar", "barvalue"),
+                         Pair("cookie", "value1; value2"), Pair("baz", "")));
+  EXPECT_EQ(-1, content_length);
+}
+
+using CopyAndValidateTrailers = QuicTest;
+
+TEST_F(CopyAndValidateTrailers, SimplestValidList) {
+  // Verify that the simplest trailers are valid: just a final byte offset that
+  // gets parsed successfully.
+  auto trailers = FromList({{kFinalOffsetHeaderKey, "1234"}});
+  size_t final_byte_offset = 0;
+  SpdyHeaderBlock block;
+  EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(*trailers, &final_byte_offset,
+                                                 &block));
+  EXPECT_EQ(1234u, final_byte_offset);
+}
+
+TEST_F(CopyAndValidateTrailers, EmptyTrailerList) {
+  // An empty trailer list will fail as required key kFinalOffsetHeaderKey is
+  // not present.
+  QuicHeaderList trailers;
+  size_t final_byte_offset = 0;
+  SpdyHeaderBlock block;
+  EXPECT_FALSE(
+      SpdyUtils::CopyAndValidateTrailers(trailers, &final_byte_offset, &block));
+}
+
+TEST_F(CopyAndValidateTrailers, FinalByteOffsetNotPresent) {
+  // Validation fails if required kFinalOffsetHeaderKey is not present, even if
+  // the rest of the header block is valid.
+  auto trailers = FromList({{"key", "value"}});
+  size_t final_byte_offset = 0;
+  SpdyHeaderBlock block;
+  EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(*trailers, &final_byte_offset,
+                                                  &block));
+}
+
+TEST_F(CopyAndValidateTrailers, EmptyName) {
+  // Trailer validation will fail with an empty header key, in an otherwise
+  // valid block of trailers.
+  auto trailers = FromList({{"", "value"}, {kFinalOffsetHeaderKey, "1234"}});
+  size_t final_byte_offset = 0;
+  SpdyHeaderBlock block;
+  EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(*trailers, &final_byte_offset,
+                                                  &block));
+}
+
+TEST_F(CopyAndValidateTrailers, PseudoHeaderInTrailers) {
+  // Pseudo headers are illegal in trailers.
+  auto trailers =
+      FromList({{":pseudo_key", "value"}, {kFinalOffsetHeaderKey, "1234"}});
+  size_t final_byte_offset = 0;
+  SpdyHeaderBlock block;
+  EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(*trailers, &final_byte_offset,
+                                                  &block));
+}
+
+TEST_F(CopyAndValidateTrailers, DuplicateTrailers) {
+  // Duplicate trailers are allowed, and their values are concatenated into a
+  // single string delimted with '\0'. Some of the duplicate headers
+  // deliberately have an empty value.
+  auto trailers = FromList({{"key", "value0"},
+                            {"key", "value1"},
+                            {"key", ""},
+                            {"key", ""},
+                            {"key", "value2"},
+                            {"key", ""},
+                            {kFinalOffsetHeaderKey, "1234"},
+                            {"other_key", "value"},
+                            {"key", "non_contiguous_duplicate"}});
+  size_t final_byte_offset = 0;
+  SpdyHeaderBlock block;
+  EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(*trailers, &final_byte_offset,
+                                                 &block));
+  EXPECT_THAT(
+      block,
+      UnorderedElementsAre(
+          Pair("key",
+               QuicStringPiece(
+                   "value0\0value1\0\0\0value2\0\0non_contiguous_duplicate",
+                   48)),
+          Pair("other_key", "value")));
+}
+
+TEST_F(CopyAndValidateTrailers, DuplicateCookies) {
+  // Duplicate cookie headers in trailers should be concatenated into a single
+  //  "; " delimted string.
+  auto headers = FromList({{"cookie", " part 1"},
+                           {"cookie", "part 2 "},
+                           {"cookie", "part3"},
+                           {"key", "value"},
+                           {kFinalOffsetHeaderKey, "1234"},
+                           {"cookie", " non_contiguous_cookie!"}});
+
+  size_t final_byte_offset = 0;
+  SpdyHeaderBlock block;
+  EXPECT_TRUE(
+      SpdyUtils::CopyAndValidateTrailers(*headers, &final_byte_offset, &block));
+  EXPECT_THAT(
+      block,
+      UnorderedElementsAre(
+          Pair("cookie", " part 1; part 2 ; part3;  non_contiguous_cookie!"),
+          Pair("key", "value")));
+}
+
+using GetPromisedUrlFromHeaders = QuicTest;
+
+TEST_F(GetPromisedUrlFromHeaders, Basic) {
+  SpdyHeaderBlock headers;
+  headers[":method"] = "GET";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+  headers[":scheme"] = "https";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+  headers[":authority"] = "www.google.com";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+  headers[":path"] = "/index.html";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers),
+            "https://www.google.com/index.html");
+  headers["key1"] = "value1";
+  headers["key2"] = "value2";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers),
+            "https://www.google.com/index.html");
+}
+
+TEST_F(GetPromisedUrlFromHeaders, Connect) {
+  SpdyHeaderBlock headers;
+  headers[":method"] = "CONNECT";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+  headers[":authority"] = "www.google.com";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+  headers[":scheme"] = "https";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+  headers[":path"] = "https";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+}
+
+TEST_F(GetPromisedUrlFromHeaders, InvalidUserinfo) {
+  SpdyHeaderBlock headers;
+  headers[":method"] = "GET";
+  headers[":authority"] = "user@www.google.com";
+  headers[":scheme"] = "https";
+  headers[":path"] = "/";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+}
+
+TEST_F(GetPromisedUrlFromHeaders, InvalidPath) {
+  SpdyHeaderBlock headers;
+  headers[":method"] = "GET";
+  headers[":authority"] = "www.google.com";
+  headers[":scheme"] = "https";
+  headers[":path"] = "";
+  EXPECT_EQ(SpdyUtils::GetPromisedUrlFromHeaders(headers), "");
+}
+
+using GetPromisedHostNameFromHeaders = QuicTest;
+
+TEST_F(GetPromisedHostNameFromHeaders, NormalUsage) {
+  SpdyHeaderBlock headers;
+  headers[":method"] = "GET";
+  EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "");
+  headers[":scheme"] = "https";
+  EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "");
+  headers[":authority"] = "www.google.com";
+  EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "");
+  headers[":path"] = "/index.html";
+  EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers),
+            "www.google.com");
+  headers["key1"] = "value1";
+  headers["key2"] = "value2";
+  EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers),
+            "www.google.com");
+  headers[":authority"] = "www.google.com:6666";
+  EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers),
+            "www.google.com");
+  headers[":authority"] = "192.168.1.1";
+  EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "192.168.1.1");
+  headers[":authority"] = "192.168.1.1:6666";
+  EXPECT_EQ(SpdyUtils::GetPromisedHostNameFromHeaders(headers), "192.168.1.1");
+}
+
+using PopulateHeaderBlockFromUrl = QuicTest;
+
+TEST_F(PopulateHeaderBlockFromUrl, NormalUsage) {
+  QuicString url = "https://www.google.com/index.html";
+  SpdyHeaderBlock headers;
+  EXPECT_TRUE(SpdyUtils::PopulateHeaderBlockFromUrl(url, &headers));
+  EXPECT_EQ("https", headers[":scheme"].as_string());
+  EXPECT_EQ("www.google.com", headers[":authority"].as_string());
+  EXPECT_EQ("/index.html", headers[":path"].as_string());
+}
+
+TEST_F(PopulateHeaderBlockFromUrl, UrlWithNoPath) {
+  QuicString url = "https://www.google.com";
+  SpdyHeaderBlock headers;
+  EXPECT_TRUE(SpdyUtils::PopulateHeaderBlockFromUrl(url, &headers));
+  EXPECT_EQ("https", headers[":scheme"].as_string());
+  EXPECT_EQ("www.google.com", headers[":authority"].as_string());
+  EXPECT_EQ("/", headers[":path"].as_string());
+}
+
+TEST_F(PopulateHeaderBlockFromUrl, Failure) {
+  SpdyHeaderBlock headers;
+  EXPECT_FALSE(SpdyUtils::PopulateHeaderBlockFromUrl("/", &headers));
+  EXPECT_FALSE(SpdyUtils::PopulateHeaderBlockFromUrl("/index.html", &headers));
+  EXPECT_FALSE(
+      SpdyUtils::PopulateHeaderBlockFromUrl("www.google.com/", &headers));
+}
+
+using PushPromiseUrlTest = QuicTest;
+
+TEST_F(PushPromiseUrlTest, GetPushPromiseUrl) {
+  // Test rejection of various inputs.
+  EXPECT_EQ("",
+            SpdyUtils::GetPushPromiseUrl("file", "localhost", "/etc/password"));
+  EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("file", "",
+                                             "/C:/Windows/System32/Config/"));
+  EXPECT_EQ("",
+            SpdyUtils::GetPushPromiseUrl("", "https://www.google.com", "/"));
+
+  EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https://www.google.com",
+                                             "www.google.com", "/"));
+  EXPECT_EQ("",
+            SpdyUtils::GetPushPromiseUrl("https://", "www.google.com", "/"));
+  EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "", "/"));
+  EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "", "www.google.com/"));
+  EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "www.google.com/", "/"));
+  EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "www.google.com", ""));
+  EXPECT_EQ("", SpdyUtils::GetPushPromiseUrl("https", "www.google", ".com/"));
+
+  // Test acception/rejection of various input combinations.
+  // |input_headers| is an array of pairs. The first value of each pair is a
+  // string that will be used as one of the inputs of GetPushPromiseUrl(). The
+  // second value of each pair is a bitfield where the lowest 3 bits indicate
+  // for which headers that string is valid (in a PUSH_PROMISE). For example,
+  // the string "http" would be valid for both the ":scheme" and ":authority"
+  // headers, so the bitfield paired with it is set to SCHEME | AUTH.
+  const unsigned char SCHEME = (1u << 0);
+  const unsigned char AUTH = (1u << 1);
+  const unsigned char PATH = (1u << 2);
+  const std::pair<const char*, unsigned char> input_headers[] = {
+      {"http", SCHEME | AUTH},
+      {"https", SCHEME | AUTH},
+      {"hTtP", SCHEME | AUTH},
+      {"HTTPS", SCHEME | AUTH},
+      {"www.google.com", AUTH},
+      {"90af90e0", AUTH},
+      {"12foo%20-bar:00001233", AUTH},
+      {"GOO\u200b\u2060\ufeffgoo", AUTH},
+      {"192.168.0.5", AUTH},
+      {"[::ffff:192.168.0.1.]", AUTH},
+      {"http:", AUTH},
+      {"bife l", AUTH},
+      {"/", PATH},
+      {"/foo/bar/baz", PATH},
+      {"/%20-2DVdkj.cie/foe_.iif/", PATH},
+      {"http://", 0},
+      {":443", 0},
+      {":80/eddd", 0},
+      {"google.com:-0", 0},
+      {"google.com:65536", 0},
+      {"http://google.com", 0},
+      {"http://google.com:39", 0},
+      {"//google.com/foo", 0},
+      {".com/", 0},
+      {"http://www.google.com/", 0},
+      {"http://foo:439", 0},
+      {"[::ffff:192.168", 0},
+      {"]/", 0},
+      {"//", 0}};
+  for (size_t i = 0; i < QUIC_ARRAYSIZE(input_headers); ++i) {
+    bool should_accept = (input_headers[i].second & SCHEME);
+    for (size_t j = 0; j < QUIC_ARRAYSIZE(input_headers); ++j) {
+      bool should_accept_2 = should_accept && (input_headers[j].second & AUTH);
+      for (size_t k = 0; k < QUIC_ARRAYSIZE(input_headers); ++k) {
+        // |should_accept_3| indicates whether or not GetPushPromiseUrl() is
+        // expected to accept this input combination.
+        bool should_accept_3 =
+            should_accept_2 && (input_headers[k].second & PATH);
+
+        std::string url = SpdyUtils::GetPushPromiseUrl(input_headers[i].first,
+                                                       input_headers[j].first,
+                                                       input_headers[k].first);
+
+        ::testing::AssertionResult result = ::testing::AssertionSuccess();
+        if (url.empty() == should_accept_3) {
+          result = ::testing::AssertionFailure()
+                   << "GetPushPromiseUrl() accepted/rejected the inputs when "
+                      "it shouldn't have."
+                   << std::endl
+                   << "     scheme: " << input_headers[i].first << std::endl
+                   << "  authority: " << input_headers[j].first << std::endl
+                   << "       path: " << input_headers[k].first << std::endl
+                   << "Output: " << url << std::endl;
+        }
+        ASSERT_TRUE(result);
+      }
+    }
+  }
+
+  // Test canonicalization of various valid inputs.
+  EXPECT_EQ("http://www.google.com/",
+            SpdyUtils::GetPushPromiseUrl("http", "www.google.com", "/"));
+  EXPECT_EQ(
+      "https://www.goo-gle.com/fOOo/baRR",
+      SpdyUtils::GetPushPromiseUrl("hTtPs", "wWw.gOo-gLE.cOm", "/fOOo/baRR"));
+  EXPECT_EQ("https://www.goo-gle.com:3278/pAth/To/reSOurce",
+            SpdyUtils::GetPushPromiseUrl("hTtPs", "Www.gOo-Gle.Com:000003278",
+                                         "/pAth/To/reSOurce"));
+  EXPECT_EQ("https://foo%20bar/foo/bar/baz",
+            SpdyUtils::GetPushPromiseUrl("https", "foo bar", "/foo/bar/baz"));
+  EXPECT_EQ("http://foo.com:70/e/",
+            SpdyUtils::GetPushPromiseUrl("http", "foo.com:0000070", "/e/"));
+  EXPECT_EQ(
+      "http://192.168.0.1:70/e/",
+      SpdyUtils::GetPushPromiseUrl("http", "0300.0250.00.01:0070", "/e/"));
+  EXPECT_EQ("http://192.168.0.1/e/",
+            SpdyUtils::GetPushPromiseUrl("http", "0xC0a80001", "/e/"));
+  EXPECT_EQ("http://[::c0a8:1]/",
+            SpdyUtils::GetPushPromiseUrl("http", "[::192.168.0.1]", "/"));
+  EXPECT_EQ(
+      "https://[::ffff:c0a8:1]/",
+      SpdyUtils::GetPushPromiseUrl("https", "[::ffff:0xC0.0Xa8.0x0.0x1]", "/"));
+}
+
+}  // namespace test
+}  // namespace quic