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, ¤t_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, ¤t_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, ¤t_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, ¤t_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);