Incrementally process HTTP/3 METADATA frames instead of buffering the full payload.
PiperOrigin-RevId: 615608615
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 9d9cf94..19fb0c8 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -224,6 +224,7 @@
"quic/core/http/http_decoder.h",
"quic/core/http/http_encoder.h",
"quic/core/http/http_frames.h",
+ "quic/core/http/metadata_decoder.h",
"quic/core/http/quic_header_list.h",
"quic/core/http/quic_headers_stream.h",
"quic/core/http/quic_receive_control_stream.h",
@@ -562,6 +563,7 @@
"quic/core/http/http_constants.cc",
"quic/core/http/http_decoder.cc",
"quic/core/http/http_encoder.cc",
+ "quic/core/http/metadata_decoder.cc",
"quic/core/http/quic_header_list.cc",
"quic/core/http/quic_headers_stream.cc",
"quic/core/http/quic_receive_control_stream.cc",
@@ -1197,6 +1199,7 @@
"quic/core/http/http_decoder_test.cc",
"quic/core/http/http_encoder_test.cc",
"quic/core/http/http_frames_test.cc",
+ "quic/core/http/metadata_decoder_test.cc",
"quic/core/http/quic_header_list_test.cc",
"quic/core/http/quic_headers_stream_test.cc",
"quic/core/http/quic_receive_control_stream_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 589f737..b8ab180 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -224,6 +224,7 @@
"src/quiche/quic/core/http/http_decoder.h",
"src/quiche/quic/core/http/http_encoder.h",
"src/quiche/quic/core/http/http_frames.h",
+ "src/quiche/quic/core/http/metadata_decoder.h",
"src/quiche/quic/core/http/quic_header_list.h",
"src/quiche/quic/core/http/quic_headers_stream.h",
"src/quiche/quic/core/http/quic_receive_control_stream.h",
@@ -562,6 +563,7 @@
"src/quiche/quic/core/http/http_constants.cc",
"src/quiche/quic/core/http/http_decoder.cc",
"src/quiche/quic/core/http/http_encoder.cc",
+ "src/quiche/quic/core/http/metadata_decoder.cc",
"src/quiche/quic/core/http/quic_header_list.cc",
"src/quiche/quic/core/http/quic_headers_stream.cc",
"src/quiche/quic/core/http/quic_receive_control_stream.cc",
@@ -1198,6 +1200,7 @@
"src/quiche/quic/core/http/http_decoder_test.cc",
"src/quiche/quic/core/http/http_encoder_test.cc",
"src/quiche/quic/core/http/http_frames_test.cc",
+ "src/quiche/quic/core/http/metadata_decoder_test.cc",
"src/quiche/quic/core/http/quic_header_list_test.cc",
"src/quiche/quic/core/http/quic_headers_stream_test.cc",
"src/quiche/quic/core/http/quic_receive_control_stream_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 274f204..373a3ea 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -223,6 +223,7 @@
"quiche/quic/core/http/http_decoder.h",
"quiche/quic/core/http/http_encoder.h",
"quiche/quic/core/http/http_frames.h",
+ "quiche/quic/core/http/metadata_decoder.h",
"quiche/quic/core/http/quic_header_list.h",
"quiche/quic/core/http/quic_headers_stream.h",
"quiche/quic/core/http/quic_receive_control_stream.h",
@@ -561,6 +562,7 @@
"quiche/quic/core/http/http_constants.cc",
"quiche/quic/core/http/http_decoder.cc",
"quiche/quic/core/http/http_encoder.cc",
+ "quiche/quic/core/http/metadata_decoder.cc",
"quiche/quic/core/http/quic_header_list.cc",
"quiche/quic/core/http/quic_headers_stream.cc",
"quiche/quic/core/http/quic_receive_control_stream.cc",
@@ -1197,6 +1199,7 @@
"quiche/quic/core/http/http_decoder_test.cc",
"quiche/quic/core/http/http_encoder_test.cc",
"quiche/quic/core/http/http_frames_test.cc",
+ "quiche/quic/core/http/metadata_decoder_test.cc",
"quiche/quic/core/http/quic_header_list_test.cc",
"quiche/quic/core/http/quic_headers_stream_test.cc",
"quiche/quic/core/http/quic_receive_control_stream_test.cc",
diff --git a/quiche/quic/core/http/metadata_decoder.cc b/quiche/quic/core/http/metadata_decoder.cc
new file mode 100644
index 0000000..ef14513
--- /dev/null
+++ b/quiche/quic/core/http/metadata_decoder.cc
@@ -0,0 +1,43 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/http/metadata_decoder.h"
+
+namespace quic {
+
+MetadataDecoder::MetadataDecoder(QuicStreamId id, size_t max_header_list_size,
+ size_t frame_header_len, size_t payload_length)
+ : qpack_decoder_(/*maximum_dynamic_table_capacity=*/0,
+ /*maximum_blocked_streams=*/0, &delegate_),
+ accumulator_(id, &qpack_decoder_, &decoder_, max_header_list_size),
+ frame_len_(frame_header_len + payload_length),
+ bytes_remaining_(payload_length) {}
+
+bool MetadataDecoder::Decode(absl::string_view payload) {
+ accumulator_.Decode(payload);
+ bytes_remaining_ -= payload.length();
+ return decoder_.error_code() == QUIC_NO_ERROR;
+}
+
+bool MetadataDecoder::EndHeaderBlock() {
+ QUIC_BUG_IF(METADATA bytes remaining, bytes_remaining_ != 0)
+ << "More metadata remaining: " << bytes_remaining_;
+
+ accumulator_.EndHeaderBlock();
+ return !decoder_.header_list_size_limit_exceeded();
+}
+
+void MetadataDecoder::MetadataHeadersDecoder::OnHeadersDecoded(
+ QuicHeaderList headers, bool header_list_size_limit_exceeded) {
+ header_list_size_limit_exceeded_ = header_list_size_limit_exceeded;
+ headers_ = std::move(headers);
+}
+
+void MetadataDecoder::MetadataHeadersDecoder::OnHeaderDecodingError(
+ QuicErrorCode error_code, absl::string_view error_message) {
+ error_code_ = error_code;
+ error_message_ = absl::StrCat("Error decoding metadata: ", error_message);
+}
+
+} // namespace quic
diff --git a/quiche/quic/core/http/metadata_decoder.h b/quiche/quic/core/http/metadata_decoder.h
new file mode 100644
index 0000000..ec33c17
--- /dev/null
+++ b/quiche/quic/core/http/metadata_decoder.h
@@ -0,0 +1,73 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_HTTP_METADATA_DECODER_H_
+#define QUICHE_QUIC_CORE_HTTP_METADATA_DECODER_H_
+
+#include <sys/types.h>
+
+#include <cstddef>
+#include <memory>
+#include <string>
+
+#include "quiche/quic/core/http/quic_header_list.h"
+#include "quiche/quic/core/qpack/qpack_decoded_headers_accumulator.h"
+#include "quiche/quic/core/qpack/qpack_decoder.h"
+#include "quiche/quic/core/quic_error_codes.h"
+
+namespace quic {
+
+// Class for decoding the payload of HTTP/3 METADATA frames.
+class QUICHE_EXPORT MetadataDecoder {
+ public:
+ MetadataDecoder(QuicStreamId id, size_t max_header_list_size,
+ size_t frame_header_len, size_t payload_length);
+
+ // Incrementally decodes the next bytes of METADATA frame payload
+ // and returns true if there were no errors.
+ bool Decode(absl::string_view payload);
+
+ // Finishes the decoding. Must be called after the full frame payload
+ // has been decoded. Returns true if there were no errors.
+ bool EndHeaderBlock();
+
+ const std::string& error_message() { return decoder_.error_message(); }
+ size_t frame_len() { return frame_len_; }
+ const QuicHeaderList& headers() { return decoder_.headers(); }
+
+ private:
+ class MetadataHeadersDecoder
+ : public QpackDecodedHeadersAccumulator::Visitor {
+ public:
+ // QpackDecodedHeadersAccumulator::Visitor
+ void OnHeadersDecoded(QuicHeaderList headers,
+ bool header_list_size_limit_exceeded) override;
+ void OnHeaderDecodingError(QuicErrorCode error_code,
+ absl::string_view error_message) override;
+
+ QuicErrorCode error_code() { return error_code_; }
+ const std::string& error_message() { return error_message_; }
+ QuicHeaderList& headers() { return headers_; }
+ bool header_list_size_limit_exceeded() {
+ return header_list_size_limit_exceeded_;
+ }
+
+ private:
+ QuicErrorCode error_code_ = QUIC_NO_ERROR;
+ QuicHeaderList headers_;
+ std::string error_message_;
+ bool header_list_size_limit_exceeded_ = false;
+ };
+
+ NoopEncoderStreamErrorDelegate delegate_;
+ QpackDecoder qpack_decoder_;
+ MetadataHeadersDecoder decoder_;
+ QpackDecodedHeadersAccumulator accumulator_;
+ const size_t frame_len_;
+ size_t bytes_remaining_ = 0; // Debug only.
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_HTTP_METADATA_DECODER_H_
diff --git a/quiche/quic/core/http/metadata_decoder_test.cc b/quiche/quic/core/http/metadata_decoder_test.cc
new file mode 100644
index 0000000..791a84d
--- /dev/null
+++ b/quiche/quic/core/http/metadata_decoder_test.cc
@@ -0,0 +1,83 @@
+// Copyright 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/http/metadata_decoder.h"
+
+#include "absl/strings/escaping.h"
+#include "quiche/quic/core/qpack/qpack_encoder.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+class MetadataDecoderTest : public QuicTest {
+ protected:
+ std::string EncodeHeaders(spdy::Http2HeaderBlock& headers) {
+ quic::NoopDecoderStreamErrorDelegate delegate;
+ quic::QpackEncoder encoder(&delegate, quic::HuffmanEncoding::kDisabled);
+ return encoder.EncodeHeaderList(id_, headers,
+ /*encoder_stream_sent_byte_count=*/nullptr);
+ }
+
+ size_t max_header_list_size = 1 << 20; // 1 MB
+ const QuicStreamId id_ = 1;
+};
+
+TEST_F(MetadataDecoderTest, Initialize) {
+ const size_t frame_header_len = 4;
+ const size_t payload_len = 123;
+ MetadataDecoder decoder(id_, max_header_list_size, frame_header_len,
+ payload_len);
+ EXPECT_EQ(frame_header_len + payload_len, decoder.frame_len());
+ EXPECT_EQ("", decoder.error_message());
+ EXPECT_TRUE(decoder.headers().empty());
+}
+
+TEST_F(MetadataDecoderTest, Decode) {
+ spdy::Http2HeaderBlock headers;
+ headers["key1"] = "val1";
+ headers["key2"] = "val2";
+ headers["key3"] = "val3";
+ std::string data = EncodeHeaders(headers);
+
+ const size_t frame_header_len = 4;
+ MetadataDecoder decoder(id_, max_header_list_size, frame_header_len,
+ data.length());
+ EXPECT_TRUE(decoder.Decode(data));
+ EXPECT_TRUE(decoder.EndHeaderBlock());
+ EXPECT_EQ(quic::test::AsHeaderList(headers), decoder.headers());
+}
+
+TEST_F(MetadataDecoderTest, DecodeInvalidHeaders) {
+ std::string data = "aaaaaaaaaa";
+
+ const size_t frame_header_len = 4;
+ MetadataDecoder decoder(id_, max_header_list_size, frame_header_len,
+ data.length());
+ EXPECT_FALSE(decoder.Decode(data));
+ EXPECT_EQ("Error decoding metadata: Error decoding Required Insert Count.",
+ decoder.error_message());
+}
+
+TEST_F(MetadataDecoderTest, TooLarge) {
+ spdy::Http2HeaderBlock headers;
+ for (int i = 0; i < 1024; ++i) {
+ headers.AppendValueOrAddHeader(absl::StrCat(i), std::string(1024, 'a'));
+ }
+ std::string data = EncodeHeaders(headers);
+
+ EXPECT_GT(data.length(), 1 << 20);
+ const size_t frame_header_len = 4;
+ MetadataDecoder decoder(id_, max_header_list_size, frame_header_len,
+ data.length());
+ EXPECT_TRUE(decoder.Decode(data));
+ EXPECT_FALSE(decoder.EndHeaderBlock());
+ EXPECT_TRUE(decoder.error_message().empty());
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc
index 4a5c2ed..035d21f 100644
--- a/quiche/quic/core/http/quic_spdy_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -1207,11 +1207,11 @@
static_cast<uint64_t>(quic::HttpFrameType::METADATA), header_length,
payload_length);
}
- QUIC_BUG_IF(Invalid METADATA state, received_metadata_payload_ != nullptr)
- << "Invalid metadata state";
- received_metadata_payload_ =
- std::make_unique<ReceivedMetadataPayload>(payload_length);
- received_metadata_payload_->frame_len = header_length + payload_length;
+
+ QUIC_BUG_IF(Invalid METADATA state, metadata_decoder_ != nullptr);
+ constexpr size_t kMaxMetadataBlockSize = 1 << 20; // 1 MB
+ metadata_decoder_ = std::make_unique<MetadataDecoder>(
+ id(), kMaxMetadataBlockSize, header_length, payload_length);
// Consume the frame header.
QUIC_DVLOG(1) << ENDPOINT << "Consuming " << header_length
@@ -1224,8 +1224,12 @@
if (metadata_visitor_ == nullptr) {
return OnUnknownFramePayload(payload);
}
- received_metadata_payload_->buffer.push_back(std::string(payload));
- received_metadata_payload_->bytes_remaining -= payload.size();
+
+ if (!metadata_decoder_->Decode(payload)) {
+ OnUnrecoverableError(QUIC_DECOMPRESSION_FAILURE,
+ metadata_decoder_->error_message());
+ return false;
+ }
// Consume the frame payload.
QUIC_DVLOG(1) << ENDPOINT << "Consuming " << payload.size()
@@ -1233,66 +1237,21 @@
sequencer()->MarkConsumed(body_manager_.OnNonBody(payload.size()));
return true;
}
-namespace {
-class MetadataHeadersDecoder : public QpackDecodedHeadersAccumulator::Visitor {
- public:
- void OnHeadersDecoded(QuicHeaderList headers,
- bool header_list_size_limit_exceeded) override {
- header_list_size_limit_exceeded_ = header_list_size_limit_exceeded;
- headers_ = std::move(headers);
- }
-
- void OnHeaderDecodingError(QuicErrorCode error_code,
- absl::string_view error_message) override {
- error_code_ = error_code;
- error_message_ = absl::StrCat("Error decoding metadata: ", error_message);
- }
-
- QuicErrorCode error_code() { return error_code_; }
- std::string error_message() { return error_message_; }
- QuicHeaderList& headers() { return headers_; }
- bool header_list_size_limit_exceeded() {
- return header_list_size_limit_exceeded_;
- }
-
- private:
- QuicErrorCode error_code_ = QUIC_NO_ERROR;
- QuicHeaderList headers_;
- std::string error_message_;
- bool header_list_size_limit_exceeded_ = false;
-};
-} // namespace
bool QuicSpdyStream::OnMetadataFrameEnd() {
if (metadata_visitor_ == nullptr) {
return OnUnknownFrameEnd();
}
- QUIC_BUG_IF(METADATA bytes remaining,
- received_metadata_payload_->bytes_remaining != 0)
- << "More metadata remaining: "
- << received_metadata_payload_->bytes_remaining;
- quic::NoopEncoderStreamErrorDelegate delegate;
- quic::QpackDecoder qpack_decoder(/*maximum_dynamic_table_capacity=*/0,
- /*maximum_blocked_streams=*/0, &delegate);
-
- constexpr size_t kMaxMetadataBlockSize = 1 << 20; // 1 MB
- MetadataHeadersDecoder decoder;
- quic::QpackDecodedHeadersAccumulator accumulator(
- id(), &qpack_decoder, &decoder, kMaxMetadataBlockSize);
- for (const std::string& slice : received_metadata_payload_->buffer) {
- accumulator.Decode(slice);
- if (decoder.error_code() != QUIC_NO_ERROR) {
- OnUnrecoverableError(QUIC_DECOMPRESSION_FAILURE, decoder.error_message());
- return false;
- }
+ if (!metadata_decoder_->EndHeaderBlock()) {
+ OnUnrecoverableError(QUIC_DECOMPRESSION_FAILURE,
+ metadata_decoder_->error_message());
+ return false;
}
- accumulator.EndHeaderBlock();
-
- metadata_visitor_->OnMetadataComplete(received_metadata_payload_->frame_len,
- decoder.headers());
- received_metadata_payload_.reset();
+ metadata_visitor_->OnMetadataComplete(metadata_decoder_->frame_len(),
+ metadata_decoder_->headers());
+ metadata_decoder_.reset();
return !sequencer()->IsClosed() && !reading_stopped();
}
diff --git a/quiche/quic/core/http/quic_spdy_stream.h b/quiche/quic/core/http/quic_spdy_stream.h
index 10c34b1..d33abed 100644
--- a/quiche/quic/core/http/quic_spdy_stream.h
+++ b/quiche/quic/core/http/quic_spdy_stream.h
@@ -21,6 +21,7 @@
#include "absl/types/span.h"
#include "quiche/quic/core/http/http_decoder.h"
#include "quiche/quic/core/http/http_encoder.h"
+#include "quiche/quic/core/http/metadata_decoder.h"
#include "quiche/quic/core/http/quic_header_list.h"
#include "quiche/quic/core/http/quic_spdy_stream_body_manager.h"
#include "quiche/quic/core/http/web_transport_stream_adapter.h"
@@ -522,16 +523,9 @@
// Present if HTTP/3 METADATA frames should be parsed.
MetadataVisitor* metadata_visitor_ = nullptr;
- struct ReceivedMetadataPayload {
- explicit ReceivedMetadataPayload(size_t remaining)
- : bytes_remaining(remaining) {}
- std::list<std::string> buffer;
- size_t frame_len = 0;
- size_t bytes_remaining = 0;
- };
// Present if an HTTP/3 METADATA is currently being parsed.
- std::unique_ptr<ReceivedMetadataPayload> received_metadata_payload_;
+ std::unique_ptr<MetadataDecoder> metadata_decoder_;
// Empty if the headers are valid.
std::string invalid_request_details_;