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: 258400699
Change-Id: I5b28b882bf89162cfbe7ab56f34cbe0555ed475e
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_client_base.cc b/quic/tools/quic_client_base.cc
index b03f7cc..e53911d 100644
--- a/quic/tools/quic_client_base.cc
+++ b/quic/tools/quic_client_base.cc
@@ -85,6 +85,10 @@
       break;
     }
   }
+  if (max_allowed_push_id_ > 0 &&
+      dynamic_cast<QuicSpdyClientSession*>(session()))
+    static_cast<QuicSpdyClientSession*>(session())->set_max_allowed_push_id(
+        max_allowed_push_id_);
   return session()->connection()->connected();
 }
 
diff --git a/quic/tools/quic_client_base.h b/quic/tools/quic_client_base.h
index fb15b86..a54c621 100644
--- a/quic/tools/quic_client_base.h
+++ b/quic/tools/quic_client_base.h
@@ -211,6 +211,9 @@
     crypto_config_.set_pre_shared_key(key);
   }
 
+  // Set the max promise id for the client session.
+  void set_max_allowed_push_id(QuicStreamId max) { max_allowed_push_id_ = max; }
+
  protected:
   // TODO(rch): Move GetNumSentClientHellosFromSession and
   // GetNumReceivedServerConfigUpdatesFromSession into a new/better
@@ -329,6 +332,9 @@
   // The network helper used to create sockets and manage the event loop.
   // Not owned by this class.
   std::unique_ptr<NetworkHelper> network_helper_;
+
+  // The max promise id to set on the client session when created.
+  QuicStreamId max_allowed_push_id_;
 };
 
 }  // namespace quic