gfe-relnote: Add support for sending MAX_PUSH_ID, defaulting to zero and close connection if we receive a push ID higher than the max.
Protected by the existing disabled and blocked quic_enable_version_99 reloadable flag.

PiperOrigin-RevId: 258759749
Change-Id: I5a10b4145931f22ec359e8b96c36cc267de2306a
diff --git a/quic/core/http/quic_spdy_client_session_base.cc b/quic/core/http/quic_spdy_client_session_base.cc
index ba5a9cc..f27411a 100644
--- a/quic/core/http/quic_spdy_client_session_base.cc
+++ b/quic/core/http/quic_spdy_client_session_base.cc
@@ -24,7 +24,8 @@
     : QuicSpdySession(connection, nullptr, config, supported_versions),
       push_promise_index_(push_promise_index),
       largest_promised_stream_id_(
-          QuicUtils::GetInvalidStreamId(connection->transport_version())) {}
+          QuicUtils::GetInvalidStreamId(connection->transport_version())),
+      max_allowed_push_id_(0) {}
 
 QuicSpdyClientSessionBase::~QuicSpdyClientSessionBase() {
   //  all promised streams for this session
@@ -88,6 +89,14 @@
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
     return;
   }
+
+  if (VersionHasIetfQuicFrames(connection()->transport_version()) &&
+      promised_stream_id > max_allowed_push_id()) {
+    connection()->CloseConnection(
+        QUIC_INVALID_STREAM_ID,
+        "Received push stream id higher than MAX_PUSH_ID.",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  }
   largest_promised_stream_id_ = promised_stream_id;
 
   QuicSpdyStream* stream = GetSpdyDataStream(stream_id);
@@ -209,4 +218,9 @@
   return !HasActiveRequestStreams() && promised_by_id_.empty();
 }
 
+void QuicSpdyClientSessionBase::set_max_allowed_push_id(
+    QuicStreamId max_allowed_push_id) {
+  max_allowed_push_id_ = max_allowed_push_id;
+}
+
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_client_session_base.h b/quic/core/http/quic_spdy_client_session_base.h
index aec5e75..98b8589 100644
--- a/quic/core/http/quic_spdy_client_session_base.h
+++ b/quic/core/http/quic_spdy_client_session_base.h
@@ -111,6 +111,10 @@
   // Returns true if there are no active requests and no promised streams.
   bool ShouldReleaseHeadersStreamSequencerBuffer() override;
 
+  void set_max_allowed_push_id(QuicStreamId max_allowed_push_id);
+
+  QuicStreamId max_allowed_push_id() { return max_allowed_push_id_; }
+
   size_t get_max_promises() const {
     return max_open_incoming_unidirectional_streams() *
            kMaxPromisedStreamsMultiplier;
@@ -134,6 +138,7 @@
   QuicClientPushPromiseIndex* push_promise_index_;
   QuicPromisedByIdMap promised_by_id_;
   QuicStreamId largest_promised_stream_id_;
+  QuicStreamId max_allowed_push_id_;
 };
 
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index 716ae42..dfa3031 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -610,6 +610,38 @@
                                 QuicHeaderList());
 }
 
+TEST_P(QuicSpdyClientSessionTest, PushPromiseStreamIdTooHigh) {
+  // Initialize crypto before the client session will create a stream.
+  CompleteCryptoHandshake();
+  QuicStreamId stream_id =
+      QuicSessionPeer::GetNextOutgoingBidirectionalStreamId(session_.get());
+  QuicSessionPeer::ActivateStream(
+      session_.get(), QuicMakeUnique<QuicSpdyClientStream>(
+                          stream_id, session_.get(), BIDIRECTIONAL));
+
+  session_->set_max_allowed_push_id(GetNthServerInitiatedUnidirectionalStreamId(
+      connection_->transport_version(), 10));
+  if (VersionHasIetfQuicFrames(connection_->transport_version())) {
+    // TODO(b/136295430) Use PushId to represent Push IDs instead of
+    // QuicStreamId.
+    EXPECT_CALL(
+        *connection_,
+        CloseConnection(QUIC_INVALID_STREAM_ID,
+                        "Received push stream id higher than MAX_PUSH_ID.", _));
+  }
+  auto promise_id = GetNthServerInitiatedUnidirectionalStreamId(
+      connection_->transport_version(), 11);
+  auto headers = QuicHeaderList();
+  headers.OnHeaderBlockStart();
+  headers.OnHeader(":path", "/bar");
+  headers.OnHeader(":authority", "www.google.com");
+  headers.OnHeader(":version", "HTTP/1.1");
+  headers.OnHeader(":method", "GET");
+  headers.OnHeader(":scheme", "https");
+  headers.OnHeaderBlockEnd(0, 0);
+  session_->OnPromiseHeaderList(stream_id, promise_id, 0, headers);
+}
+
 TEST_P(QuicSpdyClientSessionTest, PushPromiseOnPromiseHeadersAlreadyClosed) {
   // Initialize crypto before the client session will create a stream.
   CompleteCryptoHandshake();
diff --git a/quic/tools/quic_spdy_client_base.cc b/quic/tools/quic_spdy_client_base.cc
index 06abc96..c44c677 100644
--- a/quic/tools/quic_spdy_client_base.cc
+++ b/quic/tools/quic_spdy_client_base.cc
@@ -60,6 +60,9 @@
 void QuicSpdyClientBase::InitializeSession() {
   client_session()->Initialize();
   client_session()->CryptoConnect();
+  if (max_allowed_push_id_ > 0) {
+    client_session()->set_max_allowed_push_id(max_allowed_push_id_);
+  }
 }
 
 void QuicSpdyClientBase::OnClose(QuicSpdyStream* stream) {
diff --git a/quic/tools/quic_spdy_client_base.h b/quic/tools/quic_spdy_client_base.h
index 87fc664..dc1811d 100644
--- a/quic/tools/quic_spdy_client_base.h
+++ b/quic/tools/quic_spdy_client_base.h
@@ -136,6 +136,9 @@
   }
   bool drop_response_body() const { return drop_response_body_; }
 
+  // Set the max promise id for the client session.
+  void set_max_allowed_push_id(QuicStreamId max) { max_allowed_push_id_ = max; }
+
  protected:
   int GetNumSentClientHellosFromSession() override;
   int GetNumReceivedServerConfigUpdatesFromSession() override;
@@ -209,6 +212,9 @@
   std::unique_ptr<ClientQuicDataToResend> push_promise_data_to_resend_;
 
   bool drop_response_body_ = false;
+
+  // The max promise id to set on the client session when created.
+  QuicStreamId max_allowed_push_id_;
 };
 
 }  // namespace quic