Adds unit tests demonstrating a difference in behavior between raw nghttp2 and wrapped nghttp2. PiperOrigin-RevId: 488434908
diff --git a/quiche/http2/adapter/callback_visitor_test.cc b/quiche/http2/adapter/callback_visitor_test.cc index b0bf971..3eb16c6 100644 --- a/quiche/http2/adapter/callback_visitor_test.cc +++ b/quiche/http2/adapter/callback_visitor_test.cc
@@ -3,7 +3,9 @@ #include "absl/container/flat_hash_map.h" #include "quiche/http2/adapter/http2_protocol.h" #include "quiche/http2/adapter/mock_nghttp2_callbacks.h" +#include "quiche/http2/adapter/nghttp2_adapter.h" #include "quiche/http2/adapter/nghttp2_test_utils.h" +#include "quiche/http2/adapter/test_frame_sequence.h" #include "quiche/http2/adapter/test_utils.h" #include "quiche/common/platform/api/quiche_test.h" @@ -472,6 +474,62 @@ visitor.OnCloseStream(5, Http2ErrorCode::HTTP2_NO_ERROR); } +// In the case of a Content-Length mismatch where the header value is larger +// than the actual data for the stream, nghttp2 will call +// `on_begin_frame_callback` and `on_data_chunk_recv_callback`, but not the +// `on_frame_recv_callback`. +TEST(ServerCallbackVisitorUnitTest, MismatchedContentLengthCallbacks) { + testing::StrictMock<MockNghttp2Callbacks> callbacks; + CallbackVisitor visitor(Perspective::kServer, + *MockNghttp2Callbacks::GetCallbacks(), &callbacks); + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/"}, + {"content-length", "50"}}, + /*fin=*/false) + .Data(1, "Less than 50 bytes.", true) + .Serialize(); + + EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(0, SETTINGS, _))); + + EXPECT_CALL(callbacks, OnFrameRecv(IsSettings(testing::IsEmpty()))); + + // HEADERS on stream 1 + EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader( + 1, HEADERS, NGHTTP2_FLAG_END_HEADERS))); + + EXPECT_CALL(callbacks, OnBeginHeaders(IsHeaders(1, NGHTTP2_FLAG_END_HEADERS, + NGHTTP2_HCAT_REQUEST))); + + EXPECT_CALL(callbacks, OnHeader(_, ":method", "POST", _)); + EXPECT_CALL(callbacks, OnHeader(_, ":path", "/", _)); + EXPECT_CALL(callbacks, OnHeader(_, ":scheme", "https", _)); + EXPECT_CALL(callbacks, OnHeader(_, ":authority", "example.com", _)); + EXPECT_CALL(callbacks, OnHeader(_, "content-length", "50", _)); + EXPECT_CALL(callbacks, OnFrameRecv(IsHeaders(1, NGHTTP2_FLAG_END_HEADERS, + NGHTTP2_HCAT_REQUEST))); + + // DATA on stream 1 + EXPECT_CALL(callbacks, + OnBeginFrame(HasFrameHeader(1, DATA, NGHTTP2_FLAG_END_STREAM))); + + EXPECT_CALL(callbacks, OnDataChunkRecv(NGHTTP2_FLAG_END_STREAM, 1, + "Less than 50 bytes.")); + + // BUG: CallbackVisitor should not pass on this call to OnFrameRecv. + // (b/258853437) + EXPECT_CALL(callbacks, OnFrameRecv(IsData(1, _, NGHTTP2_FLAG_END_STREAM))); + + int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), result); +} + } // namespace } // namespace test } // namespace adapter
diff --git a/quiche/http2/adapter/nghttp2_test.cc b/quiche/http2/adapter/nghttp2_test.cc index 39770ff..1363ba6 100644 --- a/quiche/http2/adapter/nghttp2_test.cc +++ b/quiche/http2/adapter/nghttp2_test.cc
@@ -199,6 +199,63 @@ EXPECT_FALSE(nghttp2_session_want_write(session_.get())); } +class Nghttp2ServerTest : public Nghttp2Test { + public: + Perspective GetPerspective() override { return Perspective::kServer; } +}; + +// Verifies the behavior when a stream ends early. +TEST_F(Nghttp2ServerTest, MismatchedContentLength) { + const std::string initial_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/"}, + {"content-length", "50"}}, + /*fin=*/false) + .Data(1, "Less than 50 bytes.", true) + .Serialize(); + + testing::InSequence seq; + EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, _))); + + EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty()))); + + // HEADERS on stream 1 + EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader( + 1, HEADERS, NGHTTP2_FLAG_END_HEADERS))); + + EXPECT_CALL(mock_callbacks_, + OnBeginHeaders(IsHeaders(1, NGHTTP2_FLAG_END_HEADERS, + NGHTTP2_HCAT_REQUEST))); + + EXPECT_CALL(mock_callbacks_, OnHeader(_, ":method", "POST", _)); + EXPECT_CALL(mock_callbacks_, OnHeader(_, ":scheme", "https", _)); + EXPECT_CALL(mock_callbacks_, OnHeader(_, ":authority", "example.com", _)); + EXPECT_CALL(mock_callbacks_, OnHeader(_, ":path", "/", _)); + EXPECT_CALL(mock_callbacks_, OnHeader(_, "content-length", "50", _)); + EXPECT_CALL(mock_callbacks_, + OnFrameRecv(IsHeaders(1, NGHTTP2_FLAG_END_HEADERS, + NGHTTP2_HCAT_REQUEST))); + + // DATA on stream 1 + EXPECT_CALL(mock_callbacks_, + OnBeginFrame(HasFrameHeader(1, DATA, NGHTTP2_FLAG_END_STREAM))); + + EXPECT_CALL(mock_callbacks_, OnDataChunkRecv(NGHTTP2_FLAG_END_STREAM, 1, + "Less than 50 bytes.")); + + // No OnFrameRecv() callback for the DATA frame, since there is a + // Content-Length mismatch error. + + ssize_t result = nghttp2_session_mem_recv( + session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size()); + ASSERT_EQ(result, initial_frames.size()); +} + } // namespace } // namespace test } // namespace adapter