Introduce a debugging utility for printing HTTP/2 logical frames from wire bytes.

This utility class, Http2TracePrinter, may be useful for a variety of
scenarios, including easier reproduction of test failures.

The printer appears to be working based on a local test run:
http://screen/5DDNEMaPFzpnJwQ. (Also with connection preface error:
http://screen/9XFCtvqMUUoYRMa.)

PiperOrigin-RevId: 566429201
diff --git a/build/source_list.bzl b/build/source_list.bzl
index b008e48..05fd184 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -763,6 +763,7 @@
     "http2/test_tools/http2_random.h",
     "http2/test_tools/http2_structure_decoder_test_util.h",
     "http2/test_tools/http2_structures_test_util.h",
+    "http2/test_tools/http2_trace_printer.h",
     "http2/test_tools/payload_decoder_base_test_util.h",
     "http2/test_tools/random_decoder_test_base.h",
     "http2/test_tools/random_util.h",
@@ -869,6 +870,7 @@
     "http2/test_tools/http2_random.cc",
     "http2/test_tools/http2_structure_decoder_test_util.cc",
     "http2/test_tools/http2_structures_test_util.cc",
+    "http2/test_tools/http2_trace_printer.cc",
     "http2/test_tools/payload_decoder_base_test_util.cc",
     "http2/test_tools/random_decoder_test_base.cc",
     "http2/test_tools/random_util.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 23b3066..a788a47 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -763,6 +763,7 @@
     "src/quiche/http2/test_tools/http2_random.h",
     "src/quiche/http2/test_tools/http2_structure_decoder_test_util.h",
     "src/quiche/http2/test_tools/http2_structures_test_util.h",
+    "src/quiche/http2/test_tools/http2_trace_printer.h",
     "src/quiche/http2/test_tools/payload_decoder_base_test_util.h",
     "src/quiche/http2/test_tools/random_decoder_test_base.h",
     "src/quiche/http2/test_tools/random_util.h",
@@ -869,6 +870,7 @@
     "src/quiche/http2/test_tools/http2_random.cc",
     "src/quiche/http2/test_tools/http2_structure_decoder_test_util.cc",
     "src/quiche/http2/test_tools/http2_structures_test_util.cc",
+    "src/quiche/http2/test_tools/http2_trace_printer.cc",
     "src/quiche/http2/test_tools/payload_decoder_base_test_util.cc",
     "src/quiche/http2/test_tools/random_decoder_test_base.cc",
     "src/quiche/http2/test_tools/random_util.cc",
diff --git a/build/source_list.json b/build/source_list.json
index ef16ce6..e3c9efd 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -762,6 +762,7 @@
     "quiche/http2/test_tools/http2_random.h",
     "quiche/http2/test_tools/http2_structure_decoder_test_util.h",
     "quiche/http2/test_tools/http2_structures_test_util.h",
+    "quiche/http2/test_tools/http2_trace_printer.h",
     "quiche/http2/test_tools/payload_decoder_base_test_util.h",
     "quiche/http2/test_tools/random_decoder_test_base.h",
     "quiche/http2/test_tools/random_util.h",
@@ -868,6 +869,7 @@
     "quiche/http2/test_tools/http2_random.cc",
     "quiche/http2/test_tools/http2_structure_decoder_test_util.cc",
     "quiche/http2/test_tools/http2_structures_test_util.cc",
+    "quiche/http2/test_tools/http2_trace_printer.cc",
     "quiche/http2/test_tools/payload_decoder_base_test_util.cc",
     "quiche/http2/test_tools/random_decoder_test_base.cc",
     "quiche/http2/test_tools/random_util.cc",
diff --git a/quiche/http2/test_tools/http2_trace_printer.cc b/quiche/http2/test_tools/http2_trace_printer.cc
new file mode 100644
index 0000000..682f91b
--- /dev/null
+++ b/quiche/http2/test_tools/http2_trace_printer.cc
@@ -0,0 +1,61 @@
+#include "quiche/http2/test_tools/http2_trace_printer.h"
+
+#include <algorithm>
+#include <cstddef>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "quiche/http2/core/http2_trace_logging.h"
+#include "quiche/spdy/core/spdy_protocol.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+bool IsLoggingEnabled() { return true; }
+
+}  // namespace
+
+Http2TracePrinter::Http2TracePrinter(absl::string_view perspective,
+                                     const void* connection_id,
+                                     bool consume_connection_preface)
+    : logger_(&visitor_, perspective, IsLoggingEnabled, connection_id),
+      perspective_(perspective) {
+  decoder_.set_visitor(&logger_);
+  if (consume_connection_preface) {
+    remaining_preface_ =
+        absl::string_view(spdy::kHttp2ConnectionHeaderPrefix,
+                          spdy::kHttp2ConnectionHeaderPrefixSize);
+  }
+}
+
+void Http2TracePrinter::ProcessInput(absl::string_view bytes) {
+  if (preface_error_) {
+    HTTP2_TRACE_LOG(perspective_, IsLoggingEnabled)
+        << "Earlier connection preface error, ignoring " << bytes.size()
+        << " bytes";
+    return;
+  }
+  if (!remaining_preface_.empty()) {
+    const size_t consumed = std::min(remaining_preface_.size(), bytes.size());
+
+    const absl::string_view preface = bytes.substr(0, consumed);
+    HTTP2_TRACE_LOG(perspective_, IsLoggingEnabled)
+        << "Received connection preface: " << absl::CEscape(preface);
+
+    if (!absl::StartsWith(remaining_preface_, preface)) {
+      HTTP2_TRACE_LOG(perspective_, IsLoggingEnabled)
+          << "Received preface does not match expected remaining preface: "
+          << absl::CEscape(remaining_preface_);
+      preface_error_ = true;
+      return;
+    }
+    bytes.remove_prefix(consumed);
+    remaining_preface_.remove_prefix(consumed);
+  }
+  decoder_.ProcessInput(bytes.data(), bytes.size());
+}
+
+}  // namespace test
+}  // namespace http2
diff --git a/quiche/http2/test_tools/http2_trace_printer.h b/quiche/http2/test_tools/http2_trace_printer.h
new file mode 100644
index 0000000..b965ad8
--- /dev/null
+++ b/quiche/http2/test_tools/http2_trace_printer.h
@@ -0,0 +1,48 @@
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HTTP2_TRACE_PRINTER_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_TRACE_PRINTER_H_
+
+#include <cstddef>
+
+#include "absl/strings/string_view.h"
+#include "quiche/http2/core/http2_trace_logging.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/spdy/core/http2_frame_decoder_adapter.h"
+#include "quiche/spdy/core/spdy_no_op_visitor.h"
+
+namespace http2 {
+namespace test {
+
+// A debugging utility that prints HTTP/2 wire bytes into logical HTTP/2 frame
+// sequences using `Http2TraceLogger`.
+class QUICHE_NO_EXPORT Http2TracePrinter {
+ public:
+  // Creates a printer with the given `perspective` prefixed with each log line
+  // (e.g., "CLIENT" or "SERVER"). The given `connection_id` is also included
+  // with each log line and distinguishes among multiple printed connections
+  // with the same `perspective`. If `consume_connection_preface` is true, the
+  // printer will attempt to consume and log the HTTP/2 client connection
+  // preface from the wire bytes.
+  explicit Http2TracePrinter(absl::string_view perspective,
+                             const void* connection_id = nullptr,
+                             bool consume_connection_preface = false);
+
+  // Processes the `bytes` as HTTP/2 wire format and INFO logs the received
+  // frames. See `Http2TraceLogger` for more details on the logging format. If
+  // `consume_connection_preface` was passed as true to the constructor, then
+  // errors in processing the connection preface will be logged and subsequent
+  // calls to `ProcessInput()` will be a no-op.
+  void ProcessInput(absl::string_view bytes);
+
+ private:
+  spdy::SpdyNoOpVisitor visitor_;
+  Http2TraceLogger logger_;
+  Http2DecoderAdapter decoder_;
+  const absl::string_view perspective_;
+  absl::string_view remaining_preface_;
+  bool preface_error_ = false;
+};
+
+}  // namespace test
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_TEST_TOOLS_HTTP2_TRACE_PRINTER_H_