Move headers stream from 0 to 60 in v99

This CL also fixes a few tests that were incorrectly using the headers stream.

gfe-relnote: change header stream number, protected by v99 flag
PiperOrigin-RevId: 253691890
Change-Id: I1351cad387871efe39fb4387eac546e9a24efb7c
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 6bd587f..01213d9 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -751,6 +751,19 @@
                                                 .length());
 }
 
+TEST_P(EndToEndTestWithTls, SimpleRequestResponseWithIetfDraftSupport) {
+  if (GetParam().negotiated_version.transport_version != QUIC_VERSION_99 ||
+      GetParam().negotiated_version.handshake_protocol != PROTOCOL_TLS1_3) {
+    ASSERT_TRUE(Initialize());
+    return;
+  }
+  QuicVersionInitializeSupportForIetfDraft(1);
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
 TEST_P(EndToEndTest, SimpleRequestResponseWithLargeReject) {
   chlo_multiplier_ = 1;
   ASSERT_TRUE(Initialize());
@@ -1785,6 +1798,7 @@
 // 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) {
+  SetQuicFlag(FLAGS_quic_headers_stream_id_in_v99, 0);
   // Connect with lower fake packet loss than we'd like to test.  Until
   // b/10126687 is fixed, losing handshake packets is pretty brutal.
   SetPacketLossPercentage(1);
@@ -2891,6 +2905,7 @@
   const size_t kNumMaxStreams = 10;
 
   EndToEndTestServerPush() : EndToEndTest() {
+    SetQuicFlag(FLAGS_quic_headers_stream_id_in_v99, 0);
     client_config_.SetMaxIncomingBidirectionalStreamsToSend(kNumMaxStreams);
     server_config_.SetMaxIncomingBidirectionalStreamsToSend(kNumMaxStreams);
     client_config_.SetMaxIncomingUnidirectionalStreamsToSend(kNumMaxStreams);
@@ -3389,7 +3404,7 @@
 TEST_P(EndToEndTest,
        SendStatelessResetIfServerConnectionClosedLocallyAfterHandshake) {
   // Prevent the connection from expiring in the time wait list.
-  FLAGS_quic_time_wait_list_seconds = 10000;
+  SetQuicFlag(FLAGS_quic_time_wait_list_seconds, 10000);
   connect_to_server_on_initialize_ = false;
   ASSERT_TRUE(Initialize());
 
diff --git a/quic/core/http/quic_headers_stream_test.cc b/quic/core/http/quic_headers_stream_test.cc
index a41235f..68d32de 100644
--- a/quic/core/http/quic_headers_stream_test.cc
+++ b/quic/core/http/quic_headers_stream_test.cc
@@ -144,14 +144,20 @@
 struct TestParams {
   TestParams(const ParsedQuicVersion& version, Perspective perspective)
       : version(version), perspective(perspective) {
-    QUIC_LOG(INFO) << "TestParams: version: "
-                   << ParsedQuicVersionToString(version)
-                   << ", perspective: " << perspective;
+    QUIC_LOG(INFO) << "TestParams:  " << *this;
   }
 
   TestParams(const TestParams& other)
       : version(other.version), perspective(other.perspective) {}
 
+  friend std::ostream& operator<<(std::ostream& os, const TestParams& tp) {
+    os << "{ version: " << ParsedQuicVersionToString(tp.version)
+       << ", perspective: "
+       << (tp.perspective == Perspective::IS_CLIENT ? "client" : "server")
+       << "}";
+    return os;
+  }
+
   ParsedQuicVersion version;
   Perspective perspective;
 };
@@ -390,6 +396,9 @@
 }
 
 TEST_P(QuicHeadersStreamTest, WritePushPromises) {
+  if (GetParam().version.DoesNotHaveHeadersStream()) {
+    return;
+  }
   for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
        stream_id += next_stream_id_) {
     QuicStreamId promised_stream_id = NextPromisedStreamId();
@@ -461,6 +470,9 @@
 }
 
 TEST_P(QuicHeadersStreamTest, ProcessPushPromise) {
+  if (GetParam().version.DoesNotHaveHeadersStream()) {
+    return;
+  }
   if (perspective() == Perspective::IS_SERVER) {
     return;
   }
@@ -470,6 +482,7 @@
     SpdyPushPromiseIR push_promise(stream_id, promised_stream_id,
                                    headers_.Clone());
     SpdySerializedFrame frame(framer_->SerializeFrame(push_promise));
+    bool connection_closed = false;
     if (perspective() == Perspective::IS_SERVER) {
       EXPECT_CALL(*connection_,
                   CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA,
@@ -477,6 +490,8 @@
           .WillRepeatedly(InvokeWithoutArgs(
               this, &QuicHeadersStreamTest::TearDownLocalConnectionState));
     } else {
+      ON_CALL(*connection_, CloseConnection(_, _, _))
+          .WillByDefault(testing::Assign(&connection_closed, true));
       EXPECT_CALL(session_, OnPromiseHeaderList(stream_id, promised_stream_id,
                                                 frame.size(), _))
           .WillOnce(
@@ -487,12 +502,18 @@
     headers_stream_->OnStreamFrame(stream_frame_);
     if (perspective() == Perspective::IS_CLIENT) {
       stream_frame_.offset += frame.size();
+      // CheckHeaders crashes if the connection is closed so this ensures we
+      // fail the test instead of crashing.
+      ASSERT_FALSE(connection_closed);
       CheckHeaders();
     }
   }
 }
 
 TEST_P(QuicHeadersStreamTest, ProcessPriorityFrame) {
+  if (GetParam().version.DoesNotHaveHeadersStream()) {
+    return;
+  }
   QuicStreamId parent_stream_id = 0;
   for (SpdyPriority priority = 0; priority < 7; ++priority) {
     for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_;
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index 9aff364..bfde618 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -241,7 +241,10 @@
   EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AnyNumber());
   EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(AnyNumber());
 
-  const uint32_t kServerMaxIncomingStreams = 1;
+  uint32_t kServerMaxIncomingStreams = 1;
+  if (VersionLacksHeadersStream(GetParam().transport_version)) {
+    kServerMaxIncomingStreams = 0;
+  }
   CompleteCryptoHandshake(kServerMaxIncomingStreams);
 
   QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
@@ -257,15 +260,22 @@
   EXPECT_FALSE(stream);
 
   if (GetParam().transport_version == QUIC_VERSION_99) {
-    // Ensure that we have/have had 3 open streams, crypto, header, and the
-    // 1 test stream. Primary purpose of this is to fail when crypto
-    // no longer uses a normal stream. Some above constants will then need
-    // to be changed.
-    EXPECT_EQ(QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
-                      ->outgoing_static_stream_count() +
-                  1,
-              QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
-                  ->outgoing_stream_count());
+    if (VersionLacksHeadersStream(GetParam().transport_version)) {
+      EXPECT_EQ(1u,
+                QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
+                    ->outgoing_stream_count());
+
+    } else {
+      // Ensure that we have/have had 3 open streams, crypto, header, and the
+      // 1 test stream. Primary purpose of this is to fail when crypto
+      // no longer uses a normal stream. Some above constants will then need
+      // to be changed.
+      EXPECT_EQ(QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
+                        ->outgoing_static_stream_count() +
+                    1,
+                QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
+                    ->outgoing_stream_count());
+    }
   }
 }
 
@@ -279,7 +289,10 @@
   EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AnyNumber());
   EXPECT_CALL(*connection_, OnStreamReset(_, _)).Times(AnyNumber());
 
-  const uint32_t kServerMaxIncomingStreams = 1;
+  uint32_t kServerMaxIncomingStreams = 1;
+  if (VersionLacksHeadersStream(GetParam().transport_version)) {
+    kServerMaxIncomingStreams = 0;
+  }
   CompleteCryptoHandshake(kServerMaxIncomingStreams);
 
   QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
@@ -312,7 +325,11 @@
   if (GetParam().transport_version == QUIC_VERSION_99) {
     // Ensure that we have/have had three open streams: two test streams and the
     // header stream.
-    EXPECT_EQ(3u,
+    QuicStreamCount expected_stream_count = 3;
+    if (VersionLacksHeadersStream(GetParam().transport_version)) {
+      expected_stream_count = 2;
+    }
+    EXPECT_EQ(expected_stream_count,
               QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
                   ->outgoing_stream_count());
   }
@@ -329,7 +346,10 @@
   // 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;
+  uint32_t kServerMaxIncomingStreams = 1;
+  if (VersionLacksHeadersStream(GetParam().transport_version)) {
+    kServerMaxIncomingStreams = 0;
+  }
   CompleteCryptoHandshake(kServerMaxIncomingStreams);
 
   QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
@@ -392,7 +412,11 @@
   if (GetParam().transport_version == QUIC_VERSION_99) {
     // Ensure that we have/have had three open streams: two test streams and the
     // header stream.
-    EXPECT_EQ(3u,
+    QuicStreamCount expected_stream_count = 3;
+    if (VersionLacksHeadersStream(GetParam().transport_version)) {
+      expected_stream_count = 2;
+    }
+    EXPECT_EQ(expected_stream_count,
               QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
                   ->outgoing_stream_count());
   }
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index b9006ca..5004557 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -351,13 +351,15 @@
 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 (!connection()->version().DoesNotHaveHeadersStream()) {
+    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())) {
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index fc1ae0a..ce4d5a9 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -483,9 +483,12 @@
     // 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.
+    QuicStreamCount headers_stream_offset =
+        VersionLacksHeadersStream(QUIC_VERSION_99) ? 1 : 0;
     QuicStreamId stream_id = StreamCountToId(
         QuicSessionPeer::v99_streamid_manager(&session_)
-            ->actual_max_allowed_incoming_bidirectional_streams(),
+                ->actual_max_allowed_incoming_bidirectional_streams() -
+            headers_stream_offset,
         Perspective::IS_CLIENT,  // Client initates stream, allocs stream id.
         /*bidirectional=*/true);
     EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id));
@@ -500,7 +503,7 @@
     stream_id = StreamCountToId(
         QuicSessionPeer::v99_streamid_manager(&session_)
                 ->actual_max_allowed_incoming_bidirectional_streams() +
-            1,
+            1 - headers_stream_offset,
         Perspective::IS_CLIENT,
         /*bidirectional=*/true);
     EXPECT_EQ(nullptr, session_.GetOrCreateDynamicStream(stream_id));
@@ -1588,10 +1591,12 @@
         .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 24 would exceed stream count limit 6", _));
+    EXPECT_CALL(*connection_,
+                CloseConnection(
+                    QUIC_INVALID_STREAM_ID,
+                    testing::MatchesRegex(
+                        "Stream id [0-9]+ would exceed stream count limit 6"),
+                    _));
   }
   // Create one more data streams to exceed limit of open stream.
   QuicStreamFrame data1(kFinalStreamId, false, 0, QuicStringPiece("HT"));
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 9ca1df2..66100bb 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -260,9 +260,12 @@
       VersionUsesQpack(GetParam().transport_version);
 
   if (version_uses_qpack) {
-    EXPECT_CALL(*connection_,
-                CloseConnection(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
-                                "Too large headers received on stream 4", _));
+    EXPECT_CALL(
+        *connection_,
+        CloseConnection(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
+                        testing::MatchesRegex(
+                            "Too large headers received on stream [0-9]+"),
+                        _));
   } else {
     EXPECT_CALL(*session_,
                 SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0));
@@ -1817,11 +1820,13 @@
   std::string stream_frame_payload = QuicStrCat(headers, data);
   QuicStreamFrame frame(stream_->id(), false, 0, stream_frame_payload);
 
-  EXPECT_CALL(*connection_,
-              CloseConnection(QUIC_DECOMPRESSION_FAILURE,
-                              "Error decompressing header block on stream 4: "
-                              "Incomplete header block.",
-                              _))
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_DECOMPRESSION_FAILURE,
+                      testing::MatchesRegex(
+                          "Error decompressing header block on stream [0-9]+: "
+                          "Incomplete header block."),
+                      _))
       .WillOnce(
           (Invoke([this](QuicErrorCode error, const std::string& error_details,
                          ConnectionCloseBehavior connection_close_behavior) {
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 7450eb6..243c13a 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -92,7 +92,9 @@
 
 QuicStreamId GetNthClientInitiatedStreamId(int n,
                                            QuicTransportVersion version) {
-  return QuicUtils::GetHeadersStreamId(version) + n * 2;
+  return QuicUtils::GetFirstBidirectionalStreamId(version,
+                                                  Perspective::IS_CLIENT) +
+         n * 2;
 }
 
 QuicLongHeaderType EncryptionlevelToLongHeaderType(EncryptionLevel level) {
@@ -3166,8 +3168,8 @@
   iov.iov_base = data_array.get();
   iov.iov_len = len;
   QuicConsumedData consumed = connection_.SaveAndSendStreamData(
-      QuicUtils::GetHeadersStreamId(connection_.transport_version()), &iov, 1,
-      len, 0, FIN);
+      GetNthClientInitiatedStreamId(0, connection_.transport_version()), &iov,
+      1, len, 0, FIN);
   EXPECT_EQ(len, consumed.bytes_consumed);
   EXPECT_TRUE(consumed.fin_consumed);
   EXPECT_EQ(0u, connection_.NumQueuedPackets());
@@ -3176,7 +3178,7 @@
   // Parse the last packet and ensure it's one stream frame with a fin.
   EXPECT_EQ(1u, writer_->frame_count());
   ASSERT_EQ(1u, writer_->stream_frames().size());
-  EXPECT_EQ(QuicUtils::GetHeadersStreamId(connection_.transport_version()),
+  EXPECT_EQ(GetNthClientInitiatedStreamId(0, connection_.transport_version()),
             writer_->stream_frames()[0]->stream_id);
   EXPECT_TRUE(writer_->stream_frames()[0]->fin);
   // Ensure the ack alarm was cancelled when the ack was sent.
@@ -4517,8 +4519,8 @@
 
   // Send and ack new data 3 seconds later to lengthen the idle timeout.
   SendStreamDataToPeer(
-      QuicUtils::GetHeadersStreamId(connection_.transport_version()), "GET /",
-      0, FIN, nullptr);
+      GetNthClientInitiatedStreamId(0, connection_.transport_version()),
+      "GET /", 0, FIN, nullptr);
   clock_.AdvanceTime(QuicTime::Delta::FromSeconds(3));
   QuicAckFrame frame = InitAckFrame(1);
   EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
@@ -4560,8 +4562,8 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
   SendStreamDataToPeer(
-      QuicUtils::GetHeadersStreamId(connection_.transport_version()), "GET /",
-      0, FIN, nullptr);
+      GetNthClientInitiatedStreamId(0, connection_.transport_version()),
+      "GET /", 0, FIN, nullptr);
   EXPECT_TRUE(connection_.GetPingAlarm()->IsSet());
   EXPECT_EQ(clock_.ApproximateNow() + QuicTime::Delta::FromSeconds(15),
             connection_.GetPingAlarm()->deadline());
@@ -4614,8 +4616,8 @@
   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
   EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
   SendStreamDataToPeer(
-      QuicUtils::GetHeadersStreamId(connection_.transport_version()), "GET /",
-      0, FIN, nullptr);
+      GetNthClientInitiatedStreamId(0, connection_.transport_version()),
+      "GET /", 0, FIN, nullptr);
   EXPECT_TRUE(connection_.GetPingAlarm()->IsSet());
   EXPECT_EQ(clock_.ApproximateNow() + QuicTime::Delta::FromSeconds(10),
             connection_.GetPingAlarm()->deadline());
diff --git a/quic/core/quic_packet_creator_test.cc b/quic/core/quic_packet_creator_test.cc
index a661c24..bc30af9 100644
--- a/quic/core/quic_packet_creator_test.cc
+++ b/quic/core/quic_packet_creator_test.cc
@@ -261,7 +261,9 @@
   }
 
   QuicStreamId GetNthClientInitiatedStreamId(int n) const {
-    return QuicUtils::GetHeadersStreamId(creator_.transport_version()) + n * 2;
+    return QuicUtils::GetFirstBidirectionalStreamId(
+               creator_.transport_version(), Perspective::IS_CLIENT) +
+           n * 2;
   }
 
   static const QuicStreamOffset kOffset = 0u;
@@ -1371,9 +1373,8 @@
   EXPECT_FALSE(creator_.HasPendingFrames());
 
   MakeIOVector("test", &iov_);
-  producer_.SaveStreamData(
-      QuicUtils::GetHeadersStreamId(client_framer_.transport_version()), &iov_,
-      1u, 0u, iov_.iov_len);
+  producer_.SaveStreamData(GetNthClientInitiatedStreamId(0), &iov_, 1u, 0u,
+                           iov_.iov_len);
   EXPECT_CALL(delegate_, OnSerializedPacket(_))
       .WillOnce(Invoke(this, &QuicPacketCreatorTest::SaveSerializedPacket));
   size_t num_bytes_consumed;
@@ -1381,8 +1382,8 @@
   creator_.set_debug_delegate(&debug);
   EXPECT_CALL(debug, OnFrameAddedToPacket(_));
   creator_.CreateAndSerializeStreamFrame(
-      QuicUtils::GetHeadersStreamId(client_framer_.transport_version()),
-      iov_.iov_len, 0, 0, true, NOT_RETRANSMISSION, &num_bytes_consumed);
+      GetNthClientInitiatedStreamId(0), iov_.iov_len, 0, 0, true,
+      NOT_RETRANSMISSION, &num_bytes_consumed);
   EXPECT_EQ(4u, num_bytes_consumed);
 
   // Ensure the packet is successfully created.
@@ -1407,15 +1408,14 @@
 
   // Send one byte of stream data.
   MakeIOVector("a", &iov_);
-  producer_.SaveStreamData(
-      QuicUtils::GetHeadersStreamId(client_framer_.transport_version()), &iov_,
-      1u, 0u, iov_.iov_len);
+  producer_.SaveStreamData(GetNthClientInitiatedStreamId(0), &iov_, 1u, 0u,
+                           iov_.iov_len);
   EXPECT_CALL(delegate_, OnSerializedPacket(_))
       .WillOnce(Invoke(this, &QuicPacketCreatorTest::SaveSerializedPacket));
   size_t num_bytes_consumed;
   creator_.CreateAndSerializeStreamFrame(
-      QuicUtils::GetHeadersStreamId(client_framer_.transport_version()),
-      iov_.iov_len, 0, 0, true, NOT_RETRANSMISSION, &num_bytes_consumed);
+      GetNthClientInitiatedStreamId(0), iov_.iov_len, 0, 0, true,
+      NOT_RETRANSMISSION, &num_bytes_consumed);
   EXPECT_EQ(1u, num_bytes_consumed);
 
   // Check that a packet is created.
@@ -1445,9 +1445,8 @@
 
   creator_.set_encryption_level(ENCRYPTION_INITIAL);
   EXPECT_CALL(delegate_, OnUnrecoverableError(_, _));
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetHeadersStreamId(client_framer_.transport_version()),
-      /*fin=*/false, 0u, QuicStringPiece());
+  QuicStreamFrame stream_frame(GetNthClientInitiatedStreamId(0),
+                               /*fin=*/false, 0u, QuicStringPiece());
   EXPECT_QUIC_BUG(
       creator_.AddSavedFrame(QuicFrame(stream_frame), NOT_RETRANSMISSION),
       "Cannot send stream data with level: ENCRYPTION_INITIAL");
@@ -1461,9 +1460,8 @@
 
   creator_.set_encryption_level(ENCRYPTION_HANDSHAKE);
   EXPECT_CALL(delegate_, OnUnrecoverableError(_, _));
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetHeadersStreamId(client_framer_.transport_version()),
-      /*fin=*/false, 0u, QuicStringPiece());
+  QuicStreamFrame stream_frame(GetNthClientInitiatedStreamId(0),
+                               /*fin=*/false, 0u, QuicStringPiece());
   EXPECT_QUIC_BUG(
       creator_.AddSavedFrame(QuicFrame(stream_frame), NOT_RETRANSMISSION),
       "Cannot send stream data with level: ENCRYPTION_HANDSHAKE");
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index c8a0a02..584593a 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -1295,6 +1295,9 @@
 }
 
 TEST_P(QuicSessionTestServer, OnStreamFrameFinStaticStreamId) {
+  if (connection_->version().DoesNotHaveHeadersStream()) {
+    return;
+  }
   QuicStreamId headers_stream_id =
       QuicUtils::GetHeadersStreamId(connection_->transport_version());
   std::unique_ptr<TestStream> fake_headers_stream = QuicMakeUnique<TestStream>(
@@ -1316,6 +1319,9 @@
 }
 
 TEST_P(QuicSessionTestServer, OnRstStreamStaticStreamId) {
+  if (connection_->version().DoesNotHaveHeadersStream()) {
+    return;
+  }
   QuicStreamId headers_stream_id =
       QuicUtils::GetHeadersStreamId(connection_->transport_version());
   std::unique_ptr<TestStream> fake_headers_stream = QuicMakeUnique<TestStream>(
diff --git a/quic/core/quic_utils.cc b/quic/core/quic_utils.cc
index 996e78f..955e82f 100644
--- a/quic/core/quic_utils.cc
+++ b/quic/core/quic_utils.cc
@@ -399,6 +399,11 @@
 
 // static
 QuicStreamId QuicUtils::GetHeadersStreamId(QuicTransportVersion version) {
+  if (version == QUIC_VERSION_99) {
+    // TODO(b/130659182) Turn this into a QUIC_BUG once we've fully removed
+    // the headers stream in those versions.
+    return GetQuicFlag(FLAGS_quic_headers_stream_id_in_v99);
+  }
   return GetFirstBidirectionalStreamId(version, Perspective::IS_CLIENT);
 }
 
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc
index f0b5416..33faa3c 100644
--- a/quic/core/quic_versions.cc
+++ b/quic/core/quic_versions.cc
@@ -63,6 +63,17 @@
   return transport_version >= QUIC_VERSION_99;
 }
 
+bool ParsedQuicVersion::DoesNotHaveHeadersStream() const {
+  return VersionLacksHeadersStream(transport_version);
+}
+
+bool VersionLacksHeadersStream(QuicTransportVersion transport_version) {
+  if (GetQuicFlag(FLAGS_quic_headers_stream_id_in_v99) == 0) {
+    return false;
+  }
+  return transport_version == QUIC_VERSION_99;
+}
+
 std::ostream& operator<<(std::ostream& os, const ParsedQuicVersion& version) {
   os << ParsedQuicVersionToString(version);
   return os;
@@ -418,6 +429,8 @@
 
   // Enable necessary flags.
   SetQuicFlag(FLAGS_quic_supports_tls_handshake, true);
+  // 60 is the highest multiple of 4 and one-byte variable length integer.
+  SetQuicFlag(FLAGS_quic_headers_stream_id_in_v99, 60);
   SetQuicReloadableFlag(quic_deprecate_ack_bundling_mode, true);
   SetQuicReloadableFlag(quic_rpm_decides_when_to_send_acks, true);
   SetQuicReloadableFlag(quic_use_uber_loss_algorithm, true);
diff --git a/quic/core/quic_versions.h b/quic/core/quic_versions.h
index dd10d96..59005cd 100644
--- a/quic/core/quic_versions.h
+++ b/quic/core/quic_versions.h
@@ -169,6 +169,9 @@
 
   // Returns whether this version supports client connection ID.
   bool SupportsClientConnectionIds() const;
+
+  // Returns whether this version does not have the Google QUIC headers stream.
+  bool DoesNotHaveHeadersStream() const;
 };
 
 QUIC_EXPORT_PRIVATE ParsedQuicVersion UnsupportedQuicVersion();
@@ -408,6 +411,11 @@
   return transport_version == QUIC_VERSION_99;
 }
 
+// Returns whether |transport_version| does not have the
+// Google QUIC headers stream.
+QUIC_EXPORT_PRIVATE bool VersionLacksHeadersStream(
+    QuicTransportVersion transport_version);
+
 // Returns the ALPN string to use in TLS for this version of QUIC.
 QUIC_EXPORT_PRIVATE std::string AlpnForVersion(
     ParsedQuicVersion parsed_version);