Public fix: Limit buffering on the QPACK encoder stream.

A malicious peer may block our stack from sending data on the QPACK encoder
stream (via flow control) while still generating traffic, causing an unlimited
amount of data to be buffered.  To prevent this from happening, this CL makes
QpackEncoder check if the amount of data currently buffered on the send encoder
stream exceeds 64 kB, and if so, it does not emit any further encoder stream
instructions.  It still encodes headers in a spec-compliant way using string
literals and references to static table entries and already emitted dynamic
table entries.

Note that since the buffered amount of data is only checked at the beginning of
encoding each header block, it might increase above the threshold by as much as
encoder stream instructions required to encode the header block.  However,
subsequent header blocks will not trigger any writes on the encoder stream until
the number of buffered bytes goes back below the threshold.

Protected by FLAGS_quic_reloadable_flag_quic_limit_encoder_stream_buffering.

PiperOrigin-RevId: 428482893
diff --git a/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
index 43bf3d3..676121e 100644
--- a/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
+++ b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
@@ -431,6 +431,7 @@
   void WriteStreamData(absl::string_view data) override {
     stream_data.push_back(std::string(data.data(), data.size()));
   }
+  uint64_t NumBytesBuffered() const override { return 0; }
 
   // Release some (possibly none) delayed stream data.
   void MaybeTransmitSomeData() {
diff --git a/quic/core/qpack/qpack_encoder.cc b/quic/core/qpack/qpack_encoder.cc
index 3ae9441..974f7b2 100644
--- a/quic/core/qpack/qpack_encoder.cc
+++ b/quic/core/qpack/qpack_encoder.cc
@@ -87,6 +87,12 @@
   const QuicByteCount initial_encoder_stream_buffered_byte_count =
       encoder_stream_sender_.BufferedByteCount();
 
+  bool can_write_to_encoder_stream = true;
+  if (GetQuicReloadableFlag(quic_limit_encoder_stream_buffering)) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_limit_encoder_stream_buffering);
+    can_write_to_encoder_stream = encoder_stream_sender_.CanWrite();
+  }
+
   Representations representations;
   representations.reserve(header_list.size());
 
@@ -152,17 +158,20 @@
                          std::min(smallest_blocking_index, index))) {
             dynamic_table_insertion_blocked = true;
           } else {
-            // If allowed, duplicate entry and refer to it.
-            encoder_stream_sender_.SendDuplicate(
-                QpackAbsoluteIndexToEncoderStreamRelativeIndex(
-                    index, header_table_.inserted_entry_count()));
-            uint64_t new_index = header_table_.InsertEntry(name, value);
-            representations.push_back(EncodeIndexedHeaderField(
-                is_static, new_index, referred_indices));
-            smallest_blocking_index = std::min(smallest_blocking_index, index);
-            header_table_.set_dynamic_table_entry_referenced();
+            if (can_write_to_encoder_stream) {
+              // If allowed, duplicate entry and refer to it.
+              encoder_stream_sender_.SendDuplicate(
+                  QpackAbsoluteIndexToEncoderStreamRelativeIndex(
+                      index, header_table_.inserted_entry_count()));
+              uint64_t new_index = header_table_.InsertEntry(name, value);
+              representations.push_back(EncodeIndexedHeaderField(
+                  is_static, new_index, referred_indices));
+              smallest_blocking_index =
+                  std::min(smallest_blocking_index, index);
+              header_table_.set_dynamic_table_entry_referenced();
 
-            break;
+              break;
+            }
           }
         }
 
@@ -182,15 +191,17 @@
                   header_table_.MaxInsertSizeWithoutEvictingGivenEntry(
                       smallest_blocking_index)) {
             // If allowed, insert entry into dynamic table and refer to it.
-            encoder_stream_sender_.SendInsertWithNameReference(is_static, index,
-                                                               value);
-            uint64_t new_index = header_table_.InsertEntry(name, value);
-            representations.push_back(EncodeIndexedHeaderField(
-                /* is_static = */ false, new_index, referred_indices));
-            smallest_blocking_index =
-                std::min<uint64_t>(smallest_blocking_index, new_index);
+            if (can_write_to_encoder_stream) {
+              encoder_stream_sender_.SendInsertWithNameReference(is_static,
+                                                                 index, value);
+              uint64_t new_index = header_table_.InsertEntry(name, value);
+              representations.push_back(EncodeIndexedHeaderField(
+                  /* is_static = */ false, new_index, referred_indices));
+              smallest_blocking_index =
+                  std::min<uint64_t>(smallest_blocking_index, new_index);
 
-            break;
+              break;
+            }
           }
 
           // Emit literal field with name reference.
@@ -208,18 +219,20 @@
           dynamic_table_insertion_blocked = true;
         } else {
           // If allowed, insert entry with name reference and refer to it.
-          encoder_stream_sender_.SendInsertWithNameReference(
-              is_static,
-              QpackAbsoluteIndexToEncoderStreamRelativeIndex(
-                  index, header_table_.inserted_entry_count()),
-              value);
-          uint64_t new_index = header_table_.InsertEntry(name, value);
-          representations.push_back(
-              EncodeIndexedHeaderField(is_static, new_index, referred_indices));
-          smallest_blocking_index = std::min(smallest_blocking_index, index);
-          header_table_.set_dynamic_table_entry_referenced();
+          if (can_write_to_encoder_stream) {
+            encoder_stream_sender_.SendInsertWithNameReference(
+                is_static,
+                QpackAbsoluteIndexToEncoderStreamRelativeIndex(
+                    index, header_table_.inserted_entry_count()),
+                value);
+            uint64_t new_index = header_table_.InsertEntry(name, value);
+            representations.push_back(EncodeIndexedHeaderField(
+                is_static, new_index, referred_indices));
+            smallest_blocking_index = std::min(smallest_blocking_index, index);
+            header_table_.set_dynamic_table_entry_referenced();
 
-          break;
+            break;
+          }
         }
 
         if ((blocking_allowed || index < known_received_count) &&
@@ -251,14 +264,16 @@
                        smallest_blocking_index)) {
           dynamic_table_insertion_blocked = true;
         } else {
-          encoder_stream_sender_.SendInsertWithoutNameReference(name, value);
-          uint64_t new_index = header_table_.InsertEntry(name, value);
-          representations.push_back(EncodeIndexedHeaderField(
-              /* is_static = */ false, new_index, referred_indices));
-          smallest_blocking_index =
-              std::min<uint64_t>(smallest_blocking_index, new_index);
+          if (can_write_to_encoder_stream) {
+            encoder_stream_sender_.SendInsertWithoutNameReference(name, value);
+            uint64_t new_index = header_table_.InsertEntry(name, value);
+            representations.push_back(EncodeIndexedHeaderField(
+                /* is_static = */ false, new_index, referred_indices));
+            smallest_blocking_index =
+                std::min<uint64_t>(smallest_blocking_index, new_index);
 
-          break;
+            break;
+          }
         }
 
         // Encode entry as string literals.
@@ -275,12 +290,18 @@
       encoder_stream_sender_.BufferedByteCount();
   QUICHE_DCHECK_GE(encoder_stream_buffered_byte_count,
                    initial_encoder_stream_buffered_byte_count);
+
   if (encoder_stream_sent_byte_count) {
     *encoder_stream_sent_byte_count =
         encoder_stream_buffered_byte_count -
         initial_encoder_stream_buffered_byte_count;
   }
-  encoder_stream_sender_.Flush();
+  if (can_write_to_encoder_stream) {
+    encoder_stream_sender_.Flush();
+  } else {
+    QUICHE_DCHECK_EQ(encoder_stream_buffered_byte_count,
+                     initial_encoder_stream_buffered_byte_count);
+  }
 
   ++header_list_count_;
 
diff --git a/quic/core/qpack/qpack_encoder_stream_sender.cc b/quic/core/qpack/qpack_encoder_stream_sender.cc
index 4079ba4..8b6f887 100644
--- a/quic/core/qpack/qpack_encoder_stream_sender.cc
+++ b/quic/core/qpack/qpack_encoder_stream_sender.cc
@@ -14,6 +14,14 @@
 
 namespace quic {
 
+namespace {
+
+// If QUIC stream bufferes more that this number of bytes,
+// CanWrite() will return false.
+constexpr uint64_t kMaxBytesBufferedByStream = 64 * 1024;
+
+}  // anonymous namespace
+
 QpackEncoderStreamSender::QpackEncoderStreamSender() : delegate_(nullptr) {}
 
 void QpackEncoderStreamSender::SendInsertWithNameReference(
@@ -44,6 +52,11 @@
       QpackInstructionWithValues::SetDynamicTableCapacity(capacity), &buffer_);
 }
 
+bool QpackEncoderStreamSender::CanWrite() const {
+  return delegate_ && delegate_->NumBytesBuffered() + buffer_.size() <=
+                          kMaxBytesBufferedByStream;
+}
+
 void QpackEncoderStreamSender::Flush() {
   if (buffer_.empty()) {
     return;
diff --git a/quic/core/qpack/qpack_encoder_stream_sender.h b/quic/core/qpack/qpack_encoder_stream_sender.h
index 44d777d..d8c6430 100644
--- a/quic/core/qpack/qpack_encoder_stream_sender.h
+++ b/quic/core/qpack/qpack_encoder_stream_sender.h
@@ -38,9 +38,18 @@
   // 5.2.4. Set Dynamic Table Capacity
   void SendSetDynamicTableCapacity(uint64_t capacity);
 
-  // Returns number of buffered bytes.
+  // Returns number of bytes buffered by this object.
+  // There is no limit on how much data this object is willing to buffer.
   QuicByteCount BufferedByteCount() const { return buffer_.size(); }
 
+  // Returns whether writing to the encoder stream is allowed.  Writing is
+  // disallowed if the amount of data buffered by the underlying stream exceeds
+  // a hardcoded limit, in order to limit memory consumption in case the encoder
+  // stream is blocked.  CanWrite() returning true does not mean that the
+  // encoder stream is not blocked, it just means the blocked data does not
+  // exceed the threshold.
+  bool CanWrite() const;
+
   // Writes all buffered instructions on the encoder stream.
   void Flush();
 
diff --git a/quic/core/qpack/qpack_encoder_test.cc b/quic/core/qpack/qpack_encoder_test.cc
index 21397ed..30f578e 100644
--- a/quic/core/qpack/qpack_encoder_test.cc
+++ b/quic/core/qpack/qpack_encoder_test.cc
@@ -18,12 +18,19 @@
 
 using ::testing::_;
 using ::testing::Eq;
+using ::testing::Return;
 using ::testing::StrictMock;
 
 namespace quic {
 namespace test {
 namespace {
 
+// A number larger than kMaxBytesBufferedByStream in
+// qpack_encoder_stream_sender.cc.  Returning this value from NumBytesBuffered()
+// will instruct QpackEncoder not to generate any instructions for the encoder
+// stream.
+constexpr uint64_t kTooManyBytesBuffered = 1024 * 1024;
+
 class QpackEncoderTest : public QuicTest {
  protected:
   QpackEncoderTest()
@@ -47,6 +54,8 @@
 };
 
 TEST_F(QpackEncoderTest, Empty) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   spdy::Http2HeaderBlock header_list;
   std::string output = Encode(header_list);
 
@@ -54,6 +63,8 @@
 }
 
 TEST_F(QpackEncoderTest, EmptyName) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   spdy::Http2HeaderBlock header_list;
   header_list[""] = "foo";
   std::string output = Encode(header_list);
@@ -62,6 +73,8 @@
 }
 
 TEST_F(QpackEncoderTest, EmptyValue) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   spdy::Http2HeaderBlock header_list;
   header_list["foo"] = "";
   std::string output = Encode(header_list);
@@ -70,6 +83,8 @@
 }
 
 TEST_F(QpackEncoderTest, EmptyNameAndValue) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   spdy::Http2HeaderBlock header_list;
   header_list[""] = "";
   std::string output = Encode(header_list);
@@ -78,6 +93,8 @@
 }
 
 TEST_F(QpackEncoderTest, Simple) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   spdy::Http2HeaderBlock header_list;
   header_list["foo"] = "bar";
   std::string output = Encode(header_list);
@@ -86,6 +103,8 @@
 }
 
 TEST_F(QpackEncoderTest, Multiple) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   spdy::Http2HeaderBlock header_list;
   header_list["foo"] = "bar";
   // 'Z' would be Huffman encoded to 8 bits, so no Huffman encoding is used.
@@ -108,6 +127,8 @@
 }
 
 TEST_F(QpackEncoderTest, StaticTable) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   {
     spdy::Http2HeaderBlock header_list;
     header_list[":method"] = "GET";
@@ -149,6 +170,8 @@
 }
 
 TEST_F(QpackEncoderTest, SplitAlongNullCharacter) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   spdy::Http2HeaderBlock header_list;
   header_list["foo"] = absl::string_view("bar\0bar\0baz", 11);
   std::string output = Encode(header_list);
@@ -217,6 +240,8 @@
 }
 
 TEST_F(QpackEncoderTest, DynamicTable) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   encoder_.SetMaximumBlockedStreams(1);
   encoder_.SetMaximumDynamicTableCapacity(4096);
   encoder_.SetDynamicTableCapacity(4096);
@@ -252,6 +277,8 @@
 
 // There is no room in the dynamic table after inserting the first entry.
 TEST_F(QpackEncoderTest, SmallDynamicTable) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   encoder_.SetMaximumBlockedStreams(1);
   encoder_.SetMaximumDynamicTableCapacity(QpackEntry::Size("foo", "bar"));
   encoder_.SetDynamicTableCapacity(QpackEntry::Size("foo", "bar"));
@@ -288,6 +315,8 @@
 }
 
 TEST_F(QpackEncoderTest, BlockedStream) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   encoder_.SetMaximumBlockedStreams(1);
   encoder_.SetMaximumDynamicTableCapacity(4096);
   encoder_.SetDynamicTableCapacity(4096);
@@ -395,6 +424,8 @@
 }
 
 TEST_F(QpackEncoderTest, Draining) {
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
   spdy::Http2HeaderBlock header_list1;
   header_list1["one"] = "foo";
   header_list1["two"] = "foo";
@@ -467,6 +498,135 @@
   EXPECT_EQ(30u, header_table->dynamic_table_capacity());
 }
 
+TEST_F(QpackEncoderTest, EncoderStreamWritesDisallowedThenAllowed) {
+  if (!GetQuicReloadableFlag(quic_limit_encoder_stream_buffering)) {
+    return;
+  }
+
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(kTooManyBytesBuffered));
+  encoder_.SetMaximumBlockedStreams(1);
+  encoder_.SetMaximumDynamicTableCapacity(4096);
+  encoder_.SetDynamicTableCapacity(4096);
+
+  spdy::Http2HeaderBlock header_list1;
+  header_list1["foo"] = "bar";
+  header_list1.AppendValueOrAddHeader("foo", "baz");
+  header_list1["cookie"] = "baz";  // name matches static entry
+
+  // Encoder is not allowed to write on the encoder stream.
+  // No Set Dynamic Table Capacity or Insert instructions are sent.
+  // Headers are encoded as string literals.
+  EXPECT_EQ(absl::HexStringToBytes("0000"        // prefix
+                                   "2a94e7"      // literal name "foo"
+                                   "03626172"    // with literal value "bar"
+                                   "2a94e7"      // literal name "foo"
+                                   "0362617a"    // with literal value "baz"
+                                   "55"          // name of static entry 5
+                                   "0362617a"),  // with literal value "baz"
+            Encode(header_list1));
+
+  EXPECT_EQ(0u, encoder_stream_sent_byte_count_);
+
+  // If number of bytes buffered by encoder stream goes under the threshold,
+  // then QpackEncoder will resume emitting encoder stream instructions.
+  ::testing::Mock::VerifyAndClearExpectations(&encoder_stream_sender_delegate_);
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
+
+  spdy::Http2HeaderBlock header_list2;
+  header_list2["foo"] = "bar";
+  header_list2.AppendValueOrAddHeader("foo",
+                                      "baz");  // name matches dynamic entry
+  header_list2["cookie"] = "baz";              // name matches static entry
+
+  // Set Dynamic Table Capacity instruction.
+  std::string set_dyanamic_table_capacity = absl::HexStringToBytes("3fe11f");
+  // Insert three entries into the dynamic table.
+  std::string insert_entries = absl::HexStringToBytes(
+      "62"          // insert without name reference
+      "94e7"        // Huffman-encoded name "foo"
+      "03626172"    // value "bar"
+      "80"          // insert with name reference, dynamic index 0
+      "0362617a"    // value "baz"
+      "c5"          // insert with name reference, static index 5
+      "0362617a");  // value "baz"
+  EXPECT_CALL(encoder_stream_sender_delegate_,
+              WriteStreamData(Eq(
+                  absl::StrCat(set_dyanamic_table_capacity, insert_entries))));
+
+  EXPECT_EQ(absl::HexStringToBytes(
+                "0400"      // prefix
+                "828180"),  // dynamic entries with relative index 0, 1, and 2
+            Encode(header_list2));
+
+  EXPECT_EQ(insert_entries.size(), encoder_stream_sent_byte_count_);
+}
+
+TEST_F(QpackEncoderTest, EncoderStreamWritesAllowedThenDisallowed) {
+  if (!GetQuicReloadableFlag(quic_limit_encoder_stream_buffering)) {
+    return;
+  }
+
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(0));
+  encoder_.SetMaximumBlockedStreams(1);
+  encoder_.SetMaximumDynamicTableCapacity(4096);
+  encoder_.SetDynamicTableCapacity(4096);
+
+  spdy::Http2HeaderBlock header_list1;
+  header_list1["foo"] = "bar";
+  header_list1.AppendValueOrAddHeader("foo",
+                                      "baz");  // name matches dynamic entry
+  header_list1["cookie"] = "baz";              // name matches static entry
+
+  // Set Dynamic Table Capacity instruction.
+  std::string set_dyanamic_table_capacity = absl::HexStringToBytes("3fe11f");
+  // Insert three entries into the dynamic table.
+  std::string insert_entries = absl::HexStringToBytes(
+      "62"          // insert without name reference
+      "94e7"        // Huffman-encoded name "foo"
+      "03626172"    // value "bar"
+      "80"          // insert with name reference, dynamic index 0
+      "0362617a"    // value "baz"
+      "c5"          // insert with name reference, static index 5
+      "0362617a");  // value "baz"
+  EXPECT_CALL(encoder_stream_sender_delegate_,
+              WriteStreamData(Eq(
+                  absl::StrCat(set_dyanamic_table_capacity, insert_entries))));
+
+  EXPECT_EQ(absl::HexStringToBytes(
+                "0400"      // prefix
+                "828180"),  // dynamic entries with relative index 0, 1, and 2
+            Encode(header_list1));
+
+  EXPECT_EQ(insert_entries.size(), encoder_stream_sent_byte_count_);
+
+  // If number of bytes buffered by encoder stream goes over the threshold,
+  // then QpackEncoder will stop emitting encoder stream instructions.
+  ::testing::Mock::VerifyAndClearExpectations(&encoder_stream_sender_delegate_);
+  EXPECT_CALL(encoder_stream_sender_delegate_, NumBytesBuffered())
+      .WillRepeatedly(Return(kTooManyBytesBuffered));
+
+  spdy::Http2HeaderBlock header_list2;
+  header_list2["foo"] = "bar";  // matches previously inserted dynamic entry
+  header_list2["bar"] = "baz";
+  header_list2["cookie"] = "baz";  // name matches static entry
+
+  // Encoder is not allowed to write on the encoder stream.
+  // No Set Dynamic Table Capacity or Insert instructions are sent.
+  // Headers are encoded as string literals.
+  EXPECT_EQ(
+      absl::HexStringToBytes("0400"      // prefix
+                             "82"        // dynamic entry with relative index 0
+                             "23626172"  // literal name "bar"
+                             "0362617a"  // with literal value "baz"
+                             "80"),      // dynamic entry with relative index 2
+      Encode(header_list2));
+
+  EXPECT_EQ(0u, encoder_stream_sent_byte_count_);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/qpack/qpack_send_stream.cc b/quic/core/qpack/qpack_send_stream.cc
index 626d063..3e921d8 100644
--- a/quic/core/qpack/qpack_send_stream.cc
+++ b/quic/core/qpack/qpack_send_stream.cc
@@ -34,6 +34,10 @@
   WriteOrBufferData(data, false, nullptr);
 }
 
+uint64_t QpackSendStream::NumBytesBuffered() const {
+  return QuicStream::BufferedDataBytes();
+}
+
 void QpackSendStream::MaybeSendStreamType() {
   if (!stream_type_sent_) {
     char type[sizeof(http3_stream_type_)];
diff --git a/quic/core/qpack/qpack_send_stream.h b/quic/core/qpack/qpack_send_stream.h
index 086ad63..72dc677 100644
--- a/quic/core/qpack/qpack_send_stream.h
+++ b/quic/core/qpack/qpack_send_stream.h
@@ -43,6 +43,9 @@
   // before the first instruction so that the peer can open an qpack stream.
   void WriteStreamData(absl::string_view data) override;
 
+  // Return the number of bytes buffered due to underlying stream being blocked.
+  uint64_t NumBytesBuffered() const override;
+
   // TODO(b/112770235): Remove this method once QuicStreamIdManager supports
   // creating HTTP/3 unidirectional streams dynamically.
   void MaybeSendStreamType();
diff --git a/quic/core/qpack/qpack_stream_sender_delegate.h b/quic/core/qpack/qpack_stream_sender_delegate.h
index 4bd1a3a..04f533d 100644
--- a/quic/core/qpack/qpack_stream_sender_delegate.h
+++ b/quic/core/qpack/qpack_stream_sender_delegate.h
@@ -17,6 +17,9 @@
 
   // Write data on the unidirectional stream.
   virtual void WriteStreamData(absl::string_view data) = 0;
+
+  // Return the number of bytes buffered due to underlying stream being blocked.
+  virtual uint64_t NumBytesBuffered() const = 0;
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 58fcb2f..fe5bcd5 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -25,6 +25,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_cached_network_parameters_to_address_token2, true)
 // If true, QUIC will default enable MTU discovery at server, with a target of 1450 bytes.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_mtu_discovery_at_server, false)
+// If true, QpackEncoder will stop emitting instructions on the encoder stream if amount of buffered data exceeds a hardcoded limit.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_limit_encoder_stream_buffering, true)
 // If true, QuicGsoBatchWriter will support release time if it is available and the process has the permission to do so.
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_support_release_time_for_gso, false)
 // If true, TlsServerHandshaker will be able to 1) request client cert, and 2) verify the client cert in the virtual method TlsServerHandshaker::VerifyCertChain.
diff --git a/quic/test_tools/qpack/qpack_test_utils.h b/quic/test_tools/qpack/qpack_test_utils.h
index 23d54aa..2230fbe 100644
--- a/quic/test_tools/qpack/qpack_test_utils.h
+++ b/quic/test_tools/qpack/qpack_test_utils.h
@@ -33,6 +33,7 @@
   ~MockQpackStreamSenderDelegate() override = default;
 
   MOCK_METHOD(void, WriteStreamData, (absl::string_view data), (override));
+  MOCK_METHOD(uint64_t, NumBytesBuffered, (), (const, override));
 };
 
 class NoopQpackStreamSenderDelegate : public QpackStreamSenderDelegate {
@@ -40,6 +41,8 @@
   ~NoopQpackStreamSenderDelegate() override = default;
 
   void WriteStreamData(absl::string_view /*data*/) override {}
+
+  uint64_t NumBytesBuffered() const override { return 0; }
 };
 
 }  // namespace test