For DATA frames with END_STREAM, invokes the `OnFrameRecv` callback only once `CallbackVisitor::OnEndStream()` has been invoked.

This fixes a behavior mismatch between raw nghttp2 and wrapped nghttp2.

PiperOrigin-RevId: 488471119
diff --git a/quiche/http2/adapter/callback_visitor.cc b/quiche/http2/adapter/callback_visitor.cc
index 993a8c4..64f0ed6 100644
--- a/quiche/http2/adapter/callback_visitor.cc
+++ b/quiche/http2/adapter/callback_visitor.cc
@@ -217,7 +217,9 @@
   QUICHE_DCHECK_GE(remaining_data_, padding_length);
   current_frame_.data.padlen = padding_length;
   remaining_data_ -= padding_length;
-  if (remaining_data_ == 0 && callbacks_->on_frame_recv_callback != nullptr) {
+  if (remaining_data_ == 0 &&
+      (current_frame_.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0 &&
+      callbacks_->on_frame_recv_callback != nullptr) {
     const int result = callbacks_->on_frame_recv_callback(
         nullptr, &current_frame_, user_data_);
     return result == 0;
@@ -228,7 +230,9 @@
 bool CallbackVisitor::OnBeginDataForStream(Http2StreamId /*stream_id*/,
                                            size_t payload_length) {
   remaining_data_ = payload_length;
-  if (remaining_data_ == 0 && callbacks_->on_frame_recv_callback != nullptr) {
+  if (remaining_data_ == 0 &&
+      (current_frame_.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0 &&
+      callbacks_->on_frame_recv_callback != nullptr) {
     const int result = callbacks_->on_frame_recv_callback(
         nullptr, &current_frame_, user_data_);
     return result == 0;
@@ -248,7 +252,10 @@
   }
   remaining_data_ -= data.size();
   if (result == 0 && remaining_data_ == 0 &&
+      (current_frame_.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0 &&
       callbacks_->on_frame_recv_callback) {
+    // If the DATA frame contains the END_STREAM flag, `on_frame_recv` is
+    // invoked later.
     result = callbacks_->on_frame_recv_callback(nullptr, &current_frame_,
                                                 user_data_);
   }
@@ -257,7 +264,17 @@
 
 bool CallbackVisitor::OnEndStream(Http2StreamId stream_id) {
   QUICHE_VLOG(1) << "OnEndStream(stream_id=" << stream_id << ")";
-  return true;
+  int result = 0;
+  if (static_cast<FrameType>(current_frame_.hd.type) == FrameType::DATA &&
+      (current_frame_.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0 &&
+      callbacks_->on_frame_recv_callback) {
+    // `on_frame_recv` is invoked here to ensure that the Http2Adapter
+    // implementation has successfully validated and processed the entire DATA
+    // frame.
+    result = callbacks_->on_frame_recv_callback(nullptr, &current_frame_,
+                                                user_data_);
+  }
+  return result == 0;
 }
 
 void CallbackVisitor::OnRstStream(Http2StreamId stream_id,
diff --git a/quiche/http2/adapter/callback_visitor_test.cc b/quiche/http2/adapter/callback_visitor_test.cc
index 68a83d3..d2b1030 100644
--- a/quiche/http2/adapter/callback_visitor_test.cc
+++ b/quiche/http2/adapter/callback_visitor_test.cc
@@ -467,8 +467,8 @@
 
   EXPECT_CALL(callbacks, OnFrameRecv(IsData(5, _, kFlags, kPaddingLength)))
       .WillOnce(testing::Return(NGHTTP2_ERR_CALLBACK_FAILURE));
-  EXPECT_FALSE(visitor.OnDataPaddingLength(5, kPaddingLength));
-  EXPECT_TRUE(visitor.OnEndStream(3));
+  EXPECT_TRUE(visitor.OnDataPaddingLength(5, kPaddingLength));
+  EXPECT_FALSE(visitor.OnEndStream(3));
 
   EXPECT_CALL(callbacks, OnStreamClose(5, NGHTTP2_NO_ERROR));
   visitor.OnCloseStream(5, Http2ErrorCode::HTTP2_NO_ERROR);
@@ -522,9 +522,8 @@
   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)));
+  // Like nghttp2, CallbackVisitor does not pass on a call to OnFrameRecv in the
+  // case of Content-Length mismatch.
 
   int64_t result = adapter->ProcessBytes(frames);
   EXPECT_EQ(frames.size(), result);