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)}};
}