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