Plumb error code from QpackDecoder to QuicSpdyStream. Also, make QpackProgressiveDecoder::OnError() private. Also, change four string_view() constructors to testing::Eq() helpers in qpack_decoder_test.cc to be consistent with the other thirty EXPECT_CALLs. (Either one is sufficient, but compilation fails in Chromium when a C-style string literal is passed directly to EXPECT_CALL.) Protected by FLAGS_quic_reloadable_flag_quic_reject_invalid_chars_in_field_value. PiperOrigin-RevId: 398298181
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc index f4fa3ee..365ad44 100644 --- a/quic/core/http/quic_spdy_stream.cc +++ b/quic/core/http/quic_spdy_stream.cc
@@ -20,7 +20,6 @@ #include "quic/core/http/web_transport_http3.h" #include "quic/core/qpack/qpack_decoder.h" #include "quic/core/qpack/qpack_encoder.h" -#include "quic/core/quic_error_codes.h" #include "quic/core/quic_types.h" #include "quic/core/quic_utils.h" #include "quic/core/quic_versions.h" @@ -565,14 +564,14 @@ } } -void QuicSpdyStream::OnHeaderDecodingError(absl::string_view error_message) { +void QuicSpdyStream::OnHeaderDecodingError(QuicErrorCode error_code, + absl::string_view error_message) { qpack_decoded_headers_accumulator_.reset(); std::string connection_close_error_message = absl::StrCat( "Error decoding ", headers_decompressed_ ? "trailers" : "headers", " on stream ", id(), ": ", error_message); - OnUnrecoverableError(QUIC_QPACK_DECOMPRESSION_FAILED, - connection_close_error_message); + OnUnrecoverableError(error_code, connection_close_error_message); } void QuicSpdyStream::MaybeSendPriorityUpdateFrame() {
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h index e3e3d6a..50befb0 100644 --- a/quic/core/http/quic_spdy_stream.h +++ b/quic/core/http/quic_spdy_stream.h
@@ -24,6 +24,7 @@ #include "quic/core/http/quic_header_list.h" #include "quic/core/http/quic_spdy_stream_body_manager.h" #include "quic/core/qpack/qpack_decoded_headers_accumulator.h" +#include "quic/core/quic_error_codes.h" #include "quic/core/quic_packets.h" #include "quic/core/quic_stream.h" #include "quic/core/quic_stream_sequencer.h" @@ -217,7 +218,8 @@ // QpackDecodedHeadersAccumulator::Visitor implementation. void OnHeadersDecoded(QuicHeaderList headers, bool header_list_size_limit_exceeded) override; - void OnHeaderDecodingError(absl::string_view error_message) override; + void OnHeaderDecodingError(QuicErrorCode error_code, + absl::string_view error_message) override; QuicSpdySession* spdy_session() const { return spdy_session_; }
diff --git a/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc b/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc index 9c51f61..466966a 100644 --- a/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc +++ b/quic/core/qpack/fuzzer/qpack_decoder_fuzzer.cc
@@ -9,6 +9,7 @@ #include "absl/strings/string_view.h" #include "quic/core/qpack/qpack_decoder.h" +#include "quic/core/quic_error_codes.h" #include "quic/platform/api/quic_fuzzed_data_provider.h" #include "quic/test_tools/qpack/qpack_decoder_test_utils.h" #include "quic/test_tools/qpack/qpack_test_utils.h" @@ -61,7 +62,8 @@ QUICHE_CHECK_EQ(1u, result); } - void OnDecodingErrorDetected(absl::string_view /*error_message*/) override { + void OnDecodingErrorDetected(QuicErrorCode /*error_code*/, + absl::string_view /*error_message*/) override { *error_detected_ = true; }
diff --git a/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc index b4ed2ef..c327bdc 100644 --- a/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc +++ b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
@@ -24,6 +24,20 @@ namespace quic { namespace test { +namespace { + +// Find the first occurrence of invalid characters NUL, LF, CR in |*value| and +// remove that and the remaining of the string. +void TruncateValueOnInvalidChars(std::string* value) { + for (auto it = value->begin(); it != value->end(); ++it) { + if (*it == '\0' || *it == '\n' || *it == '\r') { + value->erase(it, value->end()); + return; + } + } +} + +} // anonymous namespace // Class to hold QpackEncoder and its DecoderStreamErrorDelegate. class EncodingEndpoint { @@ -278,8 +292,10 @@ visitor_->OnHeaderBlockDecoded(stream_id_); } - void OnHeaderDecodingError(absl::string_view error_message) override { - QUICHE_CHECK(false) << error_message; + void OnHeaderDecodingError(QuicErrorCode error_code, + absl::string_view error_message) override { + QUICHE_CHECK(false) << QuicErrorCodeToString(error_code) << " " + << error_message; } void Decode(absl::string_view data) { accumulator_.Decode(data); } @@ -509,6 +525,7 @@ // Header name not in the static table, fuzzed header value. name = "foo"; value = provider->ConsumeRandomLengthString(128); + TruncateValueOnInvalidChars(&value); break; case 11: // Another header name not in the static table, empty header value. @@ -525,11 +542,13 @@ // Another header name not in the static table, fuzzed header value. name = "bar"; value = provider->ConsumeRandomLengthString(128); + TruncateValueOnInvalidChars(&value); break; default: // Fuzzed header name and header value. name = provider->ConsumeRandomLengthString(128); value = provider->ConsumeRandomLengthString(128); + TruncateValueOnInvalidChars(&value); } header_list.AppendValueOrAddHeader(name, value);
diff --git a/quic/core/qpack/qpack_decoded_headers_accumulator.cc b/quic/core/qpack/qpack_decoded_headers_accumulator.cc index 9a5af7e..4e50e72 100644 --- a/quic/core/qpack/qpack_decoded_headers_accumulator.cc +++ b/quic/core/qpack/qpack_decoded_headers_accumulator.cc
@@ -67,13 +67,13 @@ } void QpackDecodedHeadersAccumulator::OnDecodingErrorDetected( - absl::string_view error_message) { + QuicErrorCode error_code, absl::string_view error_message) { QUICHE_DCHECK(!error_detected_); QUICHE_DCHECK(!headers_decoded_); error_detected_ = true; // Might destroy |this|. - visitor_->OnHeaderDecodingError(error_message); + visitor_->OnHeaderDecodingError(error_code, error_message); } void QpackDecodedHeadersAccumulator::Decode(absl::string_view data) {
diff --git a/quic/core/qpack/qpack_decoded_headers_accumulator.h b/quic/core/qpack/qpack_decoded_headers_accumulator.h index b22da2a..57d1839 100644 --- a/quic/core/qpack/qpack_decoded_headers_accumulator.h +++ b/quic/core/qpack/qpack_decoded_headers_accumulator.h
@@ -11,6 +11,7 @@ #include "absl/strings/string_view.h" #include "quic/core/http/quic_header_list.h" #include "quic/core/qpack/qpack_progressive_decoder.h" +#include "quic/core/quic_error_codes.h" #include "quic/core/quic_types.h" #include "quic/platform/api/quic_export.h" @@ -45,7 +46,8 @@ bool header_list_size_limit_exceeded) = 0; // Called when an error has occurred. - virtual void OnHeaderDecodingError(absl::string_view error_message) = 0; + virtual void OnHeaderDecodingError(QuicErrorCode error_code, + absl::string_view error_message) = 0; }; QpackDecodedHeadersAccumulator(QuicStreamId id, @@ -59,7 +61,8 @@ void OnHeaderDecoded(absl::string_view name, absl::string_view value) override; void OnDecodingCompleted() override; - void OnDecodingErrorDetected(absl::string_view error_message) override; + void OnDecodingErrorDetected(QuicErrorCode error_code, + absl::string_view error_message) override; // Decode payload data. // Must not be called if an error has been detected.
diff --git a/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc b/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc index 217f866..0aa08a0 100644 --- a/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc +++ b/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc
@@ -46,9 +46,8 @@ OnHeadersDecoded, (QuicHeaderList headers, bool header_list_size_limit_exceeded), (override)); - MOCK_METHOD(void, - OnHeaderDecodingError, - (absl::string_view error_message), + MOCK_METHOD(void, OnHeaderDecodingError, + (QuicErrorCode error_code, absl::string_view error_message), (override)); }; @@ -78,7 +77,8 @@ // HEADERS frame payload must have a complete Header Block Prefix. TEST_F(QpackDecodedHeadersAccumulatorTest, EmptyPayload) { EXPECT_CALL(visitor_, - OnHeaderDecodingError(Eq("Incomplete header data prefix."))); + OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Incomplete header data prefix."))); accumulator_.EndHeaderBlock(); } @@ -87,7 +87,8 @@ accumulator_.Decode(absl::HexStringToBytes("00")); EXPECT_CALL(visitor_, - OnHeaderDecodingError(Eq("Incomplete header data prefix."))); + OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Incomplete header data prefix."))); accumulator_.EndHeaderBlock(); } @@ -110,14 +111,16 @@ TEST_F(QpackDecodedHeadersAccumulatorTest, TruncatedPayload) { accumulator_.Decode(absl::HexStringToBytes("00002366")); - EXPECT_CALL(visitor_, OnHeaderDecodingError(Eq("Incomplete header block."))); + EXPECT_CALL(visitor_, OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Incomplete header block."))); accumulator_.EndHeaderBlock(); } // This payload is invalid because it refers to a non-existing static entry. TEST_F(QpackDecodedHeadersAccumulatorTest, InvalidPayload) { EXPECT_CALL(visitor_, - OnHeaderDecodingError(Eq("Static table entry not found."))); + OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Static table entry not found."))); accumulator_.Decode(absl::HexStringToBytes("0000ff23ff24")); } @@ -241,7 +244,8 @@ qpack_decoder_.OnSetDynamicTableCapacity(kMaxDynamicTableCapacity); // Adding dynamic table entry unblocks decoding. Error is detected. - EXPECT_CALL(visitor_, OnHeaderDecodingError(Eq("Invalid relative index."))); + EXPECT_CALL(visitor_, OnHeaderDecodingError(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Invalid relative index."))); qpack_decoder_.OnInsertWithoutNameReference("foo", "bar"); }
diff --git a/quic/core/qpack/qpack_decoder_test.cc b/quic/core/qpack/qpack_decoder_test.cc index 772b1d2..8f4b092 100644 --- a/quic/core/qpack/qpack_decoder_test.cc +++ b/quic/core/qpack/qpack_decoder_test.cc
@@ -49,8 +49,9 @@ void SetUp() override { // Destroy QpackProgressiveDecoder on error to test that it does not crash. // See https://crbug.com/1025209. - ON_CALL(handler_, OnDecodingErrorDetected(_)) - .WillByDefault(Invoke([this](absl::string_view /* error_message */) { + ON_CALL(handler_, OnDecodingErrorDetected(_, _)) + .WillByDefault(Invoke([this](QuicErrorCode /* error_code */, + absl::string_view /* error_message */) { progressive_decoder_.reset(); })); } @@ -114,7 +115,8 @@ TEST_P(QpackDecoderTest, NoPrefix) { EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Incomplete header data prefix."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Incomplete header data prefix."))); // Header Data Prefix is at least two bytes long. DecodeHeaderBlock(absl::HexStringToBytes("00")); @@ -126,7 +128,8 @@ StartDecoding(); EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Encoded integer too large."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Encoded integer too large."))); // Encoded Required Insert Count in Header Data Prefix is too large. DecodeData(absl::HexStringToBytes("ffffffffffffffffffffffffffff")); @@ -188,7 +191,8 @@ // Name Length value is too large for varint decoder to decode. TEST_P(QpackDecoderTest, NameLenTooLargeForVarintDecoder) { EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Encoded integer too large."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Encoded integer too large."))); DecodeHeaderBlock(absl::HexStringToBytes("000027ffffffffffffffffffff")); } @@ -196,7 +200,8 @@ // Name Length value can be decoded by varint decoder but exceeds 1 MB limit. TEST_P(QpackDecoderTest, NameLenExceedsLimit) { EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("String literal too long."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("String literal too long."))); DecodeHeaderBlock(absl::HexStringToBytes("000027ffff7f")); } @@ -204,7 +209,8 @@ // Value Length value is too large for varint decoder to decode. TEST_P(QpackDecoderTest, ValueLenTooLargeForVarintDecoder) { EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Encoded integer too large."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Encoded integer too large."))); DecodeHeaderBlock( absl::HexStringToBytes("000023666f6f7fffffffffffffffffffff")); @@ -213,14 +219,29 @@ // Value Length value can be decoded by varint decoder but exceeds 1 MB limit. TEST_P(QpackDecoderTest, ValueLenExceedsLimit) { EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("String literal too long."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("String literal too long."))); DecodeHeaderBlock(absl::HexStringToBytes("000023666f6f7fffff7f")); } +TEST_P(QpackDecoderTest, LineFeedInValue) { + if (GetQuicReloadableFlag(quic_reject_invalid_chars_in_field_value)) { + EXPECT_CALL(handler_, + OnDecodingErrorDetected(QUIC_INVALID_CHARACTER_IN_FIELD_VALUE, + "Invalid character in field value.")); + } else { + EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("ba\nr"))); + EXPECT_CALL(handler_, OnDecodingCompleted()); + } + + DecodeHeaderBlock(absl::HexStringToBytes("000023666f6f0462610a72")); +} + TEST_P(QpackDecoderTest, IncompleteHeaderBlock) { EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Incomplete header block."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Incomplete header block."))); DecodeHeaderBlock(absl::HexStringToBytes("00002366")); } @@ -251,8 +272,9 @@ } TEST_P(QpackDecoderTest, HuffmanNameDoesNotHaveEOSPrefix) { - EXPECT_CALL(handler_, OnDecodingErrorDetected(absl::string_view( - "Error in Huffman-encoded string."))); + EXPECT_CALL(handler_, + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Error in Huffman-encoded string."))); // 'y' ends in 0b0 on the most significant bit of the last byte. // The remaining 7 bits must be a prefix of EOS, which is all 1s. @@ -261,8 +283,9 @@ } TEST_P(QpackDecoderTest, HuffmanValueDoesNotHaveEOSPrefix) { - EXPECT_CALL(handler_, OnDecodingErrorDetected(absl::string_view( - "Error in Huffman-encoded string."))); + EXPECT_CALL(handler_, + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Error in Huffman-encoded string."))); // 'e' ends in 0b101, taking up the 3 most significant bits of the last byte. // The remaining 5 bits must be a prefix of EOS, which is all 1s. @@ -271,8 +294,9 @@ } TEST_P(QpackDecoderTest, HuffmanNameEOSPrefixTooLong) { - EXPECT_CALL(handler_, OnDecodingErrorDetected(absl::string_view( - "Error in Huffman-encoded string."))); + EXPECT_CALL(handler_, + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Error in Huffman-encoded string."))); // The trailing EOS prefix must be at most 7 bits long. Appending one octet // with value 0xff is invalid, even though 0b111111111111111 (15 bits) is a @@ -282,8 +306,9 @@ } TEST_P(QpackDecoderTest, HuffmanValueEOSPrefixTooLong) { - EXPECT_CALL(handler_, OnDecodingErrorDetected(absl::string_view( - "Error in Huffman-encoded string."))); + EXPECT_CALL(handler_, + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Error in Huffman-encoded string."))); // The trailing EOS prefix must be at most 7 bits long. Appending one octet // with value 0xff is invalid, even though 0b1111111111111 (13 bits) is a @@ -321,7 +346,8 @@ // Addressing entry 99 should trigger an error. EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Static table entry not found."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Static table entry not found."))); DecodeHeaderBlock(absl::HexStringToBytes("0000ff23ff24")); } @@ -430,6 +456,7 @@ DecodeEncoderStreamData(absl::HexStringToBytes("3f01")); EXPECT_CALL(handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Dynamic table entry already evicted."))); DecodeHeaderBlock(absl::HexStringToBytes( @@ -497,7 +524,8 @@ } TEST_P(QpackDecoderTest, InvalidDynamicEntryWhenBaseIsZero) { - EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index."))); + EXPECT_CALL(handler_, OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Invalid relative index."))); // Set dynamic table capacity to 1024. DecodeEncoderStreamData(absl::HexStringToBytes("3fe107")); @@ -512,7 +540,8 @@ } TEST_P(QpackDecoderTest, InvalidNegativeBase) { - EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Error calculating Base."))); + EXPECT_CALL(handler_, OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Error calculating Base."))); // Required Insert Count 1, Delta Base 1 with sign bit set, Base would // be 1 - 1 - 1 = -1, but it is not allowed to be negative. @@ -525,7 +554,8 @@ // Add literal entry with name "foo" and value "bar". DecodeEncoderStreamData(absl::HexStringToBytes("6294e703626172")); - EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index."))); + EXPECT_CALL(handler_, OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Invalid relative index."))); DecodeHeaderBlock(absl::HexStringToBytes( "0200" // Required Insert Count 1 and Delta Base 0. @@ -533,7 +563,8 @@ "81")); // Indexed Header Field instruction addressing relative index 1. // This is absolute index -1, which is invalid. - EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index."))); + EXPECT_CALL(handler_, OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Invalid relative index."))); DecodeHeaderBlock(absl::HexStringToBytes( "0200" // Required Insert Count 1 and Delta Base 0. @@ -554,6 +585,7 @@ DecodeEncoderStreamData(absl::HexStringToBytes("00000000")); EXPECT_CALL(handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Dynamic table entry already evicted."))); DecodeHeaderBlock(absl::HexStringToBytes( @@ -563,6 +595,7 @@ // This is absolute index 1. Such entry does not exist. EXPECT_CALL(handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Dynamic table entry already evicted."))); DecodeHeaderBlock(absl::HexStringToBytes( @@ -573,6 +606,7 @@ // entry does not exist. EXPECT_CALL(handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Dynamic table entry already evicted."))); DecodeHeaderBlock(absl::HexStringToBytes( @@ -583,6 +617,7 @@ // does not exist. EXPECT_CALL(handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Dynamic table entry already evicted."))); DecodeHeaderBlock(absl::HexStringToBytes( @@ -614,6 +649,7 @@ // Required Insert Count is decoded modulo 2 * MaxEntries, that is, modulo 64. // A value of 1 cannot be encoded as 65 even though it has the same remainder. EXPECT_CALL(handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Error decoding Required Insert Count."))); DecodeHeaderBlock(absl::HexStringToBytes("4100")); } @@ -622,6 +658,7 @@ // after a Header Block Prefix with an invalid Encoded Required Insert Count. TEST_P(QpackDecoderTest, DataAfterInvalidEncodedRequiredInsertCount) { EXPECT_CALL(handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Error decoding Required Insert Count."))); // Header Block Prefix followed by some extra data. DecodeHeaderBlock(absl::HexStringToBytes("410000")); @@ -666,7 +703,8 @@ EXPECT_CALL(handler_, OnHeaderDecoded(Eq(":method"), Eq("GET"))); EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Required Insert Count too large."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Required Insert Count too large."))); DecodeHeaderBlock(absl::HexStringToBytes( "0200" // Required Insert Count is 1. @@ -682,6 +720,7 @@ EXPECT_CALL( handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Absolute Index must be smaller than Required Insert Count."))); DecodeHeaderBlock(absl::HexStringToBytes( @@ -694,6 +733,7 @@ EXPECT_CALL( handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Absolute Index must be smaller than Required Insert Count."))); DecodeHeaderBlock(absl::HexStringToBytes( @@ -707,6 +747,7 @@ EXPECT_CALL( handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Absolute Index must be smaller than Required Insert Count."))); DecodeHeaderBlock(absl::HexStringToBytes( @@ -720,6 +761,7 @@ EXPECT_CALL( handler_, OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, Eq("Absolute Index must be smaller than Required Insert Count."))); DecodeHeaderBlock(absl::HexStringToBytes( @@ -743,7 +785,8 @@ EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Required Insert Count too large."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Required Insert Count too large."))); DecodeHeaderBlock(absl::HexStringToBytes( "0300" // Required Insert Count 2 and Delta Base 0. @@ -755,7 +798,8 @@ EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq(""))); EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Required Insert Count too large."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Required Insert Count too large."))); DecodeHeaderBlock(absl::HexStringToBytes( "0300" // Required Insert Count 2 and Delta Base 0. @@ -767,7 +811,8 @@ EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Required Insert Count too large."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Required Insert Count too large."))); DecodeHeaderBlock(absl::HexStringToBytes( "0481" // Required Insert Count 3 and Delta Base 1 with sign bit set. @@ -779,7 +824,8 @@ EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq(""))); EXPECT_CALL(handler_, - OnDecodingErrorDetected(Eq("Required Insert Count too large."))); + OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Required Insert Count too large."))); DecodeHeaderBlock(absl::HexStringToBytes( "0481" // Required Insert Count 3 and Delta Base 1 with sign bit set. @@ -864,7 +910,8 @@ // Count of the header block. |handler_| methods are called immediately for // the already consumed part of the header block. EXPECT_CALL(handler_, OnHeaderDecoded(Eq("foo"), Eq("bar"))); - EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq("Invalid relative index."))); + EXPECT_CALL(handler_, OnDecodingErrorDetected(QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Invalid relative index."))); DecodeEncoderStreamData(absl::HexStringToBytes("6294e703626172")); } @@ -905,8 +952,10 @@ auto progressive_decoder1 = CreateProgressiveDecoder(/* stream_id = */ 1); progressive_decoder1->Decode(data); - EXPECT_CALL(handler_, OnDecodingErrorDetected(Eq( - "Limit on number of blocked streams exceeded."))); + EXPECT_CALL(handler_, + OnDecodingErrorDetected( + QUIC_QPACK_DECOMPRESSION_FAILED, + Eq("Limit on number of blocked streams exceeded."))); auto progressive_decoder2 = CreateProgressiveDecoder(/* stream_id = */ 2); progressive_decoder2->Decode(data);
diff --git a/quic/core/qpack/qpack_progressive_decoder.cc b/quic/core/qpack/qpack_progressive_decoder.cc index 3e7d0ad..96a3da5 100644 --- a/quic/core/qpack/qpack_progressive_decoder.cc +++ b/quic/core/qpack/qpack_progressive_decoder.cc
@@ -12,20 +12,27 @@ #include "quic/core/qpack/qpack_index_conversions.h" #include "quic/core/qpack/qpack_instructions.h" #include "quic/core/qpack/qpack_required_insert_count.h" +#include "quic/platform/api/quic_flag_utils.h" +#include "quic/platform/api/quic_flags.h" #include "quic/platform/api/quic_logging.h" namespace quic { +namespace { + +// The value argument passed to OnHeaderDecoded() is from an entry in the static +// table. +constexpr bool kValueFromStaticTable = true; + +} // anonymous namespace + QpackProgressiveDecoder::QpackProgressiveDecoder( - QuicStreamId stream_id, - BlockedStreamLimitEnforcer* enforcer, - DecodingCompletedVisitor* visitor, - QpackDecoderHeaderTable* header_table, + QuicStreamId stream_id, BlockedStreamLimitEnforcer* enforcer, + DecodingCompletedVisitor* visitor, QpackDecoderHeaderTable* header_table, HeadersHandlerInterface* handler) : stream_id_(stream_id), - prefix_decoder_( - std::make_unique<QpackInstructionDecoder>(QpackPrefixLanguage(), - this)), + prefix_decoder_(std::make_unique<QpackInstructionDecoder>( + QpackPrefixLanguage(), this)), instruction_decoder_(QpackRequestStreamLanguage(), this), enforcer_(enforcer), visitor_(visitor), @@ -38,7 +45,13 @@ blocked_(false), decoding_(true), error_detected_(false), - cancelled_(false) {} + cancelled_(false), + reject_invalid_chars_in_field_value_( + GetQuicReloadableFlag(quic_reject_invalid_chars_in_field_value)) { + if (reject_invalid_chars_in_field_value_) { + QUIC_RELOADABLE_FLAG_COUNT(quic_reject_invalid_chars_in_field_value); + } +} QpackProgressiveDecoder::~QpackProgressiveDecoder() { if (blocked_ && !cancelled_) { @@ -89,14 +102,6 @@ } } -void QpackProgressiveDecoder::OnError(absl::string_view error_message) { - QUICHE_DCHECK(!error_detected_); - - error_detected_ = true; - // Might destroy |this|. - handler_->OnDecodingErrorDetected(error_message); -} - bool QpackProgressiveDecoder::OnInstructionDecoded( const QpackInstruction* instruction) { if (instruction == QpackPrefixInstruction()) { @@ -126,10 +131,9 @@ 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); + // Ignore |error_code| and always use QUIC_QPACK_DECOMPRESSION_FAILED to avoid + // having to define a new QuicErrorCode for every instruction decoder error. + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, error_message); } void QpackProgressiveDecoder::OnInsertCountReachedThreshold() { @@ -164,12 +168,13 @@ uint64_t absolute_index; if (!QpackRequestStreamRelativeIndexToAbsoluteIndex( instruction_decoder_.varint(), base_, &absolute_index)) { - OnError("Invalid relative index."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Invalid relative index."); return false; } if (absolute_index >= required_insert_count_) { - OnError("Absolute Index must be smaller than Required Insert Count."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Absolute Index must be smaller than Required Insert Count."); return false; } @@ -180,36 +185,37 @@ auto entry = header_table_->LookupEntry(/* is_static = */ false, absolute_index); if (!entry) { - OnError("Dynamic table entry already evicted."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Dynamic table entry already evicted."); return false; } header_table_->set_dynamic_table_entry_referenced(); - handler_->OnHeaderDecoded(entry->name(), entry->value()); - return true; + return OnHeaderDecoded(!kValueFromStaticTable, entry->name(), + entry->value()); } auto entry = header_table_->LookupEntry(/* is_static = */ true, instruction_decoder_.varint()); if (!entry) { - OnError("Static table entry not found."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Static table entry not found."); return false; } - handler_->OnHeaderDecoded(entry->name(), entry->value()); - return true; + return OnHeaderDecoded(kValueFromStaticTable, entry->name(), entry->value()); } bool QpackProgressiveDecoder::DoIndexedHeaderFieldPostBaseInstruction() { uint64_t absolute_index; if (!QpackPostBaseIndexToAbsoluteIndex(instruction_decoder_.varint(), base_, &absolute_index)) { - OnError("Invalid post-base index."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Invalid post-base index."); return false; } if (absolute_index >= required_insert_count_) { - OnError("Absolute Index must be smaller than Required Insert Count."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Absolute Index must be smaller than Required Insert Count."); return false; } @@ -220,13 +226,13 @@ auto entry = header_table_->LookupEntry(/* is_static = */ false, absolute_index); if (!entry) { - OnError("Dynamic table entry already evicted."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Dynamic table entry already evicted."); return false; } header_table_->set_dynamic_table_entry_referenced(); - handler_->OnHeaderDecoded(entry->name(), entry->value()); - return true; + return OnHeaderDecoded(!kValueFromStaticTable, entry->name(), entry->value()); } bool QpackProgressiveDecoder::DoLiteralHeaderFieldNameReferenceInstruction() { @@ -234,12 +240,13 @@ uint64_t absolute_index; if (!QpackRequestStreamRelativeIndexToAbsoluteIndex( instruction_decoder_.varint(), base_, &absolute_index)) { - OnError("Invalid relative index."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Invalid relative index."); return false; } if (absolute_index >= required_insert_count_) { - OnError("Absolute Index must be smaller than Required Insert Count."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Absolute Index must be smaller than Required Insert Count."); return false; } @@ -250,36 +257,38 @@ auto entry = header_table_->LookupEntry(/* is_static = */ false, absolute_index); if (!entry) { - OnError("Dynamic table entry already evicted."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Dynamic table entry already evicted."); return false; } header_table_->set_dynamic_table_entry_referenced(); - handler_->OnHeaderDecoded(entry->name(), instruction_decoder_.value()); - return true; + return OnHeaderDecoded(!kValueFromStaticTable, entry->name(), + instruction_decoder_.value()); } auto entry = header_table_->LookupEntry(/* is_static = */ true, instruction_decoder_.varint()); if (!entry) { - OnError("Static table entry not found."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Static table entry not found."); return false; } - handler_->OnHeaderDecoded(entry->name(), instruction_decoder_.value()); - return true; + return OnHeaderDecoded(kValueFromStaticTable, entry->name(), + instruction_decoder_.value()); } bool QpackProgressiveDecoder::DoLiteralHeaderFieldPostBaseInstruction() { uint64_t absolute_index; if (!QpackPostBaseIndexToAbsoluteIndex(instruction_decoder_.varint(), base_, &absolute_index)) { - OnError("Invalid post-base index."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Invalid post-base index."); return false; } if (absolute_index >= required_insert_count_) { - OnError("Absolute Index must be smaller than Required Insert Count."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Absolute Index must be smaller than Required Insert Count."); return false; } @@ -290,20 +299,19 @@ auto entry = header_table_->LookupEntry(/* is_static = */ false, absolute_index); if (!entry) { - OnError("Dynamic table entry already evicted."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Dynamic table entry already evicted."); return false; } header_table_->set_dynamic_table_entry_referenced(); - handler_->OnHeaderDecoded(entry->name(), instruction_decoder_.value()); - return true; + return OnHeaderDecoded(!kValueFromStaticTable, entry->name(), + instruction_decoder_.value()); } bool QpackProgressiveDecoder::DoLiteralHeaderFieldInstruction() { - handler_->OnHeaderDecoded(instruction_decoder_.name(), - instruction_decoder_.value()); - - return true; + return OnHeaderDecoded(!kValueFromStaticTable, instruction_decoder_.name(), + instruction_decoder_.value()); } bool QpackProgressiveDecoder::DoPrefixInstruction() { @@ -312,14 +320,15 @@ if (!QpackDecodeRequiredInsertCount( prefix_decoder_->varint(), header_table_->max_entries(), header_table_->inserted_entry_count(), &required_insert_count_)) { - OnError("Error decoding Required Insert Count."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Error decoding Required Insert Count."); return false; } const bool sign = prefix_decoder_->s_bit(); const uint64_t delta_base = prefix_decoder_->varint2(); if (!DeltaBaseToBase(sign, delta_base, &base_)) { - OnError("Error calculating Base."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Error calculating Base."); return false; } @@ -327,7 +336,8 @@ if (required_insert_count_ > header_table_->inserted_entry_count()) { if (!enforcer_->OnStreamBlocked(stream_id_)) { - OnError("Limit on number of blocked streams exceeded."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Limit on number of blocked streams exceeded."); return false; } blocked_ = true; @@ -337,6 +347,32 @@ return true; } +bool QpackProgressiveDecoder::OnHeaderDecoded(bool value_from_static_table, + absl::string_view name, + absl::string_view value) { + // Skip test for static table entries as they are all known to be valid. + if (reject_invalid_chars_in_field_value_ && !value_from_static_table) { + // According to Section 10.3 of + // https://quicwg.org/base-drafts/draft-ietf-quic-http.html, + // "[...] HTTP/3 can transport field values that are not valid. While most + // values that can be encoded will not alter field parsing, carriage return + // (CR, ASCII 0x0d), line feed (LF, ASCII 0x0a), and the zero character + // (NUL, ASCII 0x00) might be exploited by an attacker if they are + // translated verbatim. Any request or response that contains a character + // not permitted in a field value MUST be treated as malformed [...]" + for (const auto c : value) { + if (c == '\0' || c == '\n' || c == '\r') { + OnError(QUIC_INVALID_CHARACTER_IN_FIELD_VALUE, + "Invalid character in field value."); + return false; + } + } + } + + handler_->OnHeaderDecoded(name, value); + return true; +} + void QpackProgressiveDecoder::FinishDecoding() { QUICHE_DCHECK(buffer_.empty()); QUICHE_DCHECK(!blocked_); @@ -347,17 +383,18 @@ } if (!instruction_decoder_.AtInstructionBoundary()) { - OnError("Incomplete header block."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Incomplete header block."); return; } if (!prefix_decoded_) { - OnError("Incomplete header data prefix."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Incomplete header data prefix."); return; } if (required_insert_count_ != required_insert_count_so_far_) { - OnError("Required Insert Count too large."); + OnError(QUIC_QPACK_DECOMPRESSION_FAILED, + "Required Insert Count too large."); return; } @@ -365,6 +402,15 @@ handler_->OnDecodingCompleted(); } +void QpackProgressiveDecoder::OnError(QuicErrorCode error_code, + absl::string_view error_message) { + QUICHE_DCHECK(!error_detected_); + + error_detected_ = true; + // Might destroy |this|. + handler_->OnDecodingErrorDetected(error_code, error_message); +} + bool QpackProgressiveDecoder::DeltaBaseToBase(bool sign, uint64_t delta_base, uint64_t* base) {
diff --git a/quic/core/qpack/qpack_progressive_decoder.h b/quic/core/qpack/qpack_progressive_decoder.h index ced6bf0..b077ab1 100644 --- a/quic/core/qpack/qpack_progressive_decoder.h +++ b/quic/core/qpack/qpack_progressive_decoder.h
@@ -13,6 +13,7 @@ #include "quic/core/qpack/qpack_encoder_stream_receiver.h" #include "quic/core/qpack/qpack_header_table.h" #include "quic/core/qpack/qpack_instruction_decoder.h" +#include "quic/core/quic_error_codes.h" #include "quic/core/quic_types.h" #include "quic/platform/api/quic_export.h" @@ -46,7 +47,8 @@ // Called when a decoding error has occurred. No other methods will be // called afterwards. Implementations are allowed to destroy // the QpackProgressiveDecoder instance synchronously. - virtual void OnDecodingErrorDetected(absl::string_view error_message) = 0; + virtual void OnDecodingErrorDetected(QuicErrorCode error_code, + absl::string_view error_message) = 0; }; // Interface for keeping track of blocked streams for the purpose of enforcing @@ -95,9 +97,6 @@ // through Decode(). No methods must be called afterwards. void EndHeaderBlock(); - // Called on error. - void OnError(absl::string_view error_message); - // QpackInstructionDecoder::Delegate implementation. bool OnInstructionDecoded(const QpackInstruction* instruction) override; void OnInstructionDecodingError(QpackInstructionDecoder::ErrorCode error_code, @@ -115,9 +114,20 @@ bool DoLiteralHeaderFieldInstruction(); bool DoPrefixInstruction(); + // Called when an entry is decoded. Performs validation and calls + // HeadersHandlerInterface::OnHeaderDecoded() or OnError() as needed. Returns + // true if header value is valid, false otherwise. Skips validation if + // |value_from_static_table| is true, because static table entries are always + // valid. + bool OnHeaderDecoded(bool value_from_static_table, absl::string_view name, + absl::string_view value); + // Called as soon as EndHeaderBlock() is called and decoding is not blocked. void FinishDecoding(); + // Called on error. + void OnError(QuicErrorCode error_code, absl::string_view error_message); + // Calculates Base from |required_insert_count_|, which must be set before // calling this method, and sign bit and Delta Base in the Header Data Prefix, // which are passed in as arguments. Returns true on success, false on @@ -166,6 +176,10 @@ // True if QpackDecoderHeaderTable has been destroyed // while decoding is still blocked. bool cancelled_; + + // Latched value of + // gfe2_reloadable_flag_quic_reject_invalid_chars_in_field_value + const bool reject_invalid_chars_in_field_value_; }; } // namespace quic
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc index afd0bfb..162cb04 100644 --- a/quic/core/quic_error_codes.cc +++ b/quic/core/quic_error_codes.cc
@@ -275,6 +275,8 @@ RETURN_STRING_LITERAL(QUIC_TLS_UNRECOGNIZED_NAME); RETURN_STRING_LITERAL(QUIC_TLS_CERTIFICATE_REQUIRED); + RETURN_STRING_LITERAL(QUIC_INVALID_CHARACTER_IN_FIELD_VALUE); + RETURN_STRING_LITERAL(QUIC_LAST_ERROR); // Intentionally have no default case, so we'll break the build // if we add errors and don't put them here. @@ -772,6 +774,8 @@ return {true, static_cast<uint64_t>(CONNECTION_ID_LIMIT_ERROR)}; case QUIC_TOO_MANY_CONNECTION_ID_WAITING_TO_RETIRE: return {true, static_cast<uint64_t>(INTERNAL_ERROR)}; + case QUIC_INVALID_CHARACTER_IN_FIELD_VALUE: + return {false, static_cast<uint64_t>(QuicHttp3ErrorCode::MESSAGE_ERROR)}; case QUIC_LAST_ERROR: return {false, static_cast<uint64_t>(QUIC_LAST_ERROR)}; }
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h index f43ad0a..860f107 100644 --- a/quic/core/quic_error_codes.h +++ b/quic/core/quic_error_codes.h
@@ -599,8 +599,11 @@ QUIC_TLS_UNRECOGNIZED_NAME = 201, QUIC_TLS_CERTIFICATE_REQUIRED = 202, + // An HTTP field value containing an invalid character has been received. + QUIC_INVALID_CHARACTER_IN_FIELD_VALUE = 206, + // No error. Used as bound while iterating. - QUIC_LAST_ERROR = 206, + QUIC_LAST_ERROR = 207, }; // 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 @@ -716,6 +719,7 @@ REQUEST_REJECTED = 0x10B, REQUEST_CANCELLED = 0x10C, REQUEST_INCOMPLETE = 0x10D, + MESSAGE_ERROR = 0x10E, CONNECT_ERROR = 0x10F, VERSION_FALLBACK = 0x110, };
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h index db08117..125cbc0 100644 --- a/quic/core/quic_flags_list.h +++ b/quic/core/quic_flags_list.h
@@ -21,6 +21,8 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_check_cwnd_limited_before_aggregation_epoch, true) // If true, GFE will explicitly configure its signature algorithm preference. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_tls_set_signature_algorithm_prefs, true) +// If true, QPACK decoder rejects CR, LF, and NULL in field (header) values, and causes the stream to be reset with H3_MESSAGE_ERROR. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_reject_invalid_chars_in_field_value, 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, QuicAlarms that belong to a single QuicConnection will fire under the corresponding QuicConnectionContext.
diff --git a/quic/test_tools/qpack/qpack_decoder_test_utils.cc b/quic/test_tools/qpack/qpack_decoder_test_utils.cc index ab76c44..37f182a 100644 --- a/quic/test_tools/qpack/qpack_decoder_test_utils.cc +++ b/quic/test_tools/qpack/qpack_decoder_test_utils.cc
@@ -37,7 +37,7 @@ } void TestHeadersHandler::OnDecodingErrorDetected( - absl::string_view error_message) { + QuicErrorCode /*error_code*/, absl::string_view error_message) { ASSERT_FALSE(decoding_completed_); ASSERT_FALSE(decoding_error_detected_);
diff --git a/quic/test_tools/qpack/qpack_decoder_test_utils.h b/quic/test_tools/qpack/qpack_decoder_test_utils.h index 2220bd4..b6bff78 100644 --- a/quic/test_tools/qpack/qpack_decoder_test_utils.h +++ b/quic/test_tools/qpack/qpack_decoder_test_utils.h
@@ -10,6 +10,7 @@ #include "absl/strings/string_view.h" #include "quic/core/qpack/qpack_decoder.h" #include "quic/core/qpack/qpack_progressive_decoder.h" +#include "quic/core/quic_error_codes.h" #include "quic/platform/api/quic_test.h" #include "quic/test_tools/qpack/qpack_test_utils.h" #include "spdy/core/spdy_header_block.h" @@ -51,7 +52,8 @@ void OnHeaderDecoded(absl::string_view name, absl::string_view value) override; void OnDecodingCompleted() override; - void OnDecodingErrorDetected(absl::string_view error_message) override; + void OnDecodingErrorDetected(QuicErrorCode error_code, + absl::string_view error_message) override; // Release decoded header list. Must only be called if decoding is complete // and no errors have been detected. @@ -81,9 +83,8 @@ (absl::string_view name, absl::string_view value), (override)); MOCK_METHOD(void, OnDecodingCompleted, (), (override)); - MOCK_METHOD(void, - OnDecodingErrorDetected, - (absl::string_view error_message), + MOCK_METHOD(void, OnDecodingErrorDetected, + (QuicErrorCode error_code, absl::string_view error_message), (override)); }; @@ -95,7 +96,8 @@ void OnHeaderDecoded(absl::string_view /*name*/, absl::string_view /*value*/) override {} void OnDecodingCompleted() override {} - void OnDecodingErrorDetected(absl::string_view /*error_message*/) override {} + void OnDecodingErrorDetected(QuicErrorCode /*error_code*/, + absl::string_view /*error_message*/) override {} }; void QpackDecode(