Add support for HTTP/3 control stream.

Upon initialization, QuicSpdySession will create a write_unidirectional stream
and write stream type and settings when crypto handshake is completed.

The peer will receive the stream type and create a read_unidirectional stream
that reads the settings.

gfe-relnote: version 99 only. Not in prod.
PiperOrigin-RevId: 252650934
Change-Id: I708280eb94dea3d6eb7e54b96ce8ee91e2b8684f
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index bd12a90..6bd587f 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -1530,7 +1530,10 @@
   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()
+                    ->max_allowed_outgoing_unidirectional_streams() -
+                QuicSessionPeer::v99_unidirectional_stream_id_manager(
+                    client_session)
+                    ->outgoing_static_stream_count()
           : QuicSessionPeer::GetStreamIdManager(client_session)
                 ->max_open_outgoing_streams();
   EXPECT_EQ(kServerMaxIncomingDynamicStreams,
@@ -1548,7 +1551,10 @@
   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()
+                    ->max_allowed_outgoing_unidirectional_streams() -
+                QuicSessionPeer::v99_unidirectional_stream_id_manager(
+                    server_session)
+                    ->outgoing_static_stream_count()
           : QuicSessionPeer::GetStreamIdManager(server_session)
                 ->max_open_outgoing_streams();
   EXPECT_EQ(kClientMaxIncomingDynamicStreams,
@@ -2048,9 +2054,13 @@
                   crypto_stream->flow_controller()),
               kStreamIFCW);
   }
-  EXPECT_EQ(kSessionIFCW,
-            QuicFlowControllerPeer::SendWindowSize(
-                client_->client()->client_session()->flow_controller()));
+  // When stream type is enabled, control streams will send settings and
+  // contribute to flow control windows, so this expectation is no longer valid.
+  if (!VersionHasStreamType(transport_version)) {
+    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.
@@ -2092,6 +2102,40 @@
   server_thread_->Pause();
   QuicSpdySession* const client_session = client_->client()->client_session();
   auto* server_session = static_cast<QuicSpdySession*>(GetServerSession());
+
+  if (VersionHasStreamType(client_->client()
+                               ->client_session()
+                               ->connection()
+                               ->transport_version())) {
+    // Settings frame will be sent through control streams, which contribute
+    // to the session's flow controller. And due to the timing issue described
+    // below, the settings frame might not be received.
+    HttpEncoder encoder;
+    SettingsFrame settings;
+    settings.values[6] = kDefaultMaxUncompressedHeaderSize;
+    std::unique_ptr<char[]> buffer;
+    auto header_length = encoder.SerializeSettingsFrame(settings, &buffer);
+    QuicByteCount win_difference1 = QuicFlowControllerPeer::ReceiveWindowSize(
+                                        server_session->flow_controller()) -
+                                    QuicFlowControllerPeer::SendWindowSize(
+                                        client_session->flow_controller());
+    QuicByteCount win_difference2 = QuicFlowControllerPeer::ReceiveWindowSize(
+                                        client_session->flow_controller()) -
+                                    QuicFlowControllerPeer::SendWindowSize(
+                                        server_session->flow_controller());
+    EXPECT_TRUE(win_difference1 == 0 ||
+                win_difference1 ==
+                    header_length +
+                        QuicDataWriter::GetVarInt62Len(kControlStream));
+    EXPECT_TRUE(win_difference2 == 0 ||
+                win_difference2 ==
+                    header_length +
+                        QuicDataWriter::GetVarInt62Len(kControlStream));
+    // The test returns early because in this version, headers stream no longer
+    // sends settings.
+    return;
+  }
+
   ExpectFlowControlsSynced(client_session->flow_controller(),
                            server_session->flow_controller());
   if (!QuicVersionUsesCryptoFrames(client_->client()
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index 5941189..183b6e1 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -11,9 +11,6 @@
 
 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
@@ -172,10 +169,10 @@
 bool QuicReceiveControlStream::OnSettingsFrame(const SettingsFrame& settings) {
   QuicSpdySession* spdy_session = static_cast<QuicSpdySession*>(session());
   for (auto& it : settings.values) {
-    uint16_t setting_id = it.first;
+    uint64_t setting_id = it.first;
     switch (setting_id) {
       case kSettingsMaxHeaderListSize:
-        spdy_session->set_max_inbound_header_list_size(it.second);
+        spdy_session->set_max_outbound_header_list_size(it.second);
         break;
       case kSettingsNumPlaceholders:
         // TODO: Support placeholder setting
diff --git a/quic/core/http/quic_receive_control_stream.h b/quic/core/http/quic_receive_control_stream.h
index a977f63..4e854ed 100644
--- a/quic/core/http/quic_receive_control_stream.h
+++ b/quic/core/http/quic_receive_control_stream.h
@@ -34,6 +34,8 @@
   // Implementation of QuicStream.
   void OnDataAvailable() override;
 
+  void SetUnblocked() { sequencer()->SetUnblocked(); }
+
  protected:
   // Called from HttpDecoderVisitor.
   bool OnSettingsFrameStart(Http3FrameLengths frame_lengths);
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
index 73e8fed..2a4515c 100644
--- a/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -111,19 +111,19 @@
 TEST_P(QuicReceiveControlStreamTest, ReceiveSettings) {
   SettingsFrame settings;
   settings.values[3] = 2;
-  settings.values[6] = 5;
+  settings.values[kSettingsMaxHeaderListSize] = 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());
+  EXPECT_NE(5u, session_.max_outbound_header_list_size());
   receive_control_stream_->OnStreamFrame(frame);
-  EXPECT_EQ(5u, session_.max_inbound_header_list_size());
+  EXPECT_EQ(5u, session_.max_outbound_header_list_size());
 }
 
 TEST_P(QuicReceiveControlStreamTest, ReceiveSettingsTwice) {
   SettingsFrame settings;
   settings.values[3] = 2;
-  settings.values[6] = 5;
+  settings.values[kSettingsMaxHeaderListSize] = 5;
   std::string data = EncodeSettings(settings);
   QuicStreamFrame frame(receive_control_stream_->id(), false, 0,
                         QuicStringPiece(data));
@@ -139,7 +139,7 @@
 TEST_P(QuicReceiveControlStreamTest, ReceiveSettingsFragments) {
   SettingsFrame settings;
   settings.values[3] = 2;
-  settings.values[6] = 5;
+  settings.values[kSettingsMaxHeaderListSize] = 5;
   std::string data = EncodeSettings(settings);
   std::string data1 = data.substr(0, 1);
   std::string data2 = data.substr(1, data.length() - 1);
@@ -148,10 +148,10 @@
                         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());
+  EXPECT_NE(5u, session_.max_outbound_header_list_size());
   receive_control_stream_->OnStreamFrame(frame);
   receive_control_stream_->OnStreamFrame(frame2);
-  EXPECT_EQ(5u, session_.max_inbound_header_list_size());
+  EXPECT_EQ(5u, session_.max_outbound_header_list_size());
 }
 
 TEST_P(QuicReceiveControlStreamTest, ReceiveWrongFrame) {
diff --git a/quic/core/http/quic_send_control_stream.cc b/quic/core/http/quic_send_control_stream.cc
index 40b6111..19ad0c3 100644
--- a/quic/core/http/quic_send_control_stream.cc
+++ b/quic/core/http/quic_send_control_stream.cc
@@ -6,6 +6,7 @@
 
 #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"
 
@@ -26,6 +27,16 @@
 
 void QuicSendControlStream::SendSettingsFrame(const SettingsFrame& settings) {
   DCHECK(!settings_sent_);
+
+  QuicConnection::ScopedPacketFlusher flusher(
+      session()->connection(), QuicConnection::SEND_ACK_IF_PENDING);
+  // Send the stream type on so the peer knows about this stream.
+  char data[sizeof(kControlStream)];
+  QuicDataWriter writer(QUIC_ARRAYSIZE(data), data);
+  writer.WriteVarInt62(kControlStream);
+  WriteOrBufferData(QuicStringPiece(writer.data(), writer.length()), false,
+                    nullptr);
+
   std::unique_ptr<char[]> buffer;
   QuicByteCount frame_length =
       encoder_.SerializeSettingsFrame(settings, &buffer);
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc
index 1e06e17..2051e1f 100644
--- a/quic/core/http/quic_send_control_stream_test.cc
+++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -104,6 +104,7 @@
   QuicByteCount frame_length =
       encoder_.SerializeSettingsFrame(settings, &buffer);
 
+  EXPECT_CALL(session_, WritevData(_, _, 1, _, _));
   EXPECT_CALL(session_, WritevData(_, _, frame_length, _, _));
   send_control_stream_->SendSettingsFrame(settings);
 }
diff --git a/quic/core/http/quic_server_session_base_test.cc b/quic/core/http/quic_server_session_base_test.cc
index e0657a1..c3fac3e 100644
--- a/quic/core/http/quic_server_session_base_test.cc
+++ b/quic/core/http/quic_server_session_base_test.cc
@@ -437,9 +437,9 @@
 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)));
+  EXPECT_EQ(nullptr, QuicServerSessionBasePeer::GetOrCreateDynamicStream(
+                         session_.get(),
+                         session_->next_outgoing_unidirectional_stream_id()));
 }
 
 TEST_P(QuicServerSessionBaseTest, GetStreamDisconnected) {
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 13a9fa2..d760036 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -314,6 +314,7 @@
     const ParsedQuicVersionVector& supported_versions)
     : QuicSession(connection, visitor, config, supported_versions),
       max_inbound_header_list_size_(kDefaultMaxUncompressedHeaderSize),
+      max_outbound_header_list_size_(kDefaultMaxUncompressedHeaderSize),
       server_push_enabled_(true),
       stream_id_(
           QuicUtils::GetInvalidStreamId(connection->transport_version())),
@@ -374,7 +375,17 @@
   } else {
     QUIC_RELOADABLE_FLAG_COUNT_N(quic_eliminate_static_stream_map_3, 7, 17);
     unowned_headers_stream_ = headers_stream_.get();
-    RegisterStaticStreamNew(std::move(headers_stream_));
+    RegisterStaticStreamNew(std::move(headers_stream_),
+                            /*stream_already_counted = */ false);
+  }
+
+  if (VersionHasStreamType(connection()->transport_version()) &&
+      eliminate_static_stream_map()) {
+    auto send_control = QuicMakeUnique<QuicSendControlStream>(
+        GetNextOutgoingUnidirectionalStreamId(), this);
+    send_control_stream_ = send_control.get();
+    RegisterStaticStreamNew(std::move(send_control),
+                            /*stream_already_counted = */ false);
   }
 
   set_max_uncompressed_header_bytes(max_inbound_header_list_size_);
@@ -523,6 +534,12 @@
 }
 
 void QuicSpdySession::SendMaxHeaderListSize(size_t value) {
+  if (VersionHasStreamType(connection()->transport_version())) {
+    SettingsFrame settings;
+    settings.values[kSettingsMaxHeaderListSize] = value;
+    send_control_stream_->SendSettingsFrame(settings);
+    return;
+  }
   SpdySettingsIR settings_frame;
   settings_frame.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE, value);
 
@@ -750,9 +767,14 @@
   pending->MarkConsumed(stream_type_length);
 
   switch (stream_type) {
-    case kControlStream:  // HTTP/3 control stream.
-      // TODO(renjietang): Create incoming control stream.
-      break;
+    case kControlStream: {  // HTTP/3 control stream.
+      auto receive_stream = QuicMakeUnique<QuicReceiveControlStream>(pending);
+      receive_control_stream_ = receive_stream.get();
+      RegisterStaticStreamNew(std::move(receive_stream),
+                              /*stream_already_counted = */ true);
+      receive_control_stream_->SetUnblocked();
+      return true;
+    }
     case kServerPushStream: {  // Push Stream.
       QuicSpdyStream* stream = CreateIncomingStream(pending);
       stream->SetUnblocked();
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 5af73f1..9d87c0f 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -11,6 +11,8 @@
 
 #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_receive_control_stream.h"
+#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_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"
@@ -28,12 +30,16 @@
 class QuicSpdySessionPeer;
 }  // namespace test
 
-// Unidirectional stream types define by IETF HTTP/3 draft in section 3.2.
+// Unidirectional stream types defined by IETF HTTP/3 draft in section 3.2.
 const uint64_t kControlStream = 0;
 const uint64_t kServerPushStream = 1;
 const uint64_t kQpackEncoderStream = 2;
 const uint64_t kQpackDecoderStream = 3;
 
+// Supported Settings id as defined by IETF HTTP/3 draft in section 7.2.5.1.
+const uint64_t kSettingsMaxHeaderListSize = 6;
+const uint64_t kSettingsNumPlaceholders = 8;
+
 // 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.
@@ -172,6 +178,14 @@
     max_inbound_header_list_size_ = max_inbound_header_list_size;
   }
 
+  void set_max_outbound_header_list_size(size_t max_outbound_header_list_size) {
+    max_outbound_header_list_size_ = max_outbound_header_list_size;
+  }
+
+  size_t max_outbound_header_list_size() const {
+    return max_outbound_header_list_size_;
+  }
+
   size_t max_inbound_header_list_size() const {
     return max_inbound_header_list_size_;
   }
@@ -288,10 +302,20 @@
   // is deprecated.
   QuicHeadersStream* unowned_headers_stream_;
 
+  // HTTP/3 control streams. They are owned by QuicSession inside dynamic
+  // stream map, and can be accessed by those unowned pointers below.
+  QuicSendControlStream* send_control_stream_;
+  QuicReceiveControlStream* receive_control_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_;
 
+  // The maximum size of a header block that can be sent to the peer. This field
+  // is informed and set by the peer via SETTINGS frame.
+  // TODO(renjietang): Honor this field when sending headers.
+  size_t max_outbound_header_list_size_;
+
   // Set during handshake. If true, resources in x-associated-content and link
   // headers will be pushed.
   bool server_push_enabled_;
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 83b5e5b..d6463c0 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -380,6 +380,13 @@
     return QuicUtils::StreamIdDelta(connection_->transport_version());
   }
 
+  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);
+  }
+
   QuicStreamId StreamCountToId(QuicStreamCount stream_count,
                                Perspective perspective,
                                bool bidirectional) {
@@ -2064,6 +2071,55 @@
   session_.ProcessPendingStream(&pending);
 }
 
+TEST_P(QuicSpdySessionTestServer, ReceiveControlStream) {
+  if (!VersionHasStreamType(transport_version()) ||
+      !GetQuicReloadableFlag(quic_eliminate_static_stream_map_3)) {
+    return;
+  }
+  // Use a arbitrary stream id.
+  QuicStreamId stream_id =
+      GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
+  char type[] = {kControlStream};
+
+  QuicStreamFrame data1(stream_id, false, 0, QuicStringPiece(type, 1));
+  session_.OnStreamFrame(data1);
+  EXPECT_EQ(stream_id,
+            QuicSpdySessionPeer::GetReceiveControlStream(&session_)->id());
+
+  SettingsFrame settings;
+  settings.values[3] = 2;
+  settings.values[kSettingsMaxHeaderListSize] = 5;
+  std::string data = EncodeSettings(settings);
+  QuicStreamFrame frame(stream_id, false, 1, QuicStringPiece(data));
+
+  EXPECT_NE(5u, session_.max_outbound_header_list_size());
+  session_.OnStreamFrame(frame);
+  EXPECT_EQ(5u, session_.max_outbound_header_list_size());
+}
+
+TEST_P(QuicSpdySessionTestServer, ReceiveControlStreamOutOfOrderDelivery) {
+  if (!VersionHasStreamType(transport_version()) ||
+      !GetQuicReloadableFlag(quic_eliminate_static_stream_map_3)) {
+    return;
+  }
+  // Use an arbitrary stream id.
+  QuicStreamId stream_id =
+      GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
+  char type[] = {kControlStream};
+  SettingsFrame settings;
+  settings.values[3] = 2;
+  settings.values[kSettingsMaxHeaderListSize] = 5;
+  std::string data = EncodeSettings(settings);
+
+  QuicStreamFrame data1(stream_id, false, 1, QuicStringPiece(data));
+  QuicStreamFrame data2(stream_id, false, 0, QuicStringPiece(type, 1));
+
+  session_.OnStreamFrame(data1);
+  EXPECT_NE(5u, session_.max_outbound_header_list_size());
+  session_.OnStreamFrame(data2);
+  EXPECT_EQ(5u, session_.max_outbound_header_list_size());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 9707c34..33b9ddb 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -118,7 +118,7 @@
         QuicUtils::GetCryptoStreamId(connection_->transport_version());
     largest_static_stream_id_ = std::max(id, largest_static_stream_id_);
     if (connection_->transport_version() == QUIC_VERSION_99) {
-      v99_streamid_manager_.RegisterStaticStream(id);
+      v99_streamid_manager_.RegisterStaticStream(id, false);
     }
   }
 }
@@ -138,16 +138,18 @@
   largest_static_stream_id_ = std::max(id, largest_static_stream_id_);
 
   if (connection_->transport_version() == QUIC_VERSION_99) {
-    v99_streamid_manager_.RegisterStaticStream(id);
+    v99_streamid_manager_.RegisterStaticStream(id, false);
   }
 }
 
-void QuicSession::RegisterStaticStreamNew(std::unique_ptr<QuicStream> stream) {
+void QuicSession::RegisterStaticStreamNew(std::unique_ptr<QuicStream> stream,
+                                          bool stream_already_counted) {
   DCHECK(eliminate_static_stream_map_);
   QuicStreamId stream_id = stream->id();
   dynamic_stream_map_[stream_id] = std::move(stream);
   if (connection_->transport_version() == QUIC_VERSION_99) {
-    v99_streamid_manager_.RegisterStaticStream(stream_id);
+    v99_streamid_manager_.RegisterStaticStream(stream_id,
+                                               stream_already_counted);
   }
   if (IsIncomingStream(stream_id)) {
     ++num_incoming_static_streams_;
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 32dd680..ef4e804 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -500,7 +500,8 @@
   void RegisterStaticStream(QuicStreamId id, QuicStream* stream);
   // TODO(renjietang): Replace the original Register method with the new one
   // once flag is deprecated.
-  void RegisterStaticStreamNew(std::unique_ptr<QuicStream> stream);
+  void RegisterStaticStreamNew(std::unique_ptr<QuicStream> stream,
+                               bool stream_already_counted);
   const StaticStreamMap& static_streams() const { return static_stream_map_; }
 
   DynamicStreamMap& dynamic_streams() { return dynamic_stream_map_; }
diff --git a/quic/core/quic_stream_id_manager.cc b/quic/core/quic_stream_id_manager.cc
index 2921268..7eaa91d 100644
--- a/quic/core/quic_stream_id_manager.cc
+++ b/quic/core/quic_stream_id_manager.cc
@@ -239,7 +239,8 @@
   return false;
 }
 
-bool QuicStreamIdManager::RegisterStaticStream(QuicStreamId stream_id) {
+bool QuicStreamIdManager::RegisterStaticStream(QuicStreamId stream_id,
+                                               bool stream_already_counted) {
   DCHECK_NE(QuicUtils::IsBidirectionalStreamId(stream_id), unidirectional_);
   if (IsIncomingStream(stream_id)) {
     // This code is predicated on static stream ids being allocated densely, in
@@ -267,7 +268,10 @@
         QuicUtils::GetMaxStreamCount(unidirectional_, perspective())) {
       incoming_advertised_max_streams_++;
     }
-    incoming_stream_count_++;
+
+    if (!stream_already_counted) {
+      incoming_stream_count_++;
+    }
     incoming_static_stream_count_++;
     return true;
   }
diff --git a/quic/core/quic_stream_id_manager.h b/quic/core/quic_stream_id_manager.h
index 42c7d1a..d0b0824 100644
--- a/quic/core/quic_stream_id_manager.h
+++ b/quic/core/quic_stream_id_manager.h
@@ -113,9 +113,12 @@
   // advertised MAX STREAMS can be calculated based on the start of the
   // dynamic stream space. This method will take any stream ID, one that either
   // this node or the peer will initiate.
+  // If |stream_already_counted| is true, the stream is already counted as an
+  // open stream else where, so no need to count it again.
   // Returns false if this fails because the new static stream would cause the
   // stream limit to be exceeded.
-  bool RegisterStaticStream(QuicStreamId stream_id);
+  bool RegisterStaticStream(QuicStreamId stream_id,
+                            bool stream_already_counted);
 
   // Checks if the incoming stream ID exceeds the MAX_STREAMS limit.  If the
   // limit is exceeded, closes the connection and returns false.  Uses the
diff --git a/quic/core/quic_stream_id_manager_test.cc b/quic/core/quic_stream_id_manager_test.cc
index 79c4845..107f472 100644
--- a/quic/core/quic_stream_id_manager_test.cc
+++ b/quic/core/quic_stream_id_manager_test.cc
@@ -640,7 +640,8 @@
 
   // First test will register the first dynamic stream id as being for a static
   // stream.
-  stream_id_manager_->RegisterStaticStream(first_dynamic);
+  stream_id_manager_->RegisterStaticStream(first_dynamic,
+                                           /*stream_already_counted = */ false);
   // Should go up by 1 stream/stream id.
   EXPECT_EQ(actual_max + 1u, stream_id_manager_->incoming_actual_max_streams());
 }
diff --git a/quic/core/uber_quic_stream_id_manager.cc b/quic/core/uber_quic_stream_id_manager.cc
index 75f7c6a..24c3b7c 100644
--- a/quic/core/uber_quic_stream_id_manager.cc
+++ b/quic/core/uber_quic_stream_id_manager.cc
@@ -24,12 +24,16 @@
           /*unidirectional=*/true,
           max_open_outgoing_unidirectional_streams,
           max_open_incoming_unidirectional_streams) {}
-void UberQuicStreamIdManager::RegisterStaticStream(QuicStreamId id) {
+void UberQuicStreamIdManager::RegisterStaticStream(
+    QuicStreamId id,
+    bool stream_already_counted) {
   if (QuicUtils::IsBidirectionalStreamId(id)) {
-    bidirectional_stream_id_manager_.RegisterStaticStream(id);
+    bidirectional_stream_id_manager_.RegisterStaticStream(
+        id, stream_already_counted);
     return;
   }
-  unidirectional_stream_id_manager_.RegisterStaticStream(id);
+  unidirectional_stream_id_manager_.RegisterStaticStream(
+      id, stream_already_counted);
 }
 
 void UberQuicStreamIdManager::AdjustMaxOpenOutgoingUnidirectionalStreams(
diff --git a/quic/core/uber_quic_stream_id_manager.h b/quic/core/uber_quic_stream_id_manager.h
index 288f0c0..45b0d33 100644
--- a/quic/core/uber_quic_stream_id_manager.h
+++ b/quic/core/uber_quic_stream_id_manager.h
@@ -28,7 +28,9 @@
       QuicStreamCount max_open_incoming_unidirectional_streams);
 
   // Called when a stream with |stream_id| is registered as a static stream.
-  void RegisterStaticStream(QuicStreamId id);
+  // If |stream_already_counted| is true, the static stream is already counted
+  // as an open stream earlier, so no need to count it again.
+  void RegisterStaticStream(QuicStreamId id, bool stream_already_counted);
 
   // Sets the maximum outgoing stream count as a result of doing the transport
   // configuration negotiation. Forces the limit to max_streams, regardless of
diff --git a/quic/core/uber_quic_stream_id_manager_test.cc b/quic/core/uber_quic_stream_id_manager_test.cc
index 8b1bace..a2498b0 100644
--- a/quic/core/uber_quic_stream_id_manager_test.cc
+++ b/quic/core/uber_quic_stream_id_manager_test.cc
@@ -133,7 +133,8 @@
       manager_->actual_max_allowed_incoming_bidirectional_streams();
   QuicStreamCount actual_max_allowed_incoming_unidirectional_streams =
       manager_->actual_max_allowed_incoming_unidirectional_streams();
-  manager_->RegisterStaticStream(first_incoming_bidirectional_stream_id);
+  manager_->RegisterStaticStream(first_incoming_bidirectional_stream_id,
+                                 /*stream_already_counted = */ false);
   // Verify actual_max_allowed_incoming_bidirectional_streams increases.
   EXPECT_EQ(actual_max_allowed_incoming_bidirectional_streams + 1u,
             manager_->actual_max_allowed_incoming_bidirectional_streams());
@@ -142,7 +143,8 @@
   EXPECT_EQ(actual_max_allowed_incoming_unidirectional_streams,
             manager_->actual_max_allowed_incoming_unidirectional_streams());
 
-  manager_->RegisterStaticStream(first_incoming_unidirectional_stream_id);
+  manager_->RegisterStaticStream(first_incoming_unidirectional_stream_id,
+                                 /*stream_already_counted = */ false);
   EXPECT_EQ(actual_max_allowed_incoming_bidirectional_streams + 1u,
             manager_->actual_max_allowed_incoming_bidirectional_streams());
   EXPECT_EQ(actual_max_allowed_incoming_unidirectional_streams + 1u,