Enforce header list size limit in QpackDecodedHeadersAccumulator.

Move header list size limit enforcement from QuicHeaderList to
QpackDecodedHeadersAccumulator when using IETF QUIC.  This provides an explicit
signal, instead of having to rely on QuicHeaderList being empty.

Also change limit counting to account for 32 bytes of overhead per header field
as prescribed by the specification.  Keep |uncompressed_size| passed in to
QuicHeaderList without this overhead, just like it is when called from
HpackDecoder::ListenerAdapter (used for Google QUIC) and from AsHeaderList (used
for tests).

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 282822239
Change-Id: If9b27ed3189cad5bfdb6be415196e7c0f2267a74
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index b0c4343..4c5e642 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -193,6 +193,7 @@
       visitor_(nullptr),
       blocked_on_decoding_headers_(false),
       headers_decompressed_(false),
+      header_list_size_limit_exceeded_(false),
       headers_payload_length_(0),
       trailers_payload_length_(0),
       trailers_decompressed_(false),
@@ -227,6 +228,7 @@
       visitor_(nullptr),
       blocked_on_decoding_headers_(false),
       headers_decompressed_(false),
+      header_list_size_limit_exceeded_(false),
       headers_payload_length_(0),
       trailers_payload_length_(0),
       trailers_decompressed_(false),
@@ -517,13 +519,13 @@
                                         size_t frame_len,
                                         const QuicHeaderList& header_list) {
   // TODO(b/134706391): remove |fin| argument.
-  // The headers list avoid infinite buffering by clearing the headers list
-  // if the current headers are too large. So if the list is empty here
-  // then the headers list must have been too large, and the stream should
-  // be reset.
-  // TODO(rch): Use an explicit "headers too large" signal. An empty header list
-  // might be acceptable if it corresponds to a trailing header frame.
-  if (header_list.empty()) {
+  // When using Google QUIC, an empty header list indicates that the size limit
+  // has been exceeded.
+  // When using IETF QUIC, there is an explicit signal from
+  // QpackDecodedHeadersAccumulator.
+  if ((VersionUsesHttp3(transport_version()) &&
+       header_list_size_limit_exceeded_) ||
+      (!VersionUsesHttp3(transport_version()) && header_list.empty())) {
     OnHeadersTooLarge();
     if (IsDoneReading()) {
       return;
@@ -537,6 +539,8 @@
 }
 
 void QuicSpdyStream::OnHeadersDecoded(QuicHeaderList headers) {
+  header_list_size_limit_exceeded_ =
+      qpack_decoded_headers_accumulator_->header_list_size_limit_exceeded();
   qpack_decoded_headers_accumulator_.reset();
 
   QuicSpdySession::LogHeaderCompressionRatioHistogram(
@@ -564,8 +568,6 @@
 void QuicSpdyStream::OnHeaderDecodingError(QuicStringPiece error_message) {
   qpack_decoded_headers_accumulator_.reset();
 
-  // TODO(b/124216424): Use HTTP_EXCESSIVE_LOAD instead if indicated by
-  // |qpack_decoded_headers_accumulator_|.
   std::string connection_close_error_message = QuicStrCat(
       "Error decoding ", headers_decompressed_ ? "trailers" : "headers",
       " on stream ", id(), ": ", error_message);
@@ -575,7 +577,8 @@
 
 void QuicSpdyStream::OnHeadersTooLarge() {
   if (VersionUsesHttp3(transport_version())) {
-    // TODO(124216424): Use HTTP_EXCESSIVE_LOAD error code.
+    // TODO(b/124216424): Reset stream with H3_REQUEST_CANCELLED (if client)
+    // or with H3_REQUEST_REJECTED (if server).
     std::string error_message =
         QuicStrCat("Too large headers received on stream ", id());
     CloseConnectionWithDetails(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h
index a7a0939..e315dcc 100644
--- a/quic/core/http/quic_spdy_stream.h
+++ b/quic/core/http/quic_spdy_stream.h
@@ -281,6 +281,9 @@
   bool blocked_on_decoding_headers_;
   // True if the headers have been completely decompressed.
   bool headers_decompressed_;
+  // True if uncompressed headers or trailers exceed maximum allowed size
+  // advertised to peer via SETTINGS_MAX_HEADER_LIST_SIZE.
+  bool header_list_size_limit_exceeded_;
   // Contains a copy of the decompressed header (name, value) pairs until they
   // are consumed via Readv.
   QuicHeaderList header_list_;
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 39bd6b0..74ad6ef 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -346,26 +346,36 @@
 TEST_P(QuicSpdyStreamTest, ProcessTooLargeHeaderList) {
   Initialize(kShouldProcessData);
 
-  QuicHeaderList headers;
-  stream_->OnStreamHeadersPriority(
-      spdy::SpdyStreamPrecedence(kV3HighestPriority));
+  if (!UsesHttp3()) {
+    QuicHeaderList headers;
+    stream_->OnStreamHeadersPriority(
+        spdy::SpdyStreamPrecedence(kV3HighestPriority));
 
-  if (UsesHttp3()) {
-    EXPECT_CALL(
-        *connection_,
-        CloseConnection(
-            QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
-            MatchesRegex("Too large headers received on stream \\d+"), _));
-  } else {
     EXPECT_CALL(*session_,
                 SendRstStream(stream_->id(), QUIC_HEADERS_TOO_LARGE, 0));
-  }
+    stream_->OnStreamHeaderList(false, 1 << 20, headers);
 
-  stream_->OnStreamHeaderList(false, 1 << 20, headers);
-
-  if (!UsesHttp3()) {
     EXPECT_THAT(stream_->stream_error(), IsStreamError(QUIC_HEADERS_TOO_LARGE));
+
+    return;
   }
+
+  // Header list size includes 32 bytes for overhead per header field.
+  session_->set_max_inbound_header_list_size(40);
+  std::string headers =
+      HeadersFrame({std::make_pair("foo", "too long headers")});
+
+  QuicStreamFrame frame(stream_->id(), false, 0, headers);
+
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE,
+                      MatchesRegex("Too large headers received on stream \\d+"),
+                      _));
+
+  stream_->OnStreamFrame(frame);
+
+  EXPECT_TRUE(stream_->header_list().empty());
 }
 
 TEST_P(QuicSpdyStreamTest, ProcessHeaderListWithFin) {