Close QUIC connection when QuicSpdySession receives server push stream.

Since we don't send out MAX_PUSH_ID, we should never receive server push stream.

Protected by quic_reloadable_flag_quic_decline_server_push_stream.

PiperOrigin-RevId: 387622986
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index dc773ae..56b0db3 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -470,6 +470,9 @@
   h2_deframer_.set_visitor(spdy_framer_visitor_.get());
   h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get());
   spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
+  if (decline_server_push_stream_) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_decline_server_push_stream);
+  }
 }
 
 QuicSpdySession::~QuicSpdySession() {
@@ -1358,6 +1361,11 @@
       return receive_control_stream_;
     }
     case kServerPushStream: {  // Push Stream.
+      if (decline_server_push_stream_) {
+        CloseConnectionWithDetails(QUIC_HTTP_RECEIVE_SERVER_PUSH,
+                                   "Received server push stream");
+        return nullptr;
+      }
       QuicSpdyStream* stream = CreateIncomingStream(pending);
       return stream;
     }
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 8085dd8..78c132a 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -678,6 +678,10 @@
   // Limited to kMaxUnassociatedWebTransportStreams; when the list is full,
   // oldest streams are evicated first.
   std::list<BufferedWebTransportStream> buffered_streams_;
+
+  // Latched value of flag_quic_decline_server_push_stream.
+  const bool decline_server_push_stream_ =
+      GetQuicReloadableFlag(quic_decline_server_push_stream);
 };
 
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index be475b8..5a98432 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -2007,25 +2007,33 @@
   std::string frame_type1 = absl::HexStringToBytes("01");
   QuicStreamId stream_id1 =
       GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 0);
-  session_.OnStreamFrame(QuicStreamFrame(stream_id1, /* fin = */ false,
-                                         /* offset = */ 0, frame_type1));
+  if (!GetQuicReloadableFlag(quic_decline_server_push_stream)) {
+    session_.OnStreamFrame(QuicStreamFrame(stream_id1, /* fin = */ false,
+                                           /* offset = */ 0, frame_type1));
 
-  EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
-  QuicStream* stream = session_.GetOrCreateStream(stream_id1);
-  EXPECT_EQ(1u, QuicStreamPeer::bytes_consumed(stream));
-  EXPECT_EQ(1u, session_.flow_controller()->bytes_consumed());
+    EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
+    QuicStream* stream = session_.GetOrCreateStream(stream_id1);
+    EXPECT_EQ(1u, QuicStreamPeer::bytes_consumed(stream));
+    EXPECT_EQ(1u, session_.flow_controller()->bytes_consumed());
 
-  // The same stream type can be encoded differently.
-  std::string frame_type2 = absl::HexStringToBytes("80000001");
-  QuicStreamId stream_id2 =
-      GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 1);
-  session_.OnStreamFrame(QuicStreamFrame(stream_id2, /* fin = */ false,
-                                         /* offset = */ 0, frame_type2));
+    // The same stream type can be encoded differently.
+    std::string frame_type2 = absl::HexStringToBytes("80000001");
+    QuicStreamId stream_id2 =
+        GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 1);
+    session_.OnStreamFrame(QuicStreamFrame(stream_id2, /* fin = */ false,
+                                           /* offset = */ 0, frame_type2));
 
-  EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
-  stream = session_.GetOrCreateStream(stream_id2);
-  EXPECT_EQ(4u, QuicStreamPeer::bytes_consumed(stream));
-  EXPECT_EQ(5u, session_.flow_controller()->bytes_consumed());
+    EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
+    stream = session_.GetOrCreateStream(stream_id2);
+    EXPECT_EQ(4u, QuicStreamPeer::bytes_consumed(stream));
+    EXPECT_EQ(5u, session_.flow_controller()->bytes_consumed());
+  } else {
+    EXPECT_CALL(*connection_,
+                CloseConnection(QUIC_HTTP_RECEIVE_SERVER_PUSH, _, _))
+        .Times(1);
+    session_.OnStreamFrame(QuicStreamFrame(stream_id1, /* fin = */ false,
+                                           /* offset = */ 0, frame_type1));
+  }
 }
 
 TEST_P(QuicSpdySessionTestClient, Http3ServerPushOutofOrderFrame) {
@@ -2053,10 +2061,17 @@
   session_.OnStreamFrame(data2);
   EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
 
-  session_.OnStreamFrame(data1);
-  EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
-  QuicStream* stream = session_.GetOrCreateStream(stream_id);
-  EXPECT_EQ(3u, stream->highest_received_byte_offset());
+  if (!GetQuicReloadableFlag(quic_decline_server_push_stream)) {
+    session_.OnStreamFrame(data1);
+    EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
+    QuicStream* stream = session_.GetOrCreateStream(stream_id);
+    EXPECT_EQ(3u, stream->highest_received_byte_offset());
+  } else {
+    EXPECT_CALL(*connection_,
+                CloseConnection(QUIC_HTTP_RECEIVE_SERVER_PUSH, _, _))
+        .Times(1);
+    session_.OnStreamFrame(data1);
+  }
 }
 
 TEST_P(QuicSpdySessionTestServer, OnStreamFrameLost) {
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index e82e7cb..c3fcb88 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -238,6 +238,7 @@
     RETURN_STRING_LITERAL(QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS);
     RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SPDY_SETTING);
     RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SPDY_FRAME);
+    RETURN_STRING_LITERAL(QUIC_HTTP_RECEIVE_SERVER_PUSH);
     RETURN_STRING_LITERAL(QUIC_HPACK_INDEX_VARINT_ERROR);
     RETURN_STRING_LITERAL(QUIC_HPACK_NAME_LENGTH_VARINT_ERROR);
     RETURN_STRING_LITERAL(QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR);
@@ -678,6 +679,9 @@
     case QUIC_HTTP_STREAM_LIMIT_TOO_LOW:
       return {false, static_cast<uint64_t>(
                          QuicHttp3ErrorCode::GENERAL_PROTOCOL_ERROR)};
+    case QUIC_HTTP_RECEIVE_SERVER_PUSH:
+      return {false, static_cast<uint64_t>(
+                         QuicHttp3ErrorCode::GENERAL_PROTOCOL_ERROR)};
     case QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH:
       return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::SETTINGS_ERROR)};
     case QUIC_HTTP_ZERO_RTT_REJECTION_SETTINGS_MISMATCH:
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 9fa422f..53a8810 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -510,6 +510,9 @@
   QUIC_HTTP_RECEIVE_SPDY_SETTING = 169,
   // HTTP/3 session received an HTTP/2 only frame.
   QUIC_HTTP_RECEIVE_SPDY_FRAME = 171,
+  // HTTP/3 session received SERVER_PUSH stream, which is an error because
+  // PUSH_PROMISE is not accepted.
+  QUIC_HTTP_RECEIVE_SERVER_PUSH = 205,
 
   // HPACK header block decoding errors.
   // Index varint beyond implementation limit.
@@ -597,7 +600,7 @@
   QUIC_TLS_CERTIFICATE_REQUIRED = 202,
 
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 205,
+  QUIC_LAST_ERROR = 206,
 };
 // QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
 // or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 0aa0ccc..7218487 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -89,6 +89,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_discard_packets_with_invalid_cid, true)
 // If true, quic dispatcher supports one connection to use multiple connection IDs. 
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_dispatcher_support_multiple_cid_per_connection_v2, true)
+// If true, receiving server push stream will trigger QUIC connection close.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_decline_server_push_stream, false)
 // If true, require handshake confirmation for QUIC connections, functionally disabling 0-rtt handshakes.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_require_handshake_confirmation, false)
 // If true, reset per packet state before processing undecryptable packets.