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