gfe-relnote: Close connection if incoming MAX_PUSH_ID frame tries to reduce maximum push ID.  Behavior change in IETF QUIC only, protected by gfe2_reloadable_flag_quic_enable_version_draft_25_v3 and gfe2_reloadable_flag_quic_enable_version_draft_27.

As Renjie suggested at cl/302687788, this is done in QuicSpdySession instead of
QuicReceiveControlStream.

PiperOrigin-RevId: 306424146
Change-Id: I51ee39b6ac9c8ead8616009d60f1dd5c933c25ab
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index 7b54756..d24e3dd 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -91,10 +91,7 @@
     return false;
   }
 
-  // TODO(b/124216424): Signal error if received push ID is smaller than a
-  // previously received value.
-  spdy_session()->OnMaxPushIdFrame(frame.push_id);
-  return true;
+  return spdy_session()->OnMaxPushIdFrame(frame.push_id);
 }
 
 bool QuicReceiveControlStream::OnGoAwayFrame(const GoAwayFrame& frame) {
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index c7bb953..f6dd0a7 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -1255,14 +1255,23 @@
   quiche::QuicheOptional<PushId> old_max_push_id = max_push_id_;
   max_push_id_ = max_push_id;
 
-  if (!old_max_push_id.has_value() ||
-      max_push_id_.value() > old_max_push_id.value()) {
+  if (!old_max_push_id.has_value() || max_push_id > old_max_push_id.value()) {
     OnCanCreateNewOutgoingStream(true);
     return true;
   }
 
   // Equal value is not considered an error.
-  return max_push_id_.value() >= old_max_push_id.value();
+  if (max_push_id < old_max_push_id.value()) {
+    CloseConnectionWithDetails(
+        QUIC_HTTP_INVALID_MAX_PUSH_ID,
+        quiche::QuicheStrCat(
+            "MAX_PUSH_ID received with value ", max_push_id,
+            " which is smaller that previously received value ",
+            old_max_push_id.value()));
+    return false;
+  }
+
+  return true;
 }
 
 void QuicSpdySession::SendMaxPushId() {
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 307dabb..63fb18d 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -424,6 +424,15 @@
     return std::string(priority_buffer.get(), priority_frame_length);
   }
 
+  std::string SerializeMaxPushIdFrame(PushId push_id) {
+    MaxPushIdFrame max_push_id_frame;
+    max_push_id_frame.push_id = push_id;
+    std::unique_ptr<char[]> buffer;
+    QuicByteCount frame_length =
+        HttpEncoder::SerializeMaxPushIdFrame(max_push_id_frame, &buffer);
+    return std::string(buffer.get(), frame_length);
+  }
+
   QuicStreamId StreamCountToId(QuicStreamCount stream_count,
                                Perspective perspective,
                                bool bidirectional) {
@@ -1770,6 +1779,55 @@
   }
 }
 
+TEST_P(QuicSpdySessionTestServer, ReduceMaxPushId) {
+  if (!VersionUsesHttp3(transport_version())) {
+    return;
+  }
+
+  StrictMock<MockHttp3DebugVisitor> debug_visitor;
+  session_.set_debug_visitor(&debug_visitor);
+
+  // Use an arbitrary stream id for incoming control stream.
+  QuicStreamId stream_id =
+      GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
+  char type[] = {kControlStream};
+  quiche::QuicheStringPiece stream_type(type, 1);
+
+  QuicStreamOffset offset = 0;
+  QuicStreamFrame data1(stream_id, false, offset, stream_type);
+  offset += stream_type.length();
+  EXPECT_CALL(debug_visitor, OnPeerControlStreamCreated(stream_id));
+  session_.OnStreamFrame(data1);
+  EXPECT_EQ(stream_id,
+            QuicSpdySessionPeer::GetReceiveControlStream(&session_)->id());
+
+  SettingsFrame settings;
+  std::string settings_frame = EncodeSettings(settings);
+  QuicStreamFrame data2(stream_id, false, offset, settings_frame);
+  offset += settings_frame.length();
+
+  EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(settings));
+  session_.OnStreamFrame(data2);
+
+  std::string max_push_id_frame1 = SerializeMaxPushIdFrame(/* push_id = */ 3);
+  QuicStreamFrame data3(stream_id, false, offset, max_push_id_frame1);
+  offset += max_push_id_frame1.length();
+
+  EXPECT_CALL(debug_visitor, OnMaxPushIdFrameReceived(_));
+  session_.OnStreamFrame(data3);
+
+  std::string max_push_id_frame2 = SerializeMaxPushIdFrame(/* push_id = */ 1);
+  QuicStreamFrame data4(stream_id, false, offset, max_push_id_frame2);
+
+  EXPECT_CALL(debug_visitor, OnMaxPushIdFrameReceived(_));
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_HTTP_INVALID_MAX_PUSH_ID,
+                              "MAX_PUSH_ID received with value 1 which is "
+                              "smaller that previously received value 3",
+                              _));
+  session_.OnStreamFrame(data4);
+}
+
 class QuicSpdySessionTestClient : public QuicSpdySessionTestBase {
  protected:
   QuicSpdySessionTestClient()
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index efbdc69..776ae8c 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -198,6 +198,7 @@
     RETURN_STRING_LITERAL(QUIC_HTTP_CLOSED_CRITICAL_STREAM);
     RETURN_STRING_LITERAL(QUIC_HTTP_MISSING_SETTINGS_FRAME);
     RETURN_STRING_LITERAL(QUIC_HTTP_DUPLICATE_SETTING_IDENTIFIER);
+    RETURN_STRING_LITERAL(QUIC_HTTP_INVALID_MAX_PUSH_ID);
     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);
@@ -557,6 +558,8 @@
               static_cast<uint64_t>(QuicHttp3ErrorCode::MISSING_SETTINGS)};
     case QUIC_HTTP_DUPLICATE_SETTING_IDENTIFIER:
       return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::SETTINGS_ERROR)};
+    case QUIC_HTTP_INVALID_MAX_PUSH_ID:
+      return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::ID_ERROR)};
     case QUIC_HPACK_INDEX_VARINT_ERROR:
       return {true, static_cast<uint64_t>(INTERNAL_ERROR)};
     case QUIC_HPACK_NAME_LENGTH_VARINT_ERROR:
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index b878de7..46e71b9 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -426,6 +426,9 @@
   QUIC_HTTP_MISSING_SETTINGS_FRAME = 157,
   // The received SETTINGS frame contains duplicate setting identifiers.
   QUIC_HTTP_DUPLICATE_SETTING_IDENTIFIER = 158,
+  // MAX_PUSH_ID frame received with push ID value smaller than a previously
+  // received value.
+  QUIC_HTTP_INVALID_MAX_PUSH_ID = 159,
 
   // HPACK header block decoding errors.
   // Index varint beyond implementation limit.
@@ -462,7 +465,7 @@
   QUIC_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT = 150,
 
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 159,
+  QUIC_LAST_ERROR = 160,
 };
 // 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