Introduce granular QPACK encoder/decoder stream error codes.

Add new error codes to QpackInstructionDecoder.  This class is used by
QpackEncoderStreamReceived to decode the encoder stream, by
QpackDecoderStreamReceived to decode the decoder stream, and by
QpackProgressiveDecoder to decode header blocks.  Ignore error codes in
QpackProgressiveDecoder because header block decoding errors are stream errors,
not connection close errors, and unlike CONNECTION_CLOSE frames, RESET_STREAM
frames are not able to convey any extra information other than the mandatory
generic error code.  Convert QpackInstructionDecoder::ErrorCode error codes to
newly added QuicErrorCodes in
QpackEncoderStreamReceiver::OnInstructionDecodingError() and
QpackDecoderStreamReceiver::OnInstructionDecodingError().

QPACK encoder stream errors are detected in QpackEncoderStreamReceiver and in
QpackDecoder.  Swap all QuicErrorCodes to the new ones, and gate them by the
flag in a single place, in QpackDecoder::OnErrorDetected().  Do the same thing
for QPACK decoder stream errors in QpackDecoderStreamReceiver and QpackEncoder.

Protected by FLAGS_quic_reloadable_flag_quic_granular_qpack_error_codes.

PiperOrigin-RevId: 338170616
Change-Id: I40fbf1c04869d74984e20f48c317daa995b6e99d
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 07c6f4e..84ea617 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -2747,8 +2747,11 @@
 
   EXPECT_CALL(
       *connection_,
-      CloseConnection(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                      "Encoder stream error: Invalid relative index.", _));
+      CloseConnection(
+          GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+              ? QUIC_QPACK_ENCODER_STREAM_DUPLICATE_INVALID_RELATIVE_INDEX
+              : QUIC_QPACK_ENCODER_STREAM_ERROR,
+          "Encoder stream error: Invalid relative index.", _));
   session_.OnStreamFrame(frame);
 }
 
@@ -2768,7 +2771,9 @@
 
   EXPECT_CALL(
       *connection_,
-      CloseConnection(QUIC_QPACK_DECODER_STREAM_ERROR,
+      CloseConnection(GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                          ? QUIC_QPACK_DECODER_STREAM_INVALID_ZERO_INCREMENT
+                          : QUIC_QPACK_DECODER_STREAM_ERROR,
                       "Decoder stream error: Invalid increment value 0.", _));
   session_.OnStreamFrame(frame);
 }
diff --git a/quic/core/qpack/qpack_decoder.cc b/quic/core/qpack/qpack_decoder.cc
index 67d6a4e..e0d7e20 100644
--- a/quic/core/qpack/qpack_decoder.cc
+++ b/quic/core/qpack/qpack_decoder.cc
@@ -8,6 +8,7 @@
 
 #include "absl/strings/string_view.h"
 #include "net/third_party/quiche/src/quic/core/qpack/qpack_index_conversions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
 
 namespace quic {
@@ -74,14 +75,14 @@
   if (is_static) {
     auto entry = header_table_.LookupEntry(/* is_static = */ true, name_index);
     if (!entry) {
-      OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
+      OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_INVALID_STATIC_ENTRY,
                       "Invalid static table entry.");
       return;
     }
 
     entry = header_table_.InsertEntry(entry->name(), value);
     if (!entry) {
-      OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
+      OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_STATIC,
                       "Error inserting entry with name reference.");
     }
     return;
@@ -90,20 +91,21 @@
   uint64_t absolute_index;
   if (!QpackEncoderStreamRelativeIndexToAbsoluteIndex(
           name_index, header_table_.inserted_entry_count(), &absolute_index)) {
-    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR, "Invalid relative index.");
+    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_INSERTION_INVALID_RELATIVE_INDEX,
+                    "Invalid relative index.");
     return;
   }
 
   const QpackEntry* entry =
       header_table_.LookupEntry(/* is_static = */ false, absolute_index);
   if (!entry) {
-    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
+    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_INSERTION_DYNAMIC_ENTRY_NOT_FOUND,
                     "Dynamic table entry not found.");
     return;
   }
   entry = header_table_.InsertEntry(entry->name(), value);
   if (!entry) {
-    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
+    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_DYNAMIC,
                     "Error inserting entry with name reference.");
   }
 }
@@ -112,7 +114,7 @@
                                                 absl::string_view value) {
   const QpackEntry* entry = header_table_.InsertEntry(name, value);
   if (!entry) {
-    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
+    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_LITERAL,
                     "Error inserting literal entry.");
   }
 }
@@ -121,35 +123,41 @@
   uint64_t absolute_index;
   if (!QpackEncoderStreamRelativeIndexToAbsoluteIndex(
           index, header_table_.inserted_entry_count(), &absolute_index)) {
-    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR, "Invalid relative index.");
+    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_DUPLICATE_INVALID_RELATIVE_INDEX,
+                    "Invalid relative index.");
     return;
   }
 
   const QpackEntry* entry =
       header_table_.LookupEntry(/* is_static = */ false, absolute_index);
   if (!entry) {
-    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
+    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_DUPLICATE_DYNAMIC_ENTRY_NOT_FOUND,
                     "Dynamic table entry not found.");
     return;
   }
   entry = header_table_.InsertEntry(entry->name(), entry->value());
   if (!entry) {
-    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                    "Error inserting duplicate entry.");
+    // InsertEntry() can only fail if entry is larger then dynamic table
+    // capacity, but that is impossible since entry was retrieved from the
+    // dynamic table.
+    OnErrorDetected(QUIC_INTERNAL_ERROR, "Error inserting duplicate entry.");
   }
 }
 
 void QpackDecoder::OnSetDynamicTableCapacity(uint64_t capacity) {
   if (!header_table_.SetDynamicTableCapacity(capacity)) {
-    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
+    OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_SET_DYNAMIC_TABLE_CAPACITY,
                     "Error updating dynamic table capacity.");
   }
 }
 
-void QpackDecoder::OnErrorDetected(QuicErrorCode /* error_code */,
+void QpackDecoder::OnErrorDetected(QuicErrorCode error_code,
                                    absl::string_view error_message) {
   encoder_stream_error_delegate_->OnEncoderStreamError(
-      QUIC_QPACK_ENCODER_STREAM_ERROR, error_message);
+      GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+          ? error_code
+          : QUIC_QPACK_ENCODER_STREAM_ERROR,
+      error_message);
 }
 
 std::unique_ptr<QpackProgressiveDecoder> QpackDecoder::CreateProgressiveDecoder(
diff --git a/quic/core/qpack/qpack_decoder_stream_receiver.cc b/quic/core/qpack/qpack_decoder_stream_receiver.cc
index 528fb1f..e63d853 100644
--- a/quic/core/qpack/qpack_decoder_stream_receiver.cc
+++ b/quic/core/qpack/qpack_decoder_stream_receiver.cc
@@ -44,11 +44,19 @@
 }
 
 void QpackDecoderStreamReceiver::OnInstructionDecodingError(
+    QpackInstructionDecoder::ErrorCode error_code,
     absl::string_view error_message) {
   DCHECK(!error_detected_);
 
   error_detected_ = true;
-  delegate_->OnErrorDetected(QUIC_QPACK_DECODER_STREAM_ERROR, error_message);
+
+  // There is no string literals on the decoder stream,
+  // the only possible error is INTEGER_TOO_LARGE.
+  QuicErrorCode quic_error_code =
+      (error_code == QpackInstructionDecoder::ErrorCode::INTEGER_TOO_LARGE)
+          ? QUIC_QPACK_DECODER_STREAM_INTEGER_TOO_LARGE
+          : QUIC_INTERNAL_ERROR;
+  delegate_->OnErrorDetected(quic_error_code, error_message);
 }
 
 }  // namespace quic
diff --git a/quic/core/qpack/qpack_decoder_stream_receiver.h b/quic/core/qpack/qpack_decoder_stream_receiver.h
index c20306a..b3b3d69 100644
--- a/quic/core/qpack/qpack_decoder_stream_receiver.h
+++ b/quic/core/qpack/qpack_decoder_stream_receiver.h
@@ -53,7 +53,8 @@
 
   // QpackInstructionDecoder::Delegate implementation.
   bool OnInstructionDecoded(const QpackInstruction* instruction) override;
-  void OnInstructionDecodingError(absl::string_view error_message) override;
+  void OnInstructionDecodingError(QpackInstructionDecoder::ErrorCode error_code,
+                                  absl::string_view error_message) override;
 
  private:
   QpackInstructionDecoder instruction_decoder_;
diff --git a/quic/core/qpack/qpack_decoder_stream_receiver_test.cc b/quic/core/qpack/qpack_decoder_stream_receiver_test.cc
index 44e40fa..af8af8c 100644
--- a/quic/core/qpack/qpack_decoder_stream_receiver_test.cc
+++ b/quic/core/qpack/qpack_decoder_stream_receiver_test.cc
@@ -53,8 +53,9 @@
   EXPECT_CALL(delegate_, OnInsertCountIncrement(200));
   stream_.Decode(quiche::QuicheTextUtils::HexDecode("3f8901"));
 
-  EXPECT_CALL(delegate_, OnErrorDetected(QUIC_QPACK_DECODER_STREAM_ERROR,
-                                         Eq("Encoded integer too large.")));
+  EXPECT_CALL(delegate_,
+              OnErrorDetected(QUIC_QPACK_DECODER_STREAM_INTEGER_TOO_LARGE,
+                              Eq("Encoded integer too large.")));
   stream_.Decode(quiche::QuicheTextUtils::HexDecode("3fffffffffffffffffffff"));
 }
 
@@ -71,8 +72,9 @@
   EXPECT_CALL(delegate_, OnHeaderAcknowledgement(503));
   stream_.Decode(quiche::QuicheTextUtils::HexDecode("fff802"));
 
-  EXPECT_CALL(delegate_, OnErrorDetected(QUIC_QPACK_DECODER_STREAM_ERROR,
-                                         Eq("Encoded integer too large.")));
+  EXPECT_CALL(delegate_,
+              OnErrorDetected(QUIC_QPACK_DECODER_STREAM_INTEGER_TOO_LARGE,
+                              Eq("Encoded integer too large.")));
   stream_.Decode(quiche::QuicheTextUtils::HexDecode("ffffffffffffffffffffff"));
 }
 
@@ -89,8 +91,9 @@
   EXPECT_CALL(delegate_, OnStreamCancellation(110));
   stream_.Decode(quiche::QuicheTextUtils::HexDecode("7f2f"));
 
-  EXPECT_CALL(delegate_, OnErrorDetected(QUIC_QPACK_DECODER_STREAM_ERROR,
-                                         Eq("Encoded integer too large.")));
+  EXPECT_CALL(delegate_,
+              OnErrorDetected(QUIC_QPACK_DECODER_STREAM_INTEGER_TOO_LARGE,
+                              Eq("Encoded integer too large.")));
   stream_.Decode(quiche::QuicheTextUtils::HexDecode("7fffffffffffffffffffff"));
 }
 
diff --git a/quic/core/qpack/qpack_decoder_test.cc b/quic/core/qpack/qpack_decoder_test.cc
index c3348e7..988ba8f 100644
--- a/quic/core/qpack/qpack_decoder_test.cc
+++ b/quic/core/qpack/qpack_decoder_test.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 
 #include "absl/strings/string_view.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
 #include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_decoder_test_utils.h"
@@ -441,8 +442,11 @@
 
 TEST_P(QpackDecoderTest, EncoderStreamErrorEntryTooLarge) {
   EXPECT_CALL(encoder_stream_error_delegate_,
-              OnEncoderStreamError(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                   Eq("Error inserting literal entry.")));
+              OnEncoderStreamError(
+                  GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                      ? QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_LITERAL
+                      : QUIC_QPACK_ENCODER_STREAM_ERROR,
+                  Eq("Error inserting literal entry.")));
 
   // Set dynamic table capacity to 34.
   DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3f03"));
@@ -452,17 +456,24 @@
 
 TEST_P(QpackDecoderTest, EncoderStreamErrorInvalidStaticTableEntry) {
   EXPECT_CALL(encoder_stream_error_delegate_,
-              OnEncoderStreamError(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                   Eq("Invalid static table entry.")));
+              OnEncoderStreamError(
+                  GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                      ? QUIC_QPACK_ENCODER_STREAM_INVALID_STATIC_ENTRY
+                      : QUIC_QPACK_ENCODER_STREAM_ERROR,
+                  Eq("Invalid static table entry.")));
 
   // Address invalid static table entry index 99.
   DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("ff2400"));
 }
 
 TEST_P(QpackDecoderTest, EncoderStreamErrorInvalidDynamicTableEntry) {
-  EXPECT_CALL(encoder_stream_error_delegate_,
-              OnEncoderStreamError(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                   Eq("Invalid relative index.")));
+  EXPECT_CALL(
+      encoder_stream_error_delegate_,
+      OnEncoderStreamError(
+          GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+              ? QUIC_QPACK_ENCODER_STREAM_INSERTION_INVALID_RELATIVE_INDEX
+              : QUIC_QPACK_ENCODER_STREAM_ERROR,
+          Eq("Invalid relative index.")));
 
   DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode(
       "3fe107"          // Set dynamic table capacity to 1024.
@@ -473,9 +484,13 @@
 }
 
 TEST_P(QpackDecoderTest, EncoderStreamErrorDuplicateInvalidEntry) {
-  EXPECT_CALL(encoder_stream_error_delegate_,
-              OnEncoderStreamError(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                   Eq("Invalid relative index.")));
+  EXPECT_CALL(
+      encoder_stream_error_delegate_,
+      OnEncoderStreamError(
+          GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+              ? QUIC_QPACK_ENCODER_STREAM_DUPLICATE_INVALID_RELATIVE_INDEX
+              : QUIC_QPACK_ENCODER_STREAM_ERROR,
+          Eq("Invalid relative index.")));
 
   DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode(
       "3fe107"          // Set dynamic table capacity to 1024.
@@ -487,8 +502,11 @@
 
 TEST_P(QpackDecoderTest, EncoderStreamErrorTooLargeInteger) {
   EXPECT_CALL(encoder_stream_error_delegate_,
-              OnEncoderStreamError(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                   Eq("Encoded integer too large.")));
+              OnEncoderStreamError(
+                  GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                      ? QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE
+                      : QUIC_QPACK_ENCODER_STREAM_ERROR,
+                  Eq("Encoded integer too large.")));
 
   DecodeEncoderStreamData(
       quiche::QuicheTextUtils::HexDecode("3fffffffffffffffffffff"));
@@ -592,10 +610,12 @@
 }
 
 TEST_P(QpackDecoderTest, TableCapacityMustNotExceedMaximum) {
-  EXPECT_CALL(
-      encoder_stream_error_delegate_,
-      OnEncoderStreamError(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                           Eq("Error updating dynamic table capacity.")));
+  EXPECT_CALL(encoder_stream_error_delegate_,
+              OnEncoderStreamError(
+                  GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                      ? QUIC_QPACK_ENCODER_STREAM_SET_DYNAMIC_TABLE_CAPACITY
+                      : QUIC_QPACK_ENCODER_STREAM_ERROR,
+                  Eq("Error updating dynamic table capacity.")));
 
   // Try to update dynamic table capacity to 2048, which exceeds the maximum.
   DecodeEncoderStreamData(quiche::QuicheTextUtils::HexDecode("3fe10f"));
diff --git a/quic/core/qpack/qpack_encoder.cc b/quic/core/qpack/qpack_encoder.cc
index 58df9d0..f19e163 100644
--- a/quic/core/qpack/qpack_encoder.cc
+++ b/quic/core/qpack/qpack_encoder.cc
@@ -12,6 +12,7 @@
 #include "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_encoder.h"
 #include "net/third_party/quiche/src/quic/core/qpack/qpack_required_insert_count.h"
 #include "net/third_party/quiche/src/quic/core/qpack/value_splitting_header_list.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_str_cat.h"
 
@@ -403,20 +404,20 @@
 
 void QpackEncoder::OnInsertCountIncrement(uint64_t increment) {
   if (increment == 0) {
-    OnErrorDetected(QUIC_QPACK_DECODER_STREAM_ERROR,
+    OnErrorDetected(QUIC_QPACK_DECODER_STREAM_INVALID_ZERO_INCREMENT,
                     "Invalid increment value 0.");
     return;
   }
 
   if (!blocking_manager_.OnInsertCountIncrement(increment)) {
-    OnErrorDetected(QUIC_QPACK_DECODER_STREAM_ERROR,
+    OnErrorDetected(QUIC_QPACK_DECODER_STREAM_INCREMENT_OVERFLOW,
                     "Insert Count Increment instruction causes overflow.");
   }
 
   if (blocking_manager_.known_received_count() >
       header_table_.inserted_entry_count()) {
     OnErrorDetected(
-        QUIC_QPACK_DECODER_STREAM_ERROR,
+        QUIC_QPACK_DECODER_STREAM_IMPOSSIBLE_INSERT_COUNT,
         quiche::QuicheStrCat("Increment value ", increment,
                              " raises known received count to ",
                              blocking_manager_.known_received_count(),
@@ -428,7 +429,7 @@
 void QpackEncoder::OnHeaderAcknowledgement(QuicStreamId stream_id) {
   if (!blocking_manager_.OnHeaderAcknowledgement(stream_id)) {
     OnErrorDetected(
-        QUIC_QPACK_DECODER_STREAM_ERROR,
+        QUIC_QPACK_DECODER_STREAM_INCORRECT_ACKNOWLEDGEMENT,
         quiche::QuicheStrCat("Header Acknowledgement received for stream ",
                              stream_id, " with no outstanding header blocks."));
   }
@@ -438,10 +439,13 @@
   blocking_manager_.OnStreamCancellation(stream_id);
 }
 
-void QpackEncoder::OnErrorDetected(QuicErrorCode /* error_code */,
+void QpackEncoder::OnErrorDetected(QuicErrorCode error_code,
                                    absl::string_view error_message) {
   decoder_stream_error_delegate_->OnDecoderStreamError(
-      QUIC_QPACK_DECODER_STREAM_ERROR, error_message);
+      GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+          ? error_code
+          : QUIC_QPACK_DECODER_STREAM_ERROR,
+      error_message);
 }
 
 }  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_stream_receiver.cc b/quic/core/qpack/qpack_encoder_stream_receiver.cc
index 1cfa60b..95260ab 100644
--- a/quic/core/qpack/qpack_encoder_stream_receiver.cc
+++ b/quic/core/qpack/qpack_encoder_stream_receiver.cc
@@ -52,11 +52,28 @@
 }
 
 void QpackEncoderStreamReceiver::OnInstructionDecodingError(
+    QpackInstructionDecoder::ErrorCode error_code,
     absl::string_view error_message) {
   DCHECK(!error_detected_);
 
   error_detected_ = true;
-  delegate_->OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR, error_message);
+
+  QuicErrorCode quic_error_code;
+  switch (error_code) {
+    case QpackInstructionDecoder::ErrorCode::INTEGER_TOO_LARGE:
+      quic_error_code = QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE;
+      break;
+    case QpackInstructionDecoder::ErrorCode::STRING_LITERAL_TOO_LONG:
+      quic_error_code = QUIC_QPACK_ENCODER_STREAM_STRING_LITERAL_TOO_LONG;
+      break;
+    case QpackInstructionDecoder::ErrorCode::HUFFMAN_ENCODING_ERROR:
+      quic_error_code = QUIC_QPACK_ENCODER_STREAM_HUFFMAN_ENCODING_ERROR;
+      break;
+    default:
+      quic_error_code = QUIC_INTERNAL_ERROR;
+  }
+
+  delegate_->OnErrorDetected(quic_error_code, error_message);
 }
 
 }  // namespace quic
diff --git a/quic/core/qpack/qpack_encoder_stream_receiver.h b/quic/core/qpack/qpack_encoder_stream_receiver.h
index 8b86547..fa94a53 100644
--- a/quic/core/qpack/qpack_encoder_stream_receiver.h
+++ b/quic/core/qpack/qpack_encoder_stream_receiver.h
@@ -58,7 +58,8 @@
 
   // QpackInstructionDecoder::Delegate implementation.
   bool OnInstructionDecoded(const QpackInstruction* instruction) override;
-  void OnInstructionDecodingError(absl::string_view error_message) override;
+  void OnInstructionDecodingError(QpackInstructionDecoder::ErrorCode error_code,
+                                  absl::string_view error_message) override;
 
  private:
   QpackInstructionDecoder instruction_decoder_;
diff --git a/quic/core/qpack/qpack_encoder_stream_receiver_test.cc b/quic/core/qpack/qpack_encoder_stream_receiver_test.cc
index 81e47e7..235bd79 100644
--- a/quic/core/qpack/qpack_encoder_stream_receiver_test.cc
+++ b/quic/core/qpack/qpack_encoder_stream_receiver_test.cc
@@ -71,15 +71,17 @@
 }
 
 TEST_F(QpackEncoderStreamReceiverTest, InsertWithNameReferenceIndexTooLarge) {
-  EXPECT_CALL(*delegate(), OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                           Eq("Encoded integer too large.")));
+  EXPECT_CALL(*delegate(),
+              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE,
+                              Eq("Encoded integer too large.")));
 
   Decode(quiche::QuicheTextUtils::HexDecode("bfffffffffffffffffffffff"));
 }
 
 TEST_F(QpackEncoderStreamReceiverTest, InsertWithNameReferenceValueTooLong) {
-  EXPECT_CALL(*delegate(), OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                           Eq("Encoded integer too large.")));
+  EXPECT_CALL(*delegate(),
+              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE,
+                              Eq("Encoded integer too large.")));
 
   Decode(quiche::QuicheTextUtils::HexDecode("c57fffffffffffffffffffff"));
 }
@@ -111,8 +113,9 @@
 // Name Length value is too large for varint decoder to decode.
 TEST_F(QpackEncoderStreamReceiverTest,
        InsertWithoutNameReferenceNameTooLongForVarintDecoder) {
-  EXPECT_CALL(*delegate(), OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                           Eq("Encoded integer too large.")));
+  EXPECT_CALL(*delegate(),
+              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE,
+                              Eq("Encoded integer too large.")));
 
   Decode(quiche::QuicheTextUtils::HexDecode("5fffffffffffffffffffff"));
 }
@@ -120,8 +123,9 @@
 // Name Length value can be decoded by varint decoder but exceeds 1 MB limit.
 TEST_F(QpackEncoderStreamReceiverTest,
        InsertWithoutNameReferenceNameExceedsLimit) {
-  EXPECT_CALL(*delegate(), OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                           Eq("String literal too long.")));
+  EXPECT_CALL(*delegate(),
+              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_STRING_LITERAL_TOO_LONG,
+                              Eq("String literal too long.")));
 
   Decode(quiche::QuicheTextUtils::HexDecode("5fffff7f"));
 }
@@ -129,8 +133,9 @@
 // Value Length value is too large for varint decoder to decode.
 TEST_F(QpackEncoderStreamReceiverTest,
        InsertWithoutNameReferenceValueTooLongForVarintDecoder) {
-  EXPECT_CALL(*delegate(), OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                           Eq("Encoded integer too large.")));
+  EXPECT_CALL(*delegate(),
+              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE,
+                              Eq("Encoded integer too large.")));
 
   Decode(quiche::QuicheTextUtils::HexDecode("436261727fffffffffffffffffffff"));
 }
@@ -138,8 +143,9 @@
 // Value Length value can be decoded by varint decoder but exceeds 1 MB limit.
 TEST_F(QpackEncoderStreamReceiverTest,
        InsertWithoutNameReferenceValueExceedsLimit) {
-  EXPECT_CALL(*delegate(), OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                           Eq("String literal too long.")));
+  EXPECT_CALL(*delegate(),
+              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_STRING_LITERAL_TOO_LONG,
+                              Eq("String literal too long.")));
 
   Decode(quiche::QuicheTextUtils::HexDecode("436261727fffff7f"));
 }
@@ -154,8 +160,9 @@
 }
 
 TEST_F(QpackEncoderStreamReceiverTest, DuplicateIndexTooLarge) {
-  EXPECT_CALL(*delegate(), OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                           Eq("Encoded integer too large.")));
+  EXPECT_CALL(*delegate(),
+              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE,
+                              Eq("Encoded integer too large.")));
 
   Decode(quiche::QuicheTextUtils::HexDecode("1fffffffffffffffffffff"));
 }
@@ -170,15 +177,16 @@
 }
 
 TEST_F(QpackEncoderStreamReceiverTest, SetDynamicTableCapacityTooLarge) {
-  EXPECT_CALL(*delegate(), OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
-                                           Eq("Encoded integer too large.")));
+  EXPECT_CALL(*delegate(),
+              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE,
+                              Eq("Encoded integer too large.")));
 
   Decode(quiche::QuicheTextUtils::HexDecode("3fffffffffffffffffffff"));
 }
 
 TEST_F(QpackEncoderStreamReceiverTest, InvalidHuffmanEncoding) {
   EXPECT_CALL(*delegate(),
-              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_ERROR,
+              OnErrorDetected(QUIC_QPACK_ENCODER_STREAM_HUFFMAN_ENCODING_ERROR,
                               Eq("Error in Huffman-encoded string.")));
 
   Decode(quiche::QuicheTextUtils::HexDecode("c281ff"));
diff --git a/quic/core/qpack/qpack_encoder_test.cc b/quic/core/qpack/qpack_encoder_test.cc
index a248102..8c0756f 100644
--- a/quic/core/qpack/qpack_encoder_test.cc
+++ b/quic/core/qpack/qpack_encoder_test.cc
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "absl/strings/string_view.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
 #include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_encoder_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/qpack/qpack_encoder_test_utils.h"
@@ -141,8 +142,11 @@
 
 TEST_F(QpackEncoderTest, DecoderStreamError) {
   EXPECT_CALL(decoder_stream_error_delegate_,
-              OnDecoderStreamError(QUIC_QPACK_DECODER_STREAM_ERROR,
-                                   Eq("Encoded integer too large.")));
+              OnDecoderStreamError(
+                  GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                      ? QUIC_QPACK_DECODER_STREAM_INTEGER_TOO_LARGE
+                      : QUIC_QPACK_DECODER_STREAM_ERROR,
+                  Eq("Encoded integer too large.")));
 
   QpackEncoder encoder(&decoder_stream_error_delegate_);
   encoder.set_qpack_stream_sender_delegate(&encoder_stream_sender_delegate_);
@@ -166,8 +170,11 @@
 TEST_F(QpackEncoderTest, ZeroInsertCountIncrement) {
   // Encoder receives insert count increment with forbidden value 0.
   EXPECT_CALL(decoder_stream_error_delegate_,
-              OnDecoderStreamError(QUIC_QPACK_DECODER_STREAM_ERROR,
-                                   Eq("Invalid increment value 0.")));
+              OnDecoderStreamError(
+                  GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                      ? QUIC_QPACK_DECODER_STREAM_INVALID_ZERO_INCREMENT
+                      : QUIC_QPACK_DECODER_STREAM_ERROR,
+                  Eq("Invalid increment value 0.")));
   encoder_.OnInsertCountIncrement(0);
 }
 
@@ -175,11 +182,13 @@
   // Encoder receives insert count increment with value that increases Known
   // Received Count to a value (one) which is larger than the number of dynamic
   // table insertions sent (zero).
-  EXPECT_CALL(
-      decoder_stream_error_delegate_,
-      OnDecoderStreamError(QUIC_QPACK_DECODER_STREAM_ERROR,
-                           Eq("Increment value 1 raises known received count "
-                              "to 1 exceeding inserted entry count 0")));
+  EXPECT_CALL(decoder_stream_error_delegate_,
+              OnDecoderStreamError(
+                  GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                      ? QUIC_QPACK_DECODER_STREAM_IMPOSSIBLE_INSERT_COUNT
+                      : QUIC_QPACK_DECODER_STREAM_ERROR,
+                  Eq("Increment value 1 raises known received count "
+                     "to 1 exceeding inserted entry count 0")));
   encoder_.OnInsertCountIncrement(1);
 }
 
@@ -200,7 +209,9 @@
   // received count.  This must result in an error instead of a crash.
   EXPECT_CALL(decoder_stream_error_delegate_,
               OnDecoderStreamError(
-                  QUIC_QPACK_DECODER_STREAM_ERROR,
+                  GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                      ? QUIC_QPACK_DECODER_STREAM_INCREMENT_OVERFLOW
+                      : QUIC_QPACK_DECODER_STREAM_ERROR,
                   Eq("Insert Count Increment instruction causes overflow.")));
   encoder_.OnInsertCountIncrement(std::numeric_limits<uint64_t>::max());
 }
@@ -208,11 +219,13 @@
 TEST_F(QpackEncoderTest, InvalidHeaderAcknowledgement) {
   // Encoder receives header acknowledgement for a stream on which no header
   // block with dynamic table entries was ever sent.
-  EXPECT_CALL(
-      decoder_stream_error_delegate_,
-      OnDecoderStreamError(QUIC_QPACK_DECODER_STREAM_ERROR,
-                           Eq("Header Acknowledgement received for stream 0 "
-                              "with no outstanding header blocks.")));
+  EXPECT_CALL(decoder_stream_error_delegate_,
+              OnDecoderStreamError(
+                  GetQuicReloadableFlag(quic_granular_qpack_error_codes)
+                      ? QUIC_QPACK_DECODER_STREAM_INCORRECT_ACKNOWLEDGEMENT
+                      : QUIC_QPACK_DECODER_STREAM_ERROR,
+                  Eq("Header Acknowledgement received for stream 0 "
+                     "with no outstanding header blocks.")));
   encoder_.OnHeaderAcknowledgement(/* stream_id = */ 0);
 }
 
diff --git a/quic/core/qpack/qpack_instruction_decoder.cc b/quic/core/qpack/qpack_instruction_decoder.cc
index b4c6dd3..539e310 100644
--- a/quic/core/qpack/qpack_instruction_decoder.cc
+++ b/quic/core/qpack/qpack_instruction_decoder.cc
@@ -183,7 +183,7 @@
       state_ = State::kVarintResume;
       return true;
     case http2::DecodeStatus::kDecodeError:
-      OnError("Encoded integer too large.");
+      OnError(ErrorCode::INTEGER_TOO_LARGE, "Encoded integer too large.");
       return false;
     default:
       QUIC_BUG << "Unknown decode status " << status;
@@ -212,7 +212,7 @@
       DCHECK(buffer.Empty());
       return true;
     case http2::DecodeStatus::kDecodeError:
-      OnError("Encoded integer too large.");
+      OnError(ErrorCode::INTEGER_TOO_LARGE, "Encoded integer too large.");
       return false;
     default:
       QUIC_BUG << "Unknown decode status " << status;
@@ -244,7 +244,7 @@
 
   string_length_ = varint_decoder_.value();
   if (string_length_ > kStringLiteralLengthLimit) {
-    OnError("String literal too long.");
+    OnError(ErrorCode::STRING_LITERAL_TOO_LONG, "String literal too long.");
     return false;
   }
 
@@ -298,7 +298,8 @@
     std::string decoded_value;
     huffman_decoder_.Decode(*string, &decoded_value);
     if (!huffman_decoder_.InputProperlyTerminated()) {
-      OnError("Error in Huffman-encoded string.");
+      OnError(ErrorCode::HUFFMAN_ENCODING_ERROR,
+              "Error in Huffman-encoded string.");
       return false;
     }
     *string = std::move(decoded_value);
@@ -322,11 +323,12 @@
   return nullptr;
 }
 
-void QpackInstructionDecoder::OnError(absl::string_view error_message) {
+void QpackInstructionDecoder::OnError(ErrorCode error_code,
+                                      absl::string_view error_message) {
   DCHECK(!error_detected_);
 
   error_detected_ = true;
-  delegate_->OnInstructionDecodingError(error_message);
+  delegate_->OnInstructionDecodingError(error_code, error_message);
 }
 
 }  // namespace quic
diff --git a/quic/core/qpack/qpack_instruction_decoder.h b/quic/core/qpack/qpack_instruction_decoder.h
index 759c8f0..b8edfb7 100644
--- a/quic/core/qpack/qpack_instruction_decoder.h
+++ b/quic/core/qpack/qpack_instruction_decoder.h
@@ -22,6 +22,12 @@
 // fields that follow each instruction.
 class QUIC_EXPORT_PRIVATE QpackInstructionDecoder {
  public:
+  enum class ErrorCode {
+    INTEGER_TOO_LARGE,
+    STRING_LITERAL_TOO_LONG,
+    HUFFMAN_ENCODING_ERROR,
+  };
+
   // Delegate is notified each time an instruction is decoded or when an error
   // occurs.
   class QUIC_EXPORT_PRIVATE Delegate {
@@ -43,6 +49,7 @@
     // Implementations are allowed to destroy the QpackInstructionDecoder
     // instance synchronously.
     virtual void OnInstructionDecodingError(
+        ErrorCode error_code,
         absl::string_view error_message) = 0;
   };
 
@@ -110,7 +117,7 @@
   const QpackInstruction* LookupOpcode(uint8_t byte) const;
 
   // Stops decoding and calls Delegate::OnInstructionDecodingError().
-  void OnError(absl::string_view error_message);
+  void OnError(ErrorCode error_code, absl::string_view error_message);
 
   // Describes the language used for decoding.
   const QpackLanguage* const language_;
diff --git a/quic/core/qpack/qpack_instruction_decoder_test.cc b/quic/core/qpack/qpack_instruction_decoder_test.cc
index 25c7f7a..ac7b369 100644
--- a/quic/core/qpack/qpack_instruction_decoder_test.cc
+++ b/quic/core/qpack/qpack_instruction_decoder_test.cc
@@ -67,7 +67,8 @@
               (override));
   MOCK_METHOD(void,
               OnInstructionDecodingError,
-              (absl::string_view error_message),
+              (QpackInstructionDecoder::ErrorCode error_code,
+               absl::string_view error_message),
               (override));
 };
 
@@ -82,7 +83,7 @@
   void SetUp() override {
     // Destroy QpackInstructionDecoder on error to test that it does not crash.
     // See https://crbug.com/1025209.
-    ON_CALL(delegate_, OnInstructionDecodingError(_))
+    ON_CALL(delegate_, OnInstructionDecodingError(_, _))
         .WillByDefault(InvokeWithoutArgs([this]() { decoder_.reset(); }));
   }
 
@@ -164,21 +165,27 @@
 }
 
 TEST_P(QpackInstructionDecoderTest, InvalidHuffmanEncoding) {
-  EXPECT_CALL(delegate_, OnInstructionDecodingError(
-                             Eq("Error in Huffman-encoded string.")));
+  EXPECT_CALL(delegate_,
+              OnInstructionDecodingError(
+                  QpackInstructionDecoder::ErrorCode::HUFFMAN_ENCODING_ERROR,
+                  Eq("Error in Huffman-encoded string.")));
   DecodeInstruction(quiche::QuicheTextUtils::HexDecode("c1ff"));
 }
 
 TEST_P(QpackInstructionDecoderTest, InvalidVarintEncoding) {
   EXPECT_CALL(delegate_,
-              OnInstructionDecodingError(Eq("Encoded integer too large.")));
+              OnInstructionDecodingError(
+                  QpackInstructionDecoder::ErrorCode::INTEGER_TOO_LARGE,
+                  Eq("Encoded integer too large.")));
   DecodeInstruction(
       quiche::QuicheTextUtils::HexDecode("ffffffffffffffffffffff"));
 }
 
 TEST_P(QpackInstructionDecoderTest, StringLiteralTooLong) {
   EXPECT_CALL(delegate_,
-              OnInstructionDecodingError(Eq("String literal too long.")));
+              OnInstructionDecodingError(
+                  QpackInstructionDecoder::ErrorCode::STRING_LITERAL_TOO_LONG,
+                  Eq("String literal too long.")));
   DecodeInstruction(quiche::QuicheTextUtils::HexDecode("bfffff7f"));
 }
 
diff --git a/quic/core/qpack/qpack_progressive_decoder.cc b/quic/core/qpack/qpack_progressive_decoder.cc
index 2b3c21e..83c55e0 100644
--- a/quic/core/qpack/qpack_progressive_decoder.cc
+++ b/quic/core/qpack/qpack_progressive_decoder.cc
@@ -123,7 +123,11 @@
 }
 
 void QpackProgressiveDecoder::OnInstructionDecodingError(
+    QpackInstructionDecoder::ErrorCode /* error_code */,
     absl::string_view error_message) {
+  // Ignore |error_code|, because header block decoding errors trigger a
+  // RESET_STREAM frame which cannot carry an error code more granular than
+  // QPACK_DECOMPRESSION_FAILED.
   OnError(error_message);
 }
 
diff --git a/quic/core/qpack/qpack_progressive_decoder.h b/quic/core/qpack/qpack_progressive_decoder.h
index ecac020..dfc25f9 100644
--- a/quic/core/qpack/qpack_progressive_decoder.h
+++ b/quic/core/qpack/qpack_progressive_decoder.h
@@ -100,7 +100,8 @@
 
   // QpackInstructionDecoder::Delegate implementation.
   bool OnInstructionDecoded(const QpackInstruction* instruction) override;
-  void OnInstructionDecodingError(absl::string_view error_message) override;
+  void OnInstructionDecodingError(QpackInstructionDecoder::ErrorCode error_code,
+                                  absl::string_view error_message) override;
 
   // QpackHeaderTable::Observer implementation.
   void OnInsertCountReachedThreshold() override;
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index a3529cf..a57ded8 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -187,6 +187,27 @@
     RETURN_STRING_LITERAL(QUIC_QPACK_DECOMPRESSION_FAILED);
     RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_ERROR);
     RETURN_STRING_LITERAL(QUIC_QPACK_DECODER_STREAM_ERROR);
+    RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE);
+    RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_STRING_LITERAL_TOO_LONG);
+    RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_HUFFMAN_ENCODING_ERROR);
+    RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_INVALID_STATIC_ENTRY);
+    RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_STATIC);
+    RETURN_STRING_LITERAL(
+        QUIC_QPACK_ENCODER_STREAM_INSERTION_INVALID_RELATIVE_INDEX);
+    RETURN_STRING_LITERAL(
+        QUIC_QPACK_ENCODER_STREAM_INSERTION_DYNAMIC_ENTRY_NOT_FOUND);
+    RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_DYNAMIC);
+    RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_LITERAL);
+    RETURN_STRING_LITERAL(
+        QUIC_QPACK_ENCODER_STREAM_DUPLICATE_INVALID_RELATIVE_INDEX);
+    RETURN_STRING_LITERAL(
+        QUIC_QPACK_ENCODER_STREAM_DUPLICATE_DYNAMIC_ENTRY_NOT_FOUND);
+    RETURN_STRING_LITERAL(QUIC_QPACK_ENCODER_STREAM_SET_DYNAMIC_TABLE_CAPACITY);
+    RETURN_STRING_LITERAL(QUIC_QPACK_DECODER_STREAM_INTEGER_TOO_LARGE);
+    RETURN_STRING_LITERAL(QUIC_QPACK_DECODER_STREAM_INVALID_ZERO_INCREMENT);
+    RETURN_STRING_LITERAL(QUIC_QPACK_DECODER_STREAM_INCREMENT_OVERFLOW);
+    RETURN_STRING_LITERAL(QUIC_QPACK_DECODER_STREAM_IMPOSSIBLE_INSERT_COUNT);
+    RETURN_STRING_LITERAL(QUIC_QPACK_DECODER_STREAM_INCORRECT_ACKNOWLEDGEMENT);
     RETURN_STRING_LITERAL(QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET);
     RETURN_STRING_LITERAL(QUIC_STREAM_MULTIPLE_OFFSET);
     RETURN_STRING_LITERAL(QUIC_HTTP_FRAME_TOO_LARGE);
@@ -543,6 +564,57 @@
     case QUIC_QPACK_DECODER_STREAM_ERROR:
       return {false, static_cast<uint64_t>(
                          QuicHttpQpackErrorCode::DECODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_STRING_LITERAL_TOO_LONG:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_HUFFMAN_ENCODING_ERROR:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_INVALID_STATIC_ENTRY:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_STATIC:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_INSERTION_INVALID_RELATIVE_INDEX:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_INSERTION_DYNAMIC_ENTRY_NOT_FOUND:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_DYNAMIC:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_LITERAL:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_DUPLICATE_INVALID_RELATIVE_INDEX:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_DUPLICATE_DYNAMIC_ENTRY_NOT_FOUND:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_ENCODER_STREAM_SET_DYNAMIC_TABLE_CAPACITY:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::ENCODER_STREAM_ERROR)};
+    case QUIC_QPACK_DECODER_STREAM_INTEGER_TOO_LARGE:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::DECODER_STREAM_ERROR)};
+    case QUIC_QPACK_DECODER_STREAM_INVALID_ZERO_INCREMENT:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::DECODER_STREAM_ERROR)};
+    case QUIC_QPACK_DECODER_STREAM_INCREMENT_OVERFLOW:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::DECODER_STREAM_ERROR)};
+    case QUIC_QPACK_DECODER_STREAM_IMPOSSIBLE_INSERT_COUNT:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::DECODER_STREAM_ERROR)};
+    case QUIC_QPACK_DECODER_STREAM_INCORRECT_ACKNOWLEDGEMENT:
+      return {false, static_cast<uint64_t>(
+                         QuicHttpQpackErrorCode::DECODER_STREAM_ERROR)};
     case QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET:
       return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)};
     case QUIC_STREAM_MULTIPLE_OFFSET:
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 4df545e..74ab936 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -391,9 +391,58 @@
 
   // Internal error codes for QPACK errors.
   QUIC_QPACK_DECOMPRESSION_FAILED = 126,
+
+  // Obsolete generic QPACK encoder and decoder stream error codes.
+  // (Obsoleted by gfe2_reloadable_flag_quic_granular_qpack_error_codes.)
   QUIC_QPACK_ENCODER_STREAM_ERROR = 127,
   QUIC_QPACK_DECODER_STREAM_ERROR = 128,
 
+  // QPACK encoder stream errors.
+
+  // Variable integer exceeding 2^64-1 received.
+  QUIC_QPACK_ENCODER_STREAM_INTEGER_TOO_LARGE = 174,
+  // String literal exceeding kStringLiteralLengthLimit in length received.
+  QUIC_QPACK_ENCODER_STREAM_STRING_LITERAL_TOO_LONG = 175,
+  // String literal with invalid Huffman encoding received.
+  QUIC_QPACK_ENCODER_STREAM_HUFFMAN_ENCODING_ERROR = 176,
+  // Invalid static table index in Insert With Name Reference instruction.
+  QUIC_QPACK_ENCODER_STREAM_INVALID_STATIC_ENTRY = 177,
+  // Error inserting entry with static name reference in Insert With Name
+  // Reference instruction due to entry size exceeding dynamic table capacity.
+  QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_STATIC = 178,
+  // Invalid relative index in Insert With Name Reference instruction.
+  QUIC_QPACK_ENCODER_STREAM_INSERTION_INVALID_RELATIVE_INDEX = 179,
+  // Dynamic entry not found in Insert With Name Reference instruction.
+  QUIC_QPACK_ENCODER_STREAM_INSERTION_DYNAMIC_ENTRY_NOT_FOUND = 180,
+  // Error inserting entry with dynamic name reference in Insert With Name
+  // Reference instruction due to entry size exceeding dynamic table capacity.
+  QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_DYNAMIC = 181,
+  // Error inserting entry in Insert With Literal Name instruction due to entry
+  // size exceeding dynamic table capacity.
+  QUIC_QPACK_ENCODER_STREAM_ERROR_INSERTING_LITERAL = 182,
+  // Invalid relative index in Duplicate instruction.
+  QUIC_QPACK_ENCODER_STREAM_DUPLICATE_INVALID_RELATIVE_INDEX = 183,
+  // Dynamic entry not found in Duplicate instruction.
+  QUIC_QPACK_ENCODER_STREAM_DUPLICATE_DYNAMIC_ENTRY_NOT_FOUND = 184,
+  // Error in Set Dynamic Table Capacity instruction due to new capacity
+  // exceeding maximum dynamic table capacity.
+  QUIC_QPACK_ENCODER_STREAM_SET_DYNAMIC_TABLE_CAPACITY = 185,
+
+  // QPACK decoder stream errors.
+
+  // Variable integer exceeding 2^64-1 received.
+  QUIC_QPACK_DECODER_STREAM_INTEGER_TOO_LARGE = 186,
+  // Insert Count Increment instruction received with invalid 0 increment.
+  QUIC_QPACK_DECODER_STREAM_INVALID_ZERO_INCREMENT = 187,
+  // Insert Count Increment instruction causes uint64_t overflow.
+  QUIC_QPACK_DECODER_STREAM_INCREMENT_OVERFLOW = 188,
+  // Insert Count Increment instruction increases Known Received Count beyond
+  // inserted entry cound.
+  QUIC_QPACK_DECODER_STREAM_IMPOSSIBLE_INSERT_COUNT = 189,
+  // Header Acknowledgement received for stream that has no outstanding header
+  // blocks.
+  QUIC_QPACK_DECODER_STREAM_INCORRECT_ACKNOWLEDGEMENT = 190,
+
   // Received stream data beyond close offset.
   QUIC_STREAM_DATA_BEYOND_CLOSE_OFFSET = 129,
 
@@ -508,7 +557,7 @@
   QUIC_AEAD_LIMIT_REACHED = 173,
 
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 174,
+  QUIC_LAST_ERROR = 191,
 };
 // 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