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(¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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