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_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