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