Refactor qpack_round_trip_fuzzer.cc.
gfe-relnote: n/a, fuzzer-only
PiperOrigin-RevId: 262610064
Change-Id: I0dd5f53afeb7f5ba2a38dd754cbbc76f39b79c67
diff --git a/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
index a9e9a0e..391c20e 100644
--- a/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
+++ b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <string>
@@ -16,27 +17,18 @@
namespace quic {
namespace test {
-// This fuzzer exercises QpackEncoder and QpackDecoder. It should be able to
-// cover all possible code paths of QpackEncoder. However, since the resulting
-// header block is always valid and is encoded in a particular way, this fuzzer
-// is not expected to cover all code paths of QpackDecoder. On the other hand,
-// encoding then decoding is expected to result in the original header list, and
-// this fuzzer checks for that.
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
- QuicFuzzedDataProvider provider(data, size);
-
- // Build test header list.
+spdy::SpdyHeaderBlock GenerateHeaderList(QuicFuzzedDataProvider* provider) {
spdy::SpdyHeaderBlock header_list;
- uint8_t header_count = provider.ConsumeIntegral<uint8_t>();
+ uint8_t header_count = provider->ConsumeIntegral<uint8_t>();
for (uint8_t header_index = 0; header_index < header_count; ++header_index) {
- if (provider.remaining_bytes() == 0) {
+ if (provider->remaining_bytes() == 0) {
// Do not add more headers if there is no more fuzzer data.
break;
}
std::string name;
std::string value;
- switch (provider.ConsumeIntegral<uint8_t>()) {
+ switch (provider->ConsumeIntegral<uint8_t>()) {
case 0:
// Static table entry with no header value.
name = ":authority";
@@ -92,7 +84,7 @@
case 10:
// Header name not in the static table, fuzzed header value.
name = "foo";
- value = provider.ConsumeRandomLengthString(128);
+ value = provider->ConsumeRandomLengthString(128);
break;
case 11:
// Another header name not in the static table, empty header value.
@@ -108,56 +100,107 @@
case 13:
// Another header name not in the static table, fuzzed header value.
name = "bar";
- value = provider.ConsumeRandomLengthString(128);
+ value = provider->ConsumeRandomLengthString(128);
break;
default:
// Fuzzed header name and header value.
- name = provider.ConsumeRandomLengthString(128);
- value = provider.ConsumeRandomLengthString(128);
+ name = provider->ConsumeRandomLengthString(128);
+ value = provider->ConsumeRandomLengthString(128);
}
header_list.AppendValueOrAddHeader(name, value);
}
+ return header_list;
+}
+
+spdy::SpdyHeaderBlock DecodeHeaderBlock(QpackDecoder* decoder,
+ const std::string& encoded_header_block,
+ QuicFuzzedDataProvider* provider) {
// Process up to 64 kB fragments at a time. Too small upper bound might not
// provide enough coverage, too large would make fuzzing less efficient.
auto fragment_size_generator =
std::bind(&QuicFuzzedDataProvider::ConsumeIntegralInRange<uint16_t>,
- &provider, 1, std::numeric_limits<uint16_t>::max());
+ provider, 1, std::numeric_limits<uint16_t>::max());
- // Encode header list.
- NoopDecoderStreamErrorDelegate decoder_stream_error_delegate;
- NoopQpackStreamSenderDelegate encoder_stream_sender_delegate;
- QpackEncoder encoder(&decoder_stream_error_delegate);
- encoder.set_qpack_stream_sender_delegate(&encoder_stream_sender_delegate);
- std::string encoded_header_block =
- encoder.EncodeHeaderList(/* stream_id = */ 1, &header_list);
-
- // Decode header block.
TestHeadersHandler handler;
- NoopEncoderStreamErrorDelegate encoder_stream_error_delegate;
- NoopQpackStreamSenderDelegate decoder_stream_sender_delegate;
- // TODO(b/112770235): Fuzz dynamic table and blocked streams.
- QpackDecode(
- /* maximum_dynamic_table_capacity = */ 0,
- /* maximum_blocked_streams = */ 0, &encoder_stream_error_delegate,
- &decoder_stream_sender_delegate, &handler, fragment_size_generator,
- encoded_header_block);
+ auto progressive_decoder =
+ decoder->CreateProgressiveDecoder(/* stream_id = */ 1, &handler);
+ {
+ QuicStringPiece remaining_data = encoded_header_block;
+ while (!remaining_data.empty()) {
+ size_t fragment_size =
+ std::min<size_t>(fragment_size_generator(), remaining_data.size());
+ progressive_decoder->Decode(remaining_data.substr(0, fragment_size));
+ remaining_data = remaining_data.substr(fragment_size);
+ }
+ }
+ progressive_decoder->EndHeaderBlock();
// Since header block has been produced by encoding a header list, it must be
// valid.
CHECK(handler.decoding_completed());
CHECK(!handler.decoding_error_detected());
+ return handler.ReleaseHeaderList();
+}
+
+// Splits |*header_list| header values along '\0' or ';' separators.
+spdy::SpdyHeaderBlock SplitHeaderList(
+ const spdy::SpdyHeaderBlock& header_list) {
+ ValueSplittingHeaderList splitting_header_list(&header_list);
+ spdy::SpdyHeaderBlock split_header_list;
+ for (const auto& header : splitting_header_list) {
+ split_header_list.AppendValueOrAddHeader(header.first, header.second);
+ }
+ return split_header_list;
+}
+
+// This fuzzer exercises QpackEncoder and QpackDecoder. It should be able to
+// cover all possible code paths of QpackEncoder. However, since the resulting
+// header block is always valid and is encoded in a particular way, this fuzzer
+// is not expected to cover all code paths of QpackDecoder. On the other hand,
+// encoding then decoding is expected to result in the original header list, and
+// this fuzzer checks for that.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ QuicFuzzedDataProvider provider(data, size);
+
+ // TODO(b/112770235): Fuzz dynamic table and blocked streams.
+ uint64_t maximum_dynamic_table_capacity = 0;
+ uint64_t maximum_blocked_streams = 0;
+
+ // Set up encoder.
+ // TODO: crash on decoder stream error
+ NoopDecoderStreamErrorDelegate decoder_stream_error_delegate;
+ NoopQpackStreamSenderDelegate encoder_stream_sender_delegate;
+ QpackEncoder encoder(&decoder_stream_error_delegate);
+ encoder.set_qpack_stream_sender_delegate(&encoder_stream_sender_delegate);
+
+ // Set up decoder.
+ // TODO: crash on encoder stream error
+ NoopEncoderStreamErrorDelegate encoder_stream_error_delegate;
+ NoopQpackStreamSenderDelegate decoder_stream_sender_delegate;
+ QpackDecoder decoder(maximum_dynamic_table_capacity, maximum_blocked_streams,
+ &encoder_stream_error_delegate);
+ decoder.set_qpack_stream_sender_delegate(&decoder_stream_sender_delegate);
+
+ // Generate header list.
+ spdy::SpdyHeaderBlock header_list = GenerateHeaderList(&provider);
+
+ // Encode header list.
+ std::string encoded_header_block =
+ encoder.EncodeHeaderList(/* stream_id = */ 1, &header_list);
+
+ // Decode resulting header block.
+ spdy::SpdyHeaderBlock decoded_header_list =
+ DecodeHeaderBlock(&decoder, encoded_header_block, &provider);
+
// Encoder splits |header_list| header values along '\0' or ';' separators.
// Do the same here so that we get matching results.
- ValueSplittingHeaderList splitting_header_list(&header_list);
- spdy::SpdyHeaderBlock expected_header_list;
- for (const auto& header : splitting_header_list) {
- expected_header_list.AppendValueOrAddHeader(header.first, header.second);
- }
+ spdy::SpdyHeaderBlock expected_header_list = SplitHeaderList(header_list);
+
// Compare resulting header list to original.
- CHECK(expected_header_list == handler.ReleaseHeaderList());
+ CHECK(expected_header_list == decoded_header_list);
return 0;
}