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