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(