Add and use unique SpdyFramerErrors and QuicErrorCodes for each HpackDecodingError entry.

gfe-relnote: protected by gfe2_reloadable_flag_spdy_enable_granular_decompress_errors.
PiperOrigin-RevId: 296436981
Change-Id: I5dc3107b303893e82ed84d79a65a74d9f83fe276
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 0c818a3..bf76e2e 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -3527,8 +3527,12 @@
 
   client_->SendMessage(headers, "");
   client_->WaitForResponse();
-  EXPECT_THAT(client_->connection_error(),
-              IsError(QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE));
+
+  QuicErrorCode expected_error =
+      GetQuicReloadableFlag(spdy_enable_granular_decompress_errors)
+          ? QUIC_HPACK_INDEX_VARINT_ERROR
+          : QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE;
+  EXPECT_THAT(client_->connection_error(), IsError(expected_error));
 }
 
 class WindowUpdateObserver : public QuicConnectionDebugVisitor {
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index 7d376fc..e46ea5d 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -139,13 +139,68 @@
   }
 
   void OnError(Http2DecoderAdapter::SpdyFramerError error) override {
-    QuicErrorCode code = QUIC_INVALID_HEADERS_STREAM_DATA;
+    QuicErrorCode code;
     switch (error) {
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_INDEX_VARINT_ERROR:
+        code = QUIC_HPACK_NAME_LENGTH_VARINT_ERROR;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_NAME_LENGTH_VARINT_ERROR:
+        code = QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_VALUE_LENGTH_VARINT_ERROR:
+        code = QUIC_HPACK_NAME_TOO_LONG;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_NAME_TOO_LONG:
+        code = QUIC_HPACK_VALUE_TOO_LONG;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_VALUE_TOO_LONG:
+        code = QUIC_HPACK_INDEX_VARINT_ERROR;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_NAME_HUFFMAN_ERROR:
+        code = QUIC_HPACK_NAME_HUFFMAN_ERROR;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_VALUE_HUFFMAN_ERROR:
+        code = QUIC_HPACK_VALUE_HUFFMAN_ERROR;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE:
+        code = QUIC_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_INVALID_INDEX:
+        code = QUIC_HPACK_INVALID_INDEX;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_INVALID_NAME_INDEX:
+        code = QUIC_HPACK_INVALID_NAME_INDEX;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED:
+        code = QUIC_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK:
+        code = QUIC_HPACK_INITIAL_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING:
+        code = QUIC_HPACK_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_TRUNCATED_BLOCK:
+        code = QUIC_HPACK_TRUNCATED_BLOCK;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_FRAGMENT_TOO_LONG:
+        code = QUIC_HPACK_FRAGMENT_TOO_LONG;
+        break;
+      case Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT:
+        code = QUIC_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT;
+        break;
       case Http2DecoderAdapter::SpdyFramerError::SPDY_DECOMPRESS_FAILURE:
         code = QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE;
         break;
       default:
-        break;
+        code = QUIC_INVALID_HEADERS_STREAM_DATA;
     }
     CloseConnection(quiche::QuicheStrCat(
                         "SPDY framing error: ",
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 82ac0d1..fc2f653 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -2745,6 +2745,40 @@
   EXPECT_EQ(59u, hpack_encoder->CurrentHeaderTableSizeSetting());
 }
 
+TEST_P(QuicSpdySessionTestServer, FineGrainedHpackErrorCodes) {
+  if (VersionUsesHttp3(transport_version())) {
+    // HPACK is not used in HTTP/3.
+    return;
+  }
+
+  QuicFlagSaver flag_saver;
+  SetQuicReloadableFlag(spdy_enable_granular_decompress_errors, true);
+
+  QuicStreamId request_stream_id = 5;
+  session_.CreateIncomingStream(request_stream_id);
+
+  // Index 126 does not exist (static table has 61 entries and dynamic table is
+  // empty).
+  std::string headers_frame = quiche::QuicheTextUtils::HexDecode(
+      "000006"    // length
+      "01"        // type
+      "24"        // flags: PRIORITY | END_HEADERS
+      "00000005"  // stream_id
+      "00000000"  // stream dependency
+      "10"        // weight
+      "fe");      // payload: reference to index 126.
+  QuicStreamId headers_stream_id =
+      QuicUtils::GetHeadersStreamId(transport_version());
+  QuicStreamFrame data(headers_stream_id, false, 0, headers_frame);
+
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_HPACK_INVALID_INDEX,
+                      "SPDY framing error: HPACK_INVALID_INDEX",
+                      ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
+  session_.OnStreamFrame(data);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index 7aa0dff..ce9866c 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -169,6 +169,24 @@
     RETURN_STRING_LITERAL(QUIC_HTTP_FRAME_ERROR);
     RETURN_STRING_LITERAL(QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM);
     RETURN_STRING_LITERAL(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM);
+    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);
+    RETURN_STRING_LITERAL(QUIC_HPACK_NAME_TOO_LONG);
+    RETURN_STRING_LITERAL(QUIC_HPACK_VALUE_TOO_LONG);
+    RETURN_STRING_LITERAL(QUIC_HPACK_NAME_HUFFMAN_ERROR);
+    RETURN_STRING_LITERAL(QUIC_HPACK_VALUE_HUFFMAN_ERROR);
+    RETURN_STRING_LITERAL(QUIC_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE);
+    RETURN_STRING_LITERAL(QUIC_HPACK_INVALID_INDEX);
+    RETURN_STRING_LITERAL(QUIC_HPACK_INVALID_NAME_INDEX);
+    RETURN_STRING_LITERAL(QUIC_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED);
+    RETURN_STRING_LITERAL(
+        QUIC_HPACK_INITIAL_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK);
+    RETURN_STRING_LITERAL(
+        QUIC_HPACK_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING);
+    RETURN_STRING_LITERAL(QUIC_HPACK_TRUNCATED_BLOCK);
+    RETURN_STRING_LITERAL(QUIC_HPACK_FRAGMENT_TOO_LONG);
+    RETURN_STRING_LITERAL(QUIC_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT);
 
     RETURN_STRING_LITERAL(QUIC_LAST_ERROR);
     // Intentionally have no default case, so we'll break the build
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 9a08844..7f250c3 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -359,8 +359,42 @@
   QUIC_HTTP_FRAME_UNEXPECTED_ON_SPDY_STREAM = 133,
   QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM = 134,
 
+  // HPACK header block decoding errors.
+  // Index varint beyond implementation limit.
+  QUIC_HPACK_INDEX_VARINT_ERROR = 135,
+  // Name length varint beyond implementation limit.
+  QUIC_HPACK_NAME_LENGTH_VARINT_ERROR = 136,
+  // Value length varint beyond implementation limit.
+  QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR = 137,
+  // Name length exceeds buffer limit.
+  QUIC_HPACK_NAME_TOO_LONG = 138,
+  // Value length exceeds buffer limit.
+  QUIC_HPACK_VALUE_TOO_LONG = 139,
+  // Name Huffman encoding error.
+  QUIC_HPACK_NAME_HUFFMAN_ERROR = 140,
+  // Value Huffman encoding error.
+  QUIC_HPACK_VALUE_HUFFMAN_ERROR = 141,
+  // Next instruction should have been a dynamic table size update.
+  QUIC_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE = 142,
+  // Invalid index in indexed header field representation.
+  QUIC_HPACK_INVALID_INDEX = 143,
+  // Invalid index in literal header field with indexed name representation.
+  QUIC_HPACK_INVALID_NAME_INDEX = 144,
+  // Dynamic table size update not allowed.
+  QUIC_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED = 145,
+  // Initial dynamic table size update is above low water mark.
+  QUIC_HPACK_INITIAL_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK = 146,
+  // Dynamic table size update is above acknowledged setting.
+  QUIC_HPACK_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING = 147,
+  // HPACK block ends in the middle of an instruction.
+  QUIC_HPACK_TRUNCATED_BLOCK = 148,
+  // Incoming data fragment exceeds buffer limit.
+  QUIC_HPACK_FRAGMENT_TOO_LONG = 149,
+  // Total compressed HPACK data size exceeds limit.
+  QUIC_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT = 150,
+
   // No error. Used as bound while iterating.
-  QUIC_LAST_ERROR = 135,
+  QUIC_LAST_ERROR = 151,
 };
 // 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_types.cc b/quic/core/quic_types.cc
index 0fa7cfb..d39fffe 100644
--- a/quic/core/quic_types.cc
+++ b/quic/core/quic_types.cc
@@ -465,6 +465,51 @@
       return {false,
               {static_cast<uint64_t>(
                   QuicHttp3ErrorCode::IETF_QUIC_HTTP3_FRAME_UNEXPECTED)}};
+    case QUIC_HPACK_INDEX_VARINT_ERROR:
+      return {false, {static_cast<uint64_t>(QUIC_HPACK_INDEX_VARINT_ERROR)}};
+    case QUIC_HPACK_NAME_LENGTH_VARINT_ERROR:
+      return {false,
+              {static_cast<uint64_t>(QUIC_HPACK_NAME_LENGTH_VARINT_ERROR)}};
+    case QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR:
+      return {false,
+              {static_cast<uint64_t>(QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR)}};
+    case QUIC_HPACK_NAME_TOO_LONG:
+      return {false, {static_cast<uint64_t>(QUIC_HPACK_NAME_TOO_LONG)}};
+    case QUIC_HPACK_VALUE_TOO_LONG:
+      return {false, {static_cast<uint64_t>(QUIC_HPACK_VALUE_TOO_LONG)}};
+    case QUIC_HPACK_NAME_HUFFMAN_ERROR:
+      return {false, {static_cast<uint64_t>(QUIC_HPACK_NAME_HUFFMAN_ERROR)}};
+    case QUIC_HPACK_VALUE_HUFFMAN_ERROR:
+      return {false, {static_cast<uint64_t>(QUIC_HPACK_VALUE_HUFFMAN_ERROR)}};
+    case QUIC_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE:
+      return {false,
+              {static_cast<uint64_t>(
+                  QUIC_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE)}};
+    case QUIC_HPACK_INVALID_INDEX:
+      return {false, {static_cast<uint64_t>(QUIC_HPACK_INVALID_INDEX)}};
+    case QUIC_HPACK_INVALID_NAME_INDEX:
+      return {false, {static_cast<uint64_t>(QUIC_HPACK_INVALID_NAME_INDEX)}};
+    case QUIC_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED:
+      return {false,
+              {static_cast<uint64_t>(
+                  QUIC_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED)}};
+    case QUIC_HPACK_INITIAL_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK:
+      return {
+          false,
+          {static_cast<uint64_t>(
+              QUIC_HPACK_INITIAL_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK)}};
+    case QUIC_HPACK_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING:
+      return {false,
+              {static_cast<uint64_t>(
+                  QUIC_HPACK_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING)}};
+    case QUIC_HPACK_TRUNCATED_BLOCK:
+      return {false, {static_cast<uint64_t>(QUIC_HPACK_TRUNCATED_BLOCK)}};
+    case QUIC_HPACK_FRAGMENT_TOO_LONG:
+      return {false, {static_cast<uint64_t>(QUIC_HPACK_FRAGMENT_TOO_LONG)}};
+    case QUIC_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT:
+      return {false,
+              {static_cast<uint64_t>(
+                  QUIC_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT)}};
     case QUIC_LAST_ERROR:
       return {false, {static_cast<uint64_t>(QUIC_LAST_ERROR)}};
   }
diff --git a/spdy/core/http2_frame_decoder_adapter.cc b/spdy/core/http2_frame_decoder_adapter.cc
index bc2263e..df60047 100644
--- a/spdy/core/http2_frame_decoder_adapter.cc
+++ b/spdy/core/http2_frame_decoder_adapter.cc
@@ -73,9 +73,8 @@
 // Overwrites the fields of the header with invalid values, for the purpose
 // of identifying reading of unset fields. Only takes effect for debug builds.
 // In Address Sanatizer builds, it also marks the fields as un-readable.
-void CorruptFrameHeader(Http2FrameHeader*
 #ifndef NDEBUG
-                            header) {
+void CorruptFrameHeader(Http2FrameHeader* header) {
   // Beyond a valid payload length, which is 2^24 - 1.
   header->payload_length = 0x1010dead;
   // An unsupported frame type.
@@ -86,9 +85,68 @@
   // A stream id with the reserved high-bit (R in the RFC) set.
   // 2129510127 when the high-bit is cleared.
   header->stream_id = 0xfeedbeef;
+}
 #else
-                        /*header*/) {
+void CorruptFrameHeader(Http2FrameHeader* /*header*/) {}
 #endif
+
+Http2DecoderAdapter::SpdyFramerError HpackDecodingErrorToSpdyFramerError(
+    HpackDecodingError error) {
+  if (!GetSpdyReloadableFlag(spdy_enable_granular_decompress_errors)) {
+    return Http2DecoderAdapter::SpdyFramerError::SPDY_DECOMPRESS_FAILURE;
+  }
+
+  SPDY_CODE_COUNT(spdy_enable_granular_decompress_errors);
+
+  switch (error) {
+    case HpackDecodingError::kOk:
+      return Http2DecoderAdapter::SpdyFramerError::SPDY_NO_ERROR;
+    case HpackDecodingError::kIndexVarintError:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_INDEX_VARINT_ERROR;
+    case HpackDecodingError::kNameLengthVarintError:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_NAME_LENGTH_VARINT_ERROR;
+    case HpackDecodingError::kValueLengthVarintError:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_VALUE_LENGTH_VARINT_ERROR;
+    case HpackDecodingError::kNameTooLong:
+      return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_NAME_TOO_LONG;
+    case HpackDecodingError::kValueTooLong:
+      return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_VALUE_TOO_LONG;
+    case HpackDecodingError::kNameHuffmanError:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_NAME_HUFFMAN_ERROR;
+    case HpackDecodingError::kValueHuffmanError:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_VALUE_HUFFMAN_ERROR;
+    case HpackDecodingError::kMissingDynamicTableSizeUpdate:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE;
+    case HpackDecodingError::kInvalidIndex:
+      return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_INVALID_INDEX;
+    case HpackDecodingError::kInvalidNameIndex:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_INVALID_NAME_INDEX;
+    case HpackDecodingError::kDynamicTableSizeUpdateNotAllowed:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED;
+    case HpackDecodingError::kInitialDynamicTableSizeUpdateIsAboveLowWaterMark:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK;
+    case HpackDecodingError::kDynamicTableSizeUpdateIsAboveAcknowledgedSetting:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING;
+    case HpackDecodingError::kTruncatedBlock:
+      return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_TRUNCATED_BLOCK;
+    case HpackDecodingError::kFragmentTooLong:
+      return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_FRAGMENT_TOO_LONG;
+    case HpackDecodingError::kCompressedHeaderSizeExceedsLimit:
+      return Http2DecoderAdapter::SpdyFramerError::
+          SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT;
+  }
+
+  return Http2DecoderAdapter::SpdyFramerError::SPDY_DECOMPRESS_FAILURE;
 }
 
 }  // namespace
@@ -166,6 +224,38 @@
       return "INVALID_CONTROL_FRAME_SIZE";
     case SPDY_OVERSIZED_PAYLOAD:
       return "OVERSIZED_PAYLOAD";
+    case SPDY_HPACK_INDEX_VARINT_ERROR:
+      return "HPACK_INDEX_VARINT_ERROR";
+    case SPDY_HPACK_NAME_LENGTH_VARINT_ERROR:
+      return "HPACK_NAME_LENGTH_VARINT_ERROR";
+    case SPDY_HPACK_VALUE_LENGTH_VARINT_ERROR:
+      return "HPACK_VALUE_LENGTH_VARINT_ERROR";
+    case SPDY_HPACK_NAME_TOO_LONG:
+      return "HPACK_NAME_TOO_LONG";
+    case SPDY_HPACK_VALUE_TOO_LONG:
+      return "HPACK_VALUE_TOO_LONG";
+    case SPDY_HPACK_NAME_HUFFMAN_ERROR:
+      return "HPACK_NAME_HUFFMAN_ERROR";
+    case SPDY_HPACK_VALUE_HUFFMAN_ERROR:
+      return "HPACK_VALUE_HUFFMAN_ERROR";
+    case SPDY_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE:
+      return "HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE";
+    case SPDY_HPACK_INVALID_INDEX:
+      return "HPACK_INVALID_INDEX";
+    case SPDY_HPACK_INVALID_NAME_INDEX:
+      return "HPACK_INVALID_NAME_INDEX";
+    case SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED:
+      return "HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED";
+    case SPDY_HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK:
+      return "HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK";
+    case SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING:
+      return "HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING";
+    case SPDY_HPACK_TRUNCATED_BLOCK:
+      return "HPACK_TRUNCATED_BLOCK";
+    case SPDY_HPACK_FRAGMENT_TOO_LONG:
+      return "HPACK_FRAGMENT_TOO_LONG";
+    case SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT:
+      return "HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT";
     case LAST_ERROR:
       return "UNKNOWN_ERROR";
   }
@@ -410,8 +500,10 @@
 void Http2DecoderAdapter::OnHpackFragment(const char* data, size_t len) {
   SPDY_DVLOG(1) << "OnHpackFragment: len=" << len;
   on_hpack_fragment_called_ = true;
-  if (!GetHpackDecoder()->HandleControlFrameHeadersData(data, len)) {
-    SetSpdyErrorAndNotify(SpdyFramerError::SPDY_DECOMPRESS_FAILURE);
+  auto* decoder = GetHpackDecoder();
+  if (!decoder->HandleControlFrameHeadersData(data, len)) {
+    SetSpdyErrorAndNotify(
+        HpackDecodingErrorToSpdyFramerError(decoder->error()));
     return;
   }
 }
@@ -995,10 +1087,12 @@
               frame_type() == Http2FrameType::CONTINUATION)
         << frame_header();
     has_expected_frame_type_ = false;
-    if (GetHpackDecoder()->HandleControlFrameHeadersComplete(nullptr)) {
+    auto* decoder = GetHpackDecoder();
+    if (decoder->HandleControlFrameHeadersComplete(nullptr)) {
       visitor()->OnHeaderFrameEnd(stream_id());
     } else {
-      SetSpdyErrorAndNotify(SpdyFramerError::SPDY_DECOMPRESS_FAILURE);
+      SetSpdyErrorAndNotify(
+          HpackDecodingErrorToSpdyFramerError(decoder->error()));
       return;
     }
     const Http2FrameHeader& first = frame_type() == Http2FrameType::CONTINUATION
diff --git a/spdy/core/http2_frame_decoder_adapter.h b/spdy/core/http2_frame_decoder_adapter.h
index 2bbb9f1..4a67833 100644
--- a/spdy/core/http2_frame_decoder_adapter.h
+++ b/spdy/core/http2_frame_decoder_adapter.h
@@ -76,6 +76,25 @@
     SPDY_INVALID_CONTROL_FRAME_SIZE,   // Control frame not sized to spec
     SPDY_OVERSIZED_PAYLOAD,            // Payload size was too large
 
+    // HttpDecoder or HttpDecoderAdapter error.
+    // See HpackDecodingError for description of each error code.
+    SPDY_HPACK_INDEX_VARINT_ERROR,
+    SPDY_HPACK_NAME_LENGTH_VARINT_ERROR,
+    SPDY_HPACK_VALUE_LENGTH_VARINT_ERROR,
+    SPDY_HPACK_NAME_TOO_LONG,
+    SPDY_HPACK_VALUE_TOO_LONG,
+    SPDY_HPACK_NAME_HUFFMAN_ERROR,
+    SPDY_HPACK_VALUE_HUFFMAN_ERROR,
+    SPDY_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE,
+    SPDY_HPACK_INVALID_INDEX,
+    SPDY_HPACK_INVALID_NAME_INDEX,
+    SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED,
+    SPDY_HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK,
+    SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING,
+    SPDY_HPACK_TRUNCATED_BLOCK,
+    SPDY_HPACK_FRAGMENT_TOO_LONG,
+    SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT,
+
     LAST_ERROR,  // Must be the last entry in the enum.
   };
 
diff --git a/spdy/platform/api/spdy_flags.h b/spdy/platform/api/spdy_flags.h
index dc95427..7d9345e 100644
--- a/spdy/platform/api/spdy_flags.h
+++ b/spdy/platform/api/spdy_flags.h
@@ -10,6 +10,7 @@
 #define GetSpdyReloadableFlag(flag) GetSpdyReloadableFlagImpl(flag)
 #define GetSpdyRestartFlag(flag) GetSpdyRestartFlagImpl(flag)
 
+#define SPDY_CODE_COUNT SPDY_CODE_COUNT_IMPL
 #define SPDY_CODE_COUNT_N SPDY_CODE_COUNT_N_IMPL
 
 #endif  // QUICHE_SPDY_PLATFORM_API_SPDY_FLAGS_H_