Introduce Control streams for HTTP/3.

gfe-relnote: n/a --unused code.
PiperOrigin-RevId: 242161401
Change-Id: I29928112a479348ef66636b442ea6065bd832194
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
new file mode 100644
index 0000000..972dfc8
--- /dev/null
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -0,0 +1,165 @@
+// 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_receive_control_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_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+
+namespace quic {
+
+const uint16_t kSettingsMaxHeaderListSize = 6;
+const uint16_t kSettingsNumPlaceholders = 8;
+
+// Visitor of HttpDecoder that passes data frame to QuicSpdyStream and closes
+// the connection on unexpected frames.
+class QuicReceiveControlStream::HttpDecoderVisitor
+    : public HttpDecoder::Visitor {
+ public:
+  explicit HttpDecoderVisitor(QuicReceiveControlStream* 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 OnSettingsFrameStart(Http3FrameLengths frame_lengths) override {
+    stream_->OnSettingsFrameStart(frame_lengths);
+  }
+
+  void OnSettingsFrame(const SettingsFrame& frame) override {
+    stream_->OnSettingsFrame(frame);
+  }
+
+  void OnDuplicatePushFrame(const DuplicatePushFrame& frame) override {
+    CloseConnectionOnWrongFrame("Duplicate Push");
+  }
+
+  void OnDataFrameStart(Http3FrameLengths frame_lengths) override {
+    CloseConnectionOnWrongFrame("Data");
+  }
+
+  void OnDataFramePayload(QuicStringPiece payload) override {
+    CloseConnectionOnWrongFrame("Data");
+  }
+
+  void OnDataFrameEnd() override { CloseConnectionOnWrongFrame("Data"); }
+
+  void OnHeadersFrameStart(Http3FrameLengths frame_length) 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(std::string frame_type) {
+    // TODO(renjietang): Change to HTTP/3 error type.
+    stream_->session()->connection()->CloseConnection(
+        QUIC_HTTP_DECODER_ERROR,
+        frame_type + " frame received on control stream",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  }
+
+  QuicReceiveControlStream* stream_;
+};
+
+QuicReceiveControlStream::QuicReceiveControlStream(QuicStreamId id,
+                                                   QuicSpdySession* session)
+    : QuicStream(id, session, /*is_static = */ true, READ_UNIDIRECTIONAL),
+      received_settings_length_(0),
+      http_decoder_visitor_(new HttpDecoderVisitor(this)) {
+  decoder_.set_visitor(http_decoder_visitor_.get());
+  sequencer()->set_level_triggered(true);
+}
+
+QuicReceiveControlStream::~QuicReceiveControlStream() {}
+
+void QuicReceiveControlStream::OnStreamReset(const QuicRstStreamFrame& frame) {
+  // TODO(renjietang) Change the error code to H/3 specific
+  // HTTP_CLOSED_CRITICAL_STREAM.
+  session()->connection()->CloseConnection(
+      QUIC_INVALID_STREAM_ID, "Attempt to reset receive control stream",
+      ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void QuicReceiveControlStream::OnDataAvailable() {
+  iovec iov;
+  while (!reading_stopped() && sequencer()->PrefetchNextRegion(&iov)) {
+    decoder_.ProcessInput(reinterpret_cast<const char*>(iov.iov_base),
+                          iov.iov_len);
+  }
+}
+
+void QuicReceiveControlStream::OnSettingsFrameStart(
+    Http3FrameLengths frame_lengths) {
+  if (received_settings_length_ != 0) {
+    // TODO(renjietang): Change error code to HTTP_UNEXPECTED_FRAME.
+    session()->connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID, "Settings frames are received twice.",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  received_settings_length_ +=
+      frame_lengths.header_length + frame_lengths.payload_length;
+}
+
+void QuicReceiveControlStream::OnSettingsFrame(const SettingsFrame& settings) {
+  QuicSpdySession* spdy_session = static_cast<QuicSpdySession*>(session());
+  for (auto& it : settings.values) {
+    uint16_t setting_id = it.first;
+    switch (setting_id) {
+      case kSettingsMaxHeaderListSize:
+        spdy_session->set_max_inbound_header_list_size(it.second);
+        break;
+      case kSettingsNumPlaceholders:
+        // TODO: Support placeholder setting
+        break;
+      default:
+        break;
+    }
+  }
+  sequencer()->MarkConsumed(received_settings_length_);
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_receive_control_stream.h b/quic/core/http/quic_receive_control_stream.h
new file mode 100644
index 0000000..1805d6c
--- /dev/null
+++ b/quic/core/http/quic_receive_control_stream.h
@@ -0,0 +1,53 @@
+// 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_RECEIVE_CONTROL_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_RECEIVE_CONTROL_STREAM_H_
+
+#include "net/third_party/quiche/src/quic/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+class QuicSpdySession;
+
+// 3.2.1 Control Stream.
+// The receive control stream is peer initiated and is read only.
+class QUIC_EXPORT_PRIVATE QuicReceiveControlStream : public QuicStream {
+ public:
+  // |session| can't be nullptr, and the ownership is not passed. The stream can
+  // only be accessed through the session.
+  explicit QuicReceiveControlStream(QuicStreamId id, QuicSpdySession* session);
+  QuicReceiveControlStream(const QuicReceiveControlStream&) = delete;
+  QuicReceiveControlStream& operator=(const QuicReceiveControlStream&) = delete;
+  ~QuicReceiveControlStream() override;
+
+  // Overriding QuicStream::OnStreamReset to make sure control stream is never
+  // closed before connection.
+  void OnStreamReset(const QuicRstStreamFrame& frame) override;
+
+  // Implementation of QuicStream.
+  void OnDataAvailable() override;
+
+ protected:
+  // Called from HttpDecoderVisitor.
+  void OnSettingsFrameStart(Http3FrameLengths frame_lengths);
+  void OnSettingsFrame(const SettingsFrame& settings);
+
+ private:
+  class HttpDecoderVisitor;
+
+  HttpDecoder decoder_;
+
+  // Track the number of settings bytes received.
+  size_t received_settings_length_;
+
+  // HttpDecoder's visitor.
+  std::unique_ptr<HttpDecoderVisitor> http_decoder_visitor_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_RECEIVE_CONTROL_STREAM_H_
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
new file mode 100644
index 0000000..1861d17
--- /dev/null
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -0,0 +1,172 @@
+// 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_receive_control_stream.h"
+
+#include <cstdint>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#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/spdy_utils.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_piece.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"
+
+namespace quic {
+namespace test {
+
+namespace {
+using testing::_;
+using testing::StrictMock;
+
+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 (const auto& version : AllSupportedVersions()) {
+    if (!VersionHasControlStreams(version.transport_version)) {
+      continue;
+    }
+    for (Perspective p : {Perspective::IS_SERVER, Perspective::IS_CLIENT}) {
+      params.emplace_back(version, p);
+    }
+  }
+  return params;
+}
+
+class QuicReceiveControlStreamTest : public QuicTestWithParam<TestParams> {
+ public:
+  QuicReceiveControlStreamTest()
+      : connection_(new StrictMock<MockQuicConnection>(
+            &helper_,
+            &alarm_factory_,
+            perspective(),
+            SupportedVersions(GetParam().version))),
+        session_(connection_) {
+    session_.Initialize();
+    receive_control_stream_ = QuicMakeUnique<QuicReceiveControlStream>(
+        QuicUtils::GetFirstUnidirectionalStreamId(
+            GetParam().version.transport_version,
+            perspective() == Perspective::IS_CLIENT ? Perspective::IS_SERVER
+                                                    : Perspective::IS_CLIENT),
+        &session_);
+  }
+
+  Perspective perspective() const { return GetParam().perspective; }
+
+  std::string EncodeSettings(const SettingsFrame& settings) {
+    HttpEncoder encoder;
+    std::unique_ptr<char[]> buffer;
+    auto header_length = encoder.SerializeSettingsFrame(settings, &buffer);
+    return std::string(buffer.get(), header_length);
+  }
+
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnection>* connection_;
+  StrictMock<MockQuicSpdySession> session_;
+  HttpDecoder decoder_;
+  std::unique_ptr<QuicReceiveControlStream> receive_control_stream_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+                         QuicReceiveControlStreamTest,
+                         ::testing::ValuesIn(GetTestParams()));
+
+TEST_P(QuicReceiveControlStreamTest, ResetControlStream) {
+  QuicRstStreamFrame rst_frame(kInvalidControlFrameId,
+                               receive_control_stream_->id(),
+                               QUIC_STREAM_CANCELLED, 1234);
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+  receive_control_stream_->OnStreamReset(rst_frame);
+}
+
+TEST_P(QuicReceiveControlStreamTest, ReceiveSettings) {
+  SettingsFrame settings;
+  settings.values[3] = 2;
+  settings.values[6] = 5;
+  std::string data = EncodeSettings(settings);
+  QuicStreamFrame frame(receive_control_stream_->id(), false, 0,
+                        QuicStringPiece(data));
+  EXPECT_NE(5u, session_.max_inbound_header_list_size());
+  receive_control_stream_->OnStreamFrame(frame);
+  EXPECT_EQ(5u, session_.max_inbound_header_list_size());
+}
+
+TEST_P(QuicReceiveControlStreamTest, ReceiveSettingsTwice) {
+  SettingsFrame settings;
+  settings.values[3] = 2;
+  settings.values[6] = 5;
+  std::string data = EncodeSettings(settings);
+  QuicStreamFrame frame(receive_control_stream_->id(), false, 0,
+                        QuicStringPiece(data));
+  QuicStreamFrame frame2(receive_control_stream_->id(), false, data.length(),
+                         QuicStringPiece(data));
+  receive_control_stream_->OnStreamFrame(frame);
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_INVALID_STREAM_ID,
+                              "Settings frames are received twice.", _));
+  receive_control_stream_->OnStreamFrame(frame2);
+}
+
+TEST_P(QuicReceiveControlStreamTest, ReceiveSettingsFragments) {
+  SettingsFrame settings;
+  settings.values[3] = 2;
+  settings.values[6] = 5;
+  std::string data = EncodeSettings(settings);
+  std::string data1 = data.substr(0, 1);
+  std::string data2 = data.substr(1, data.length() - 1);
+
+  QuicStreamFrame frame(receive_control_stream_->id(), false, 0,
+                        QuicStringPiece(data.data(), 1));
+  QuicStreamFrame frame2(receive_control_stream_->id(), false, 1,
+                         QuicStringPiece(data.data() + 1, data.length() - 1));
+  EXPECT_NE(5u, session_.max_inbound_header_list_size());
+  receive_control_stream_->OnStreamFrame(frame);
+  receive_control_stream_->OnStreamFrame(frame2);
+  EXPECT_EQ(5u, session_.max_inbound_header_list_size());
+}
+
+TEST_P(QuicReceiveControlStreamTest, ReceiveWrongFrame) {
+  GoAwayFrame goaway;
+  goaway.stream_id = 0x1;
+  HttpEncoder encoder;
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length = encoder.SerializeGoAwayFrame(goaway, &buffer);
+  std::string data = std::string(buffer.get(), header_length);
+
+  QuicStreamFrame frame(receive_control_stream_->id(), false, 0,
+                        QuicStringPiece(data));
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_DECODER_ERROR, _, _));
+  receive_control_stream_->OnStreamFrame(frame);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_send_control_stream.cc b/quic/core/http/quic_send_control_stream.cc
new file mode 100644
index 0000000..40b6111
--- /dev/null
+++ b/quic/core/http/quic_send_control_stream.cc
@@ -0,0 +1,37 @@
+// 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_send_control_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_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+
+namespace quic {
+
+QuicSendControlStream::QuicSendControlStream(QuicStreamId id,
+                                             QuicSpdySession* session)
+    : QuicStream(id, session, /*is_static = */ true, WRITE_UNIDIRECTIONAL),
+      settings_sent_(false) {}
+
+void QuicSendControlStream::OnStreamReset(const QuicRstStreamFrame& frame) {
+  // TODO(renjietang) Change the error code to H/3 specific
+  // HTTP_CLOSED_CRITICAL_STREAM.
+  session()->connection()->CloseConnection(
+      QUIC_INVALID_STREAM_ID, "Attempt to reset send control stream",
+      ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void QuicSendControlStream::SendSettingsFrame(const SettingsFrame& settings) {
+  DCHECK(!settings_sent_);
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount frame_length =
+      encoder_.SerializeSettingsFrame(settings, &buffer);
+  WriteOrBufferData(QuicStringPiece(buffer.get(), frame_length),
+                    /*fin = */ false, nullptr);
+  settings_sent_ = true;
+}
+
+}  // namespace quic
diff --git a/quic/core/http/quic_send_control_stream.h b/quic/core/http/quic_send_control_stream.h
new file mode 100644
index 0000000..09bdafb
--- /dev/null
+++ b/quic/core/http/quic_send_control_stream.h
@@ -0,0 +1,47 @@
+// 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_SEND_CONTROL_STREAM_H_
+#define QUICHE_QUIC_CORE_HTTP_QUIC_SEND_CONTROL_STREAM_H_
+
+#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+class QuicSpdySession;
+
+// 3.2.1 Control Stream.
+// The send control stream is self initiated and is write only.
+class QUIC_EXPORT_PRIVATE QuicSendControlStream : public QuicStream {
+ public:
+  // |session| can't be nullptr, and the ownership is not passed. The stream can
+  // only be accessed through the session.
+  explicit QuicSendControlStream(QuicStreamId id, QuicSpdySession* session);
+  QuicSendControlStream(const QuicSendControlStream&) = delete;
+  QuicSendControlStream& operator=(const QuicSendControlStream&) = delete;
+  ~QuicSendControlStream() override = default;
+
+  // Overriding QuicStream::OnStreamReset to make sure control stream is never
+  // closed before connection.
+  void OnStreamReset(const QuicRstStreamFrame& frame) override;
+
+  // Send |settings| on this stream.
+  // Settings frame must be the first frame sent on this stream.
+  void SendSettingsFrame(const SettingsFrame& settings);
+
+  // The send control stream is write unidirectional, so this method should
+  // never be called.
+  void OnDataAvailable() override { QUIC_NOTREACHED(); }
+
+ private:
+  HttpEncoder encoder_;
+  // Track if a settings frame is already sent.
+  bool settings_sent_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_HTTP_QUIC_SEND_CONTROL_STREAM_H_
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc
new file mode 100644
index 0000000..980cdf5
--- /dev/null
+++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -0,0 +1,124 @@
+// 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_send_control_stream.h"
+
+#include <cstdint>
+#include <ostream>
+#include <utility>
+#include <vector>
+
+#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_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_piece.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"
+
+namespace quic {
+namespace test {
+
+namespace {
+using testing::_;
+using testing::Invoke;
+using testing::StrictMock;
+
+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 (const auto& version : AllSupportedVersions()) {
+    if (!VersionHasControlStreams(version.transport_version)) {
+      continue;
+    }
+    for (Perspective p : {Perspective::IS_SERVER, Perspective::IS_CLIENT}) {
+      params.emplace_back(version, p);
+    }
+  }
+  return params;
+}
+
+class QuicSendControlStreamTest : public QuicTestWithParam<TestParams> {
+ public:
+  QuicSendControlStreamTest()
+      : connection_(new StrictMock<MockQuicConnection>(
+            &helper_,
+            &alarm_factory_,
+            perspective(),
+            SupportedVersions(GetParam().version))),
+        session_(connection_) {
+    session_.Initialize();
+    send_control_stream_ = QuicMakeUnique<QuicSendControlStream>(
+        QuicSpdySessionPeer::GetNextOutgoingUnidirectionalStreamId(&session_),
+        &session_);
+    ON_CALL(session_, WritevData(_, _, _, _, _))
+        .WillByDefault(Invoke(MockQuicSession::ConsumeData));
+  }
+
+  Perspective perspective() const { return GetParam().perspective; }
+
+  MockQuicConnectionHelper helper_;
+  MockAlarmFactory alarm_factory_;
+  StrictMock<MockQuicConnection>* connection_;
+  StrictMock<MockQuicSpdySession> session_;
+  HttpEncoder encoder_;
+  std::unique_ptr<QuicSendControlStream> send_control_stream_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+                         QuicSendControlStreamTest,
+                         ::testing::ValuesIn(GetTestParams()));
+
+TEST_P(QuicSendControlStreamTest, WriteSettingsOnStartUp) {
+  SettingsFrame settings;
+  settings.values[3] = 2;
+  settings.values[6] = 5;
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount frame_length =
+      encoder_.SerializeSettingsFrame(settings, &buffer);
+
+  EXPECT_CALL(session_, WritevData(_, _, frame_length, _, _));
+  send_control_stream_->SendSettingsFrame(settings);
+}
+
+TEST_P(QuicSendControlStreamTest, ResetControlStream) {
+  QuicRstStreamFrame rst_frame(kInvalidControlFrameId,
+                               send_control_stream_->id(),
+                               QUIC_STREAM_CANCELLED, 1234);
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+  send_control_stream_->OnStreamReset(rst_frame);
+}
+
+TEST_P(QuicSendControlStreamTest, ReceiveDataOnSendControlStream) {
+  QuicStreamFrame frame(send_control_stream_->id(), false, 0, "test");
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_DATA_RECEIVED_ON_WRITE_UNIDIRECTIONAL_STREAM, _, _));
+  send_control_stream_->OnStreamFrame(frame);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index c968284..0ff1150 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -158,6 +158,10 @@
     max_inbound_header_list_size_ = max_inbound_header_list_size;
   }
 
+  size_t max_inbound_header_list_size() const {
+    return max_inbound_header_list_size_;
+  }
+
   // Returns true if the session has active request streams.
   bool HasActiveRequestStreams() const;
 
diff --git a/quic/core/quic_versions.h b/quic/core/quic_versions.h
index 2c78760..c270f95 100644
--- a/quic/core/quic_versions.h
+++ b/quic/core/quic_versions.h
@@ -353,6 +353,12 @@
   return transport_version == QUIC_VERSION_99;
 }
 
+// Returns whether |transport_version| has HTTP/3 Control stream.
+QUIC_EXPORT_PRIVATE inline bool VersionHasControlStreams(
+    QuicTransportVersion transport_version) {
+  return transport_version == QUIC_VERSION_99;
+}
+
 // Initializes support for the provided IETF draft version by setting flags
 // and the version label.
 QUIC_EXPORT_PRIVATE void QuicVersionInitializeSupportForIetfDraft(
diff --git a/quic/core/quic_write_blocked_list.h b/quic/core/quic_write_blocked_list.h
index df6b27d..83c2e20 100644
--- a/quic/core/quic_write_blocked_list.h
+++ b/quic/core/quic_write_blocked_list.h
@@ -189,9 +189,6 @@
     // Add |id| to the collection in unblocked state.
     void Register(QuicStreamId id) {
       DCHECK(!IsRegistered(id));
-      DCHECK(streams_.empty() || id > streams_.back().id)
-          << "stream_id: " << id
-          << " last static stream: " << streams_.back().id;
       streams_.push_back({id, false});
     }
 
diff --git a/quic/test_tools/quic_spdy_session_peer.cc b/quic/test_tools/quic_spdy_session_peer.cc
index b739fa1..dcba12c 100644
--- a/quic/test_tools/quic_spdy_session_peer.cc
+++ b/quic/test_tools/quic_spdy_session_peer.cc
@@ -61,5 +61,11 @@
       id, std::move(headers), fin, priority, std::move(ack_listener));
 }
 
+// static
+QuicStreamId QuicSpdySessionPeer::GetNextOutgoingUnidirectionalStreamId(
+    QuicSpdySession* session) {
+  return session->GetNextOutgoingUnidirectionalStreamId();
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_spdy_session_peer.h b/quic/test_tools/quic_spdy_session_peer.h
index 58a864d..83e8b0c 100644
--- a/quic/test_tools/quic_spdy_session_peer.h
+++ b/quic/test_tools/quic_spdy_session_peer.h
@@ -41,6 +41,9 @@
       bool fin,
       spdy::SpdyPriority priority,
       QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener);
+  // |session| can't be nullptr.
+  static QuicStreamId GetNextOutgoingUnidirectionalStreamId(
+      QuicSpdySession* session);
 };
 
 }  // namespace test