Adds a visitor implementation that translates from Http2VisitorInterface events back to nghttp2-style callback events.

A program that uses the nghttp2 library can reuse existing callbacks with this visitor and an implementation of Http2Adapter. Note that send-side callbacks are still not hooked up quite yet. This only handles the receive side of things.

PiperOrigin-RevId: 372405228
Change-Id: I01ee061479efbf7da8720ecdbd72767db329cf05
diff --git a/http2/adapter/callback_visitor.cc b/http2/adapter/callback_visitor.cc
new file mode 100644
index 0000000..eaf4b61
--- /dev/null
+++ b/http2/adapter/callback_visitor.cc
@@ -0,0 +1,215 @@
+#include "http2/adapter/callback_visitor.h"
+
+#include "http2/adapter/nghttp2_util.h"
+#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
+#include "common/quiche_endian.h"
+
+// This visitor implementation needs visibility into the
+// nghttp2_session_callbacks type. There's no public header, so we'll redefine
+// the struct here.
+struct nghttp2_session_callbacks {
+  nghttp2_send_callback send_callback;
+  nghttp2_recv_callback recv_callback;
+  nghttp2_on_frame_recv_callback on_frame_recv_callback;
+  nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback;
+  nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback;
+  nghttp2_before_frame_send_callback before_frame_send_callback;
+  nghttp2_on_frame_send_callback on_frame_send_callback;
+  nghttp2_on_frame_not_send_callback on_frame_not_send_callback;
+  nghttp2_on_stream_close_callback on_stream_close_callback;
+  nghttp2_on_begin_headers_callback on_begin_headers_callback;
+  nghttp2_on_header_callback on_header_callback;
+  nghttp2_on_header_callback2 on_header_callback2;
+  nghttp2_on_invalid_header_callback on_invalid_header_callback;
+  nghttp2_on_invalid_header_callback2 on_invalid_header_callback2;
+  nghttp2_select_padding_callback select_padding_callback;
+  nghttp2_data_source_read_length_callback read_length_callback;
+  nghttp2_on_begin_frame_callback on_begin_frame_callback;
+  nghttp2_send_data_callback send_data_callback;
+  nghttp2_pack_extension_callback pack_extension_callback;
+  nghttp2_unpack_extension_callback unpack_extension_callback;
+  nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback;
+  nghttp2_error_callback error_callback;
+  nghttp2_error_callback2 error_callback2;
+};
+
+namespace http2 {
+namespace adapter {
+
+void CallbackVisitor::OnConnectionError() {
+  QUICHE_LOG(FATAL) << "Not implemented";
+}
+
+void CallbackVisitor::OnFrameHeader(Http2StreamId stream_id,
+                                    size_t length,
+                                    uint8_t type,
+                                    uint8_t flags) {
+  // The general strategy is to clear |current_frame_| at the start of a new
+  // frame, accumulate frame information from the various callback events, then
+  // invoke the on_frame_recv_callback() with the accumulated frame data.
+  memset(&current_frame_, 0, sizeof(current_frame_));
+  current_frame_.hd.stream_id = stream_id;
+  current_frame_.hd.length = length;
+  current_frame_.hd.type = type;
+  current_frame_.hd.flags = flags;
+  callbacks_->on_begin_frame_callback(nullptr, &current_frame_.hd, user_data_);
+}
+
+void CallbackVisitor::OnSettingsStart() {}
+
+void CallbackVisitor::OnSetting(Http2Setting setting) {
+  settings_.push_back({.settings_id = setting.id, .value = setting.value});
+}
+
+void CallbackVisitor::OnSettingsEnd() {
+  current_frame_.settings.niv = settings_.size();
+  current_frame_.settings.iv = settings_.data();
+  callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+  settings_.clear();
+}
+
+void CallbackVisitor::OnSettingsAck() {
+  // ACK is part of the flags, which were set in OnFrameHeader().
+  callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+}
+
+void CallbackVisitor::OnBeginHeadersForStream(Http2StreamId stream_id) {
+  auto it = stream_map_.find(stream_id);
+  if (it == stream_map_.end()) {
+    auto p = stream_map_.insert({stream_id, absl::make_unique<StreamInfo>()});
+    it = p.first;
+  }
+  if (it->second->received_headers) {
+    // At least one headers frame has already been received.
+    current_frame_.headers.cat = NGHTTP2_HCAT_HEADERS;
+  } else {
+    switch (perspective_) {
+      case Perspective::kClient:
+        current_frame_.headers.cat = NGHTTP2_HCAT_RESPONSE;
+        break;
+      case Perspective::kServer:
+        current_frame_.headers.cat = NGHTTP2_HCAT_REQUEST;
+        break;
+    }
+  }
+  callbacks_->on_begin_headers_callback(nullptr, &current_frame_, user_data_);
+  it->second->received_headers = true;
+}
+
+void CallbackVisitor::OnHeaderForStream(Http2StreamId stream_id,
+                                        absl::string_view name,
+                                        absl::string_view value) {
+  callbacks_->on_header_callback(
+      nullptr, &current_frame_, ToUint8Ptr(name.data()), name.size(),
+      ToUint8Ptr(value.data()), value.size(), NGHTTP2_NV_FLAG_NONE, user_data_);
+}
+
+void CallbackVisitor::OnEndHeadersForStream(Http2StreamId stream_id) {
+  callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+}
+
+void CallbackVisitor::OnBeginDataForStream(Http2StreamId stream_id,
+                                           size_t payload_length) {
+  // TODO(b/181586191): Interpret padding, subtract padding from
+  // |remaining_data_|.
+  remaining_data_ = payload_length;
+  if (remaining_data_ == 0) {
+    callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+  }
+}
+
+void CallbackVisitor::OnDataForStream(Http2StreamId stream_id,
+                                      absl::string_view data) {
+  callbacks_->on_data_chunk_recv_callback(nullptr, current_frame_.hd.flags,
+                                          stream_id, ToUint8Ptr(data.data()),
+                                          data.size(), user_data_);
+  remaining_data_ -= data.size();
+  if (remaining_data_ == 0) {
+    callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+  }
+}
+
+void CallbackVisitor::OnEndStream(Http2StreamId stream_id) {}
+
+void CallbackVisitor::OnRstStream(Http2StreamId stream_id,
+                                  Http2ErrorCode error_code) {
+  current_frame_.rst_stream.error_code = static_cast<uint32_t>(error_code);
+  callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+}
+
+void CallbackVisitor::OnCloseStream(Http2StreamId stream_id,
+                                    Http2ErrorCode error_code) {
+  callbacks_->on_stream_close_callback(
+      nullptr, stream_id, static_cast<uint32_t>(error_code), user_data_);
+}
+
+void CallbackVisitor::OnPriorityForStream(Http2StreamId stream_id,
+                                          Http2StreamId parent_stream_id,
+                                          int weight,
+                                          bool exclusive) {
+  current_frame_.priority.pri_spec.stream_id = parent_stream_id;
+  current_frame_.priority.pri_spec.weight = weight;
+  current_frame_.priority.pri_spec.exclusive = exclusive;
+  callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+}
+
+void CallbackVisitor::OnPing(Http2PingId ping_id, bool is_ack) {
+  uint64_t network_order_opaque_data =
+      quiche::QuicheEndian::HostToNet64(ping_id);
+  std::memcpy(current_frame_.ping.opaque_data, &network_order_opaque_data,
+              sizeof(network_order_opaque_data));
+  callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+}
+
+void CallbackVisitor::OnPushPromiseForStream(Http2StreamId stream_id,
+                                             Http2StreamId promised_stream_id) {
+  QUICHE_LOG(FATAL) << "Not implemented";
+}
+
+void CallbackVisitor::OnGoAway(Http2StreamId last_accepted_stream_id,
+                               Http2ErrorCode error_code,
+                               absl::string_view opaque_data) {
+  current_frame_.goaway.last_stream_id = last_accepted_stream_id;
+  current_frame_.goaway.error_code = static_cast<uint32_t>(error_code);
+  current_frame_.goaway.opaque_data = ToUint8Ptr(opaque_data.data());
+  current_frame_.goaway.opaque_data_len = opaque_data.size();
+  callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+}
+
+void CallbackVisitor::OnWindowUpdate(Http2StreamId stream_id,
+                                     int window_increment) {
+  current_frame_.window_update.window_size_increment = window_increment;
+  callbacks_->on_frame_recv_callback(nullptr, &current_frame_, user_data_);
+}
+
+void CallbackVisitor::OnReadyToSendDataForStream(Http2StreamId stream_id,
+                                                 char* destination_buffer,
+                                                 size_t length,
+                                                 ssize_t* written,
+                                                 bool* end_stream) {
+  QUICHE_LOG(FATAL) << "Not implemented";
+}
+
+void CallbackVisitor::OnReadyToSendMetadataForStream(Http2StreamId stream_id,
+                                                     char* buffer,
+                                                     size_t length,
+                                                     ssize_t* written) {
+  QUICHE_LOG(FATAL) << "Not implemented";
+}
+
+void CallbackVisitor::OnBeginMetadataForStream(Http2StreamId stream_id,
+                                               size_t payload_length) {
+  QUICHE_LOG(FATAL) << "Not implemented";
+}
+
+void CallbackVisitor::OnMetadataForStream(Http2StreamId stream_id,
+                                          absl::string_view metadata) {
+  QUICHE_LOG(FATAL) << "Not implemented";
+}
+
+void CallbackVisitor::OnMetadataEndForStream(Http2StreamId stream_id) {
+  QUICHE_LOG(FATAL) << "Not implemented";
+}
+
+}  // namespace adapter
+}  // namespace http2
diff --git a/http2/adapter/callback_visitor.h b/http2/adapter/callback_visitor.h
new file mode 100644
index 0000000..94746bf
--- /dev/null
+++ b/http2/adapter/callback_visitor.h
@@ -0,0 +1,92 @@
+#ifndef QUICHE_HTTP2_ADAPTER_CALLBACK_VISITOR_H_
+#define QUICHE_HTTP2_ADAPTER_CALLBACK_VISITOR_H_
+
+#include <memory>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "http2/adapter/http2_visitor_interface.h"
+#include "http2/adapter/nghttp2_util.h"
+#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
+
+namespace http2 {
+namespace adapter {
+
+// This visitor implementation accepts a set of nghttp2 callbacks and a "user
+// data" pointer, and invokes the callbacks according to HTTP/2 events received.
+class CallbackVisitor : public Http2VisitorInterface {
+ public:
+  explicit CallbackVisitor(Perspective perspective,
+                           nghttp2_session_callbacks_unique_ptr callbacks,
+                           void* user_data)
+      : perspective_(perspective),
+        callbacks_(std::move(callbacks)),
+        user_data_(user_data) {}
+
+  void OnConnectionError() override;
+  void OnFrameHeader(Http2StreamId stream_id,
+                     size_t length,
+                     uint8_t type,
+                     uint8_t flags) override;
+  void OnSettingsStart() override;
+  void OnSetting(Http2Setting setting) override;
+  void OnSettingsEnd() override;
+  void OnSettingsAck() override;
+  void OnBeginHeadersForStream(Http2StreamId stream_id) override;
+  void OnHeaderForStream(Http2StreamId stream_id,
+                         absl::string_view name,
+                         absl::string_view value) override;
+  void OnEndHeadersForStream(Http2StreamId stream_id) override;
+  void OnBeginDataForStream(Http2StreamId stream_id,
+                            size_t payload_length) override;
+  void OnDataForStream(Http2StreamId stream_id,
+                       absl::string_view data) override;
+  void OnEndStream(Http2StreamId stream_id) override;
+  void OnRstStream(Http2StreamId stream_id, Http2ErrorCode error_code) override;
+  void OnCloseStream(Http2StreamId stream_id,
+                     Http2ErrorCode error_code) override;
+  void OnPriorityForStream(Http2StreamId stream_id,
+                           Http2StreamId parent_stream_id,
+                           int weight,
+                           bool exclusive) override;
+  void OnPing(Http2PingId ping_id, bool is_ack) override;
+  void OnPushPromiseForStream(Http2StreamId stream_id,
+                              Http2StreamId promised_stream_id) override;
+  void OnGoAway(Http2StreamId last_accepted_stream_id,
+                Http2ErrorCode error_code,
+                absl::string_view opaque_data) override;
+  void OnWindowUpdate(Http2StreamId stream_id, int window_increment) override;
+  void OnReadyToSendDataForStream(Http2StreamId stream_id,
+                                  char* destination_buffer,
+                                  size_t length,
+                                  ssize_t* written,
+                                  bool* end_stream) override;
+  void OnReadyToSendMetadataForStream(Http2StreamId stream_id,
+                                      char* buffer,
+                                      size_t length,
+                                      ssize_t* written) override;
+  void OnBeginMetadataForStream(Http2StreamId stream_id,
+                                size_t payload_length) override;
+  void OnMetadataForStream(Http2StreamId stream_id,
+                           absl::string_view metadata) override;
+  void OnMetadataEndForStream(Http2StreamId stream_id) override;
+
+ private:
+  Perspective perspective_;
+  nghttp2_session_callbacks_unique_ptr callbacks_;
+  void* user_data_;
+
+  nghttp2_frame current_frame_;
+  std::vector<nghttp2_settings_entry> settings_;
+  size_t remaining_data_ = 0;
+
+  struct StreamInfo {
+    bool received_headers = false;
+  };
+  absl::flat_hash_map<Http2StreamId, std::unique_ptr<StreamInfo>> stream_map_;
+};
+
+}  // namespace adapter
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_ADAPTER_CALLBACK_VISITOR_H_
diff --git a/http2/adapter/callback_visitor_test.cc b/http2/adapter/callback_visitor_test.cc
new file mode 100644
index 0000000..4dfcb5f
--- /dev/null
+++ b/http2/adapter/callback_visitor_test.cc
@@ -0,0 +1,260 @@
+#include "http2/adapter/callback_visitor.h"
+
+#include "http2/adapter/mock_nghttp2_callbacks.h"
+#include "http2/adapter/test_utils.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+namespace {
+
+using testing::_;
+
+enum FrameType {
+  DATA,
+  HEADERS,
+  PRIORITY,
+  RST_STREAM,
+  SETTINGS,
+  PUSH_PROMISE,
+  PING,
+  GOAWAY,
+  WINDOW_UPDATE,
+};
+
+// Tests connection-level events.
+TEST(ClientCallbackVisitorUnitTest, ConnectionFrames) {
+  testing::StrictMock<MockNghttp2Callbacks> callbacks;
+  CallbackVisitor visitor(Perspective::kClient,
+                          MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+
+  testing::InSequence seq;
+
+  // SETTINGS
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(0, SETTINGS, _)));
+  visitor.OnFrameHeader(0, 0, SETTINGS, 0);
+
+  visitor.OnSettingsStart();
+  EXPECT_CALL(callbacks, OnFrameRecv(IsSettings(testing::IsEmpty())));
+  visitor.OnSettingsEnd();
+
+  // PING
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(0, PING, _)));
+  visitor.OnFrameHeader(0, 8, PING, 0);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsPing(42)));
+  visitor.OnPing(42, false);
+
+  // WINDOW_UPDATE
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(0, WINDOW_UPDATE, _)));
+  visitor.OnFrameHeader(0, 4, WINDOW_UPDATE, 0);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsWindowUpdate(1000)));
+  visitor.OnWindowUpdate(0, 1000);
+
+  // PING ack
+  EXPECT_CALL(callbacks,
+              OnBeginFrame(HasFrameHeader(0, PING, NGHTTP2_FLAG_ACK)));
+  visitor.OnFrameHeader(0, 8, PING, 1);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsPingAck(247)));
+  visitor.OnPing(247, true);
+
+  // GOAWAY
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(0, GOAWAY, 0)));
+  visitor.OnFrameHeader(0, 19, GOAWAY, 0);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsGoAway(5, NGHTTP2_ENHANCE_YOUR_CALM,
+                                              "calm down!!")));
+  visitor.OnGoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!");
+}
+
+TEST(ClientCallbackVisitorUnitTest, StreamFrames) {
+  testing::StrictMock<MockNghttp2Callbacks> callbacks;
+  CallbackVisitor visitor(Perspective::kClient,
+                          MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+
+  testing::InSequence seq;
+
+  // HEADERS on stream 1
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, HEADERS, _)));
+  visitor.OnFrameHeader(1, 23, HEADERS, 4);
+
+  EXPECT_CALL(callbacks,
+              OnBeginHeaders(IsHeaders(1, _, NGHTTP2_HCAT_RESPONSE)));
+  visitor.OnBeginHeadersForStream(1);
+
+  EXPECT_CALL(callbacks, OnHeader(_, ":status", "200", _));
+  visitor.OnHeaderForStream(1, ":status", "200");
+
+  EXPECT_CALL(callbacks, OnHeader(_, "server", "my-fake-server", _));
+  visitor.OnHeaderForStream(1, "server", "my-fake-server");
+
+  EXPECT_CALL(callbacks,
+              OnHeader(_, "date", "Tue, 6 Apr 2021 12:54:01 GMT", _));
+  visitor.OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT");
+
+  EXPECT_CALL(callbacks, OnHeader(_, "trailer", "x-server-status", _));
+  visitor.OnHeaderForStream(1, "trailer", "x-server-status");
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsHeaders(1, _, NGHTTP2_HCAT_RESPONSE)));
+  visitor.OnEndHeadersForStream(1);
+
+  // DATA for stream 1
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, DATA, 0)));
+  visitor.OnFrameHeader(1, 26, DATA, 0);
+
+  visitor.OnBeginDataForStream(1, 26);
+  EXPECT_CALL(callbacks, OnDataChunkRecv(0, 1, "This is the response body."));
+  EXPECT_CALL(callbacks, OnFrameRecv(IsData(1, _, 0)));
+  visitor.OnDataForStream(1, "This is the response body.");
+
+  // Trailers for stream 1, with a different nghttp2 "category".
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, HEADERS, _)));
+  visitor.OnFrameHeader(1, 23, HEADERS, 4);
+
+  EXPECT_CALL(callbacks, OnBeginHeaders(IsHeaders(1, _, NGHTTP2_HCAT_HEADERS)));
+  visitor.OnBeginHeadersForStream(1);
+
+  EXPECT_CALL(callbacks, OnHeader(_, "x-server-status", "OK", _));
+  visitor.OnHeaderForStream(1, "x-server-status", "OK");
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsHeaders(1, _, NGHTTP2_HCAT_HEADERS)));
+  visitor.OnEndHeadersForStream(1);
+
+  // RST_STREAM on stream 3
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(3, RST_STREAM, 0)));
+  visitor.OnFrameHeader(3, 4, RST_STREAM, 0);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsRstStream(3, NGHTTP2_INTERNAL_ERROR)));
+  visitor.OnRstStream(3, Http2ErrorCode::INTERNAL_ERROR);
+
+  EXPECT_CALL(callbacks, OnStreamClose(3, NGHTTP2_INTERNAL_ERROR));
+  visitor.OnCloseStream(3, Http2ErrorCode::INTERNAL_ERROR);
+
+  // More stream close events
+  EXPECT_CALL(callbacks,
+              OnBeginFrame(HasFrameHeader(1, DATA, NGHTTP2_FLAG_END_STREAM)));
+  visitor.OnFrameHeader(1, 0, DATA, 1);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsData(1, _, NGHTTP2_FLAG_END_STREAM)));
+  visitor.OnBeginDataForStream(1, 0);
+  visitor.OnEndStream(1);
+
+  EXPECT_CALL(callbacks, OnStreamClose(1, NGHTTP2_NO_ERROR));
+  visitor.OnCloseStream(1, Http2ErrorCode::NO_ERROR);
+
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(5, RST_STREAM, _)));
+  visitor.OnFrameHeader(5, 4, RST_STREAM, 0);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsRstStream(5, NGHTTP2_REFUSED_STREAM)));
+  visitor.OnRstStream(5, Http2ErrorCode::REFUSED_STREAM);
+
+  EXPECT_CALL(callbacks, OnStreamClose(5, NGHTTP2_REFUSED_STREAM));
+  visitor.OnCloseStream(5, Http2ErrorCode::REFUSED_STREAM);
+}
+
+TEST(ServerCallbackVisitorUnitTest, ConnectionFrames) {
+  testing::StrictMock<MockNghttp2Callbacks> callbacks;
+  CallbackVisitor visitor(Perspective::kServer,
+                          MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+
+  testing::InSequence seq;
+
+  // SETTINGS
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(0, SETTINGS, _)));
+  visitor.OnFrameHeader(0, 0, SETTINGS, 0);
+
+  visitor.OnSettingsStart();
+  EXPECT_CALL(callbacks, OnFrameRecv(IsSettings(testing::IsEmpty())));
+  visitor.OnSettingsEnd();
+
+  // PING
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(0, PING, _)));
+  visitor.OnFrameHeader(0, 8, PING, 0);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsPing(42)));
+  visitor.OnPing(42, false);
+
+  // WINDOW_UPDATE
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(0, WINDOW_UPDATE, _)));
+  visitor.OnFrameHeader(0, 4, WINDOW_UPDATE, 0);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsWindowUpdate(1000)));
+  visitor.OnWindowUpdate(0, 1000);
+
+  // PING ack
+  EXPECT_CALL(callbacks,
+              OnBeginFrame(HasFrameHeader(0, PING, NGHTTP2_FLAG_ACK)));
+  visitor.OnFrameHeader(0, 8, PING, 1);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsPingAck(247)));
+  visitor.OnPing(247, true);
+}
+
+TEST(ServerCallbackVisitorUnitTest, StreamFrames) {
+  testing::StrictMock<MockNghttp2Callbacks> callbacks;
+  CallbackVisitor visitor(Perspective::kServer,
+                          MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+
+  testing::InSequence seq;
+
+  // HEADERS on stream 1
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(
+                             1, HEADERS, NGHTTP2_FLAG_END_HEADERS)));
+  visitor.OnFrameHeader(1, 23, HEADERS, 4);
+
+  EXPECT_CALL(callbacks, OnBeginHeaders(IsHeaders(1, NGHTTP2_FLAG_END_HEADERS,
+                                                  NGHTTP2_HCAT_REQUEST)));
+  visitor.OnBeginHeadersForStream(1);
+
+  EXPECT_CALL(callbacks, OnHeader(_, ":method", "POST", _));
+  visitor.OnHeaderForStream(1, ":method", "POST");
+
+  EXPECT_CALL(callbacks, OnHeader(_, ":path", "/example/path", _));
+  visitor.OnHeaderForStream(1, ":path", "/example/path");
+
+  EXPECT_CALL(callbacks, OnHeader(_, ":scheme", "https", _));
+  visitor.OnHeaderForStream(1, ":scheme", "https");
+
+  EXPECT_CALL(callbacks, OnHeader(_, ":authority", "example.com", _));
+  visitor.OnHeaderForStream(1, ":authority", "example.com");
+
+  EXPECT_CALL(callbacks, OnHeader(_, "accept", "text/html", _));
+  visitor.OnHeaderForStream(1, "accept", "text/html");
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsHeaders(1, NGHTTP2_FLAG_END_HEADERS,
+                                               NGHTTP2_HCAT_REQUEST)));
+  visitor.OnEndHeadersForStream(1);
+
+  // DATA on stream 1
+  EXPECT_CALL(callbacks,
+              OnBeginFrame(HasFrameHeader(1, DATA, NGHTTP2_FLAG_END_STREAM)));
+  visitor.OnFrameHeader(1, 25, DATA, NGHTTP2_FLAG_END_STREAM);
+
+  visitor.OnBeginDataForStream(1, 25);
+  EXPECT_CALL(callbacks, OnDataChunkRecv(NGHTTP2_FLAG_END_STREAM, 1,
+                                         "This is the request body."));
+  EXPECT_CALL(callbacks, OnFrameRecv(IsData(1, _, NGHTTP2_FLAG_END_STREAM)));
+  visitor.OnDataForStream(1, "This is the request body.");
+  visitor.OnEndStream(1);
+
+  EXPECT_CALL(callbacks, OnStreamClose(1, NGHTTP2_NO_ERROR));
+  visitor.OnCloseStream(1, Http2ErrorCode::NO_ERROR);
+
+  // RST_STREAM on stream 3
+  EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(3, RST_STREAM, 0)));
+  visitor.OnFrameHeader(3, 4, RST_STREAM, 0);
+
+  EXPECT_CALL(callbacks, OnFrameRecv(IsRstStream(3, NGHTTP2_INTERNAL_ERROR)));
+  visitor.OnRstStream(3, Http2ErrorCode::INTERNAL_ERROR);
+
+  EXPECT_CALL(callbacks, OnStreamClose(3, NGHTTP2_INTERNAL_ERROR));
+  visitor.OnCloseStream(3, Http2ErrorCode::INTERNAL_ERROR);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace adapter
+}  // namespace http2
diff --git a/http2/adapter/http2_protocol.h b/http2/adapter/http2_protocol.h
index 49e5f16..35abea0 100644
--- a/http2/adapter/http2_protocol.h
+++ b/http2/adapter/http2_protocol.h
@@ -99,6 +99,11 @@
 // Section 7 definitions.
 absl::string_view Http2ErrorCodeToString(Http2ErrorCode error_code);
 
+enum class Perspective {
+  kClient,
+  kServer,
+};
+
 }  // namespace adapter
 }  // namespace http2
 
diff --git a/http2/adapter/http2_session.h b/http2/adapter/http2_session.h
index 43a26e9..0a6321c 100644
--- a/http2/adapter/http2_session.h
+++ b/http2/adapter/http2_session.h
@@ -26,11 +26,6 @@
   virtual int GetRemoteWindowSize() const = 0;
 };
 
-enum class Perspective {
-  kClient,
-  kServer,
-};
-
 }  // namespace adapter
 }  // namespace http2
 
diff --git a/http2/adapter/mock_nghttp2_callbacks.cc b/http2/adapter/mock_nghttp2_callbacks.cc
index 4bc1e8e..4699a37 100644
--- a/http2/adapter/mock_nghttp2_callbacks.cc
+++ b/http2/adapter/mock_nghttp2_callbacks.cc
@@ -107,6 +107,14 @@
             ->OnInvalidFrameRecv(frame, error_code);
       });
 
+  nghttp2_session_callbacks_set_error_callback2(
+      callbacks,
+      [](nghttp2_session* session, int lib_error_code, const char* msg,
+         size_t len, void* user_data) -> int {
+        return static_cast<MockNghttp2Callbacks*>(user_data)->OnErrorCallback2(
+            lib_error_code, msg, len);
+      });
+
   return MakeCallbacksPtr(callbacks);
 }
 
diff --git a/http2/adapter/mock_nghttp2_callbacks.h b/http2/adapter/mock_nghttp2_callbacks.h
index fcc9c59..e279457 100644
--- a/http2/adapter/mock_nghttp2_callbacks.h
+++ b/http2/adapter/mock_nghttp2_callbacks.h
@@ -53,10 +53,10 @@
 
   MOCK_METHOD(int, OnStreamClose, (int32_t stream_id, uint32_t error_code), ());
 
-  MOCK_METHOD(int, OnFrameSend, (const nghttp2_frame* frame), ());
-
   MOCK_METHOD(int, BeforeFrameSend, (const nghttp2_frame* frame), ());
 
+  MOCK_METHOD(int, OnFrameSend, (const nghttp2_frame* frame), ());
+
   MOCK_METHOD(int,
               OnFrameNotSend,
               (const nghttp2_frame* frame, int lib_error_code),
@@ -66,6 +66,11 @@
               OnInvalidFrameRecv,
               (const nghttp2_frame* frame, int error_code),
               ());
+
+  MOCK_METHOD(int,
+              OnErrorCallback2,
+              (int lib_error_code, const char* msg, size_t len),
+              ());
 };
 
 }  // namespace test