Adds unit test cases covering the case where a content-length overshoot causes a PROTOCOL_ERROR. Both nghttp2 and oghttp2 appear to treat this case the same. PiperOrigin-RevId: 700101380
diff --git a/quiche/http2/adapter/nghttp2_adapter_test.cc b/quiche/http2/adapter/nghttp2_adapter_test.cc index 0674441..99cab9e 100644 --- a/quiche/http2/adapter/nghttp2_adapter_test.cc +++ b/quiche/http2/adapter/nghttp2_adapter_test.cc
@@ -7502,6 +7502,94 @@ SpdyFrameType::RST_STREAM})); } +TEST(NgHttp2AdapterTest, ServerHandlesContentLengthMismatchWithDataPending) { + TestVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + // Server preface + adapter->SubmitSettings({}); + + testing::InSequence s; + + const std::string stream_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"content-length", "4"}}) + .Data(1, "ok", /*fin=*/false) + .Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + // Stream 1: content-length is smaller than actual data, but not yet + // Headers and the beginning of data is delivered to the visitor. + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 2, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 2)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + // Initial response data for stream 1. + visitor.AppendPayloadForStream( + 1, "Here is some response data, and there will be more. "); + adapter->SubmitResponse(1, ToHeaders({{":status", "200"}}), false); + + // Server preface (SETTINGS) + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + // SETTINGS ack + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, ACK_FLAG)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, ACK_FLAG, 0)); + // Stream 1, with some DATA + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::HEADERS, SpdyFrameType::DATA})); + + visitor.Clear(); + + // Final response data and fin for stream 1. + visitor.AppendPayloadForStream(1, "Last data!"); + visitor.SetEndData(1, true); + adapter->ResumeStream(1); + + const std::string client_fin = + TestFrameSequence().Data(1, "ay!", /*fin=*/true).Serialize(); + + // The library does not deliver the actual data or fin from the client to the + // visitor. + EXPECT_CALL(visitor, OnFrameHeader(1, 3, DATA, 0x1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 3)); + + const int64_t fin_result = adapter->ProcessBytes(client_fin); + ASSERT_EQ(client_fin.size(), fin_result); + + // The library sends the RST_STREAM but not the end of data. + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR)); + result = adapter->Send(); + EXPECT_EQ(0, result); +} + TEST(NgHttp2AdapterTest, ServerHandlesAsteriskPathForOptions) { TestVisitor visitor; auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
diff --git a/quiche/http2/adapter/oghttp2_adapter_test.cc b/quiche/http2/adapter/oghttp2_adapter_test.cc index 82980cb..bf11371 100644 --- a/quiche/http2/adapter/oghttp2_adapter_test.cc +++ b/quiche/http2/adapter/oghttp2_adapter_test.cc
@@ -8279,6 +8279,94 @@ SpdyFrameType::RST_STREAM, SpdyFrameType::RST_STREAM})); } +TEST(OgHttp2AdapterTest, ServerHandlesContentLengthMismatchWithDataPending) { + TestVisitor visitor; + OgHttp2Adapter::Options options; + options.perspective = Perspective::kServer; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + testing::InSequence s; + + const std::string stream_frames = + TestFrameSequence() + .ClientPreface() + .Headers(1, {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"content-length", "4"}}) + .Data(1, "ok", /*fin=*/false) + .Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + // Stream 1: content-length is smaller than actual data, but not yet + // Headers and the beginning of data is delivered to the visitor. + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(5); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, 2, DATA, 0)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 2)); + EXPECT_CALL(visitor, OnDataForStream(1, _)); + + const int64_t stream_result = adapter->ProcessBytes(stream_frames); + EXPECT_EQ(stream_frames.size(), static_cast<size_t>(stream_result)); + + // Initial response data for stream 1. + visitor.AppendPayloadForStream( + 1, "Here is some response data, and there will be more. "); + adapter->SubmitResponse(1, ToHeaders({{":status", "200"}}), false); + + // Server preface (SETTINGS) + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + // SETTINGS ack + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, ACK_FLAG)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, ACK_FLAG, 0)); + // Stream 1, with some DATA + EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, 1, _, 0x4)); + EXPECT_CALL(visitor, OnFrameSent(HEADERS, 1, _, 0x4, 0)); + EXPECT_CALL(visitor, OnFrameSent(DATA, 1, _, 0x0, 0)); + + EXPECT_TRUE(adapter->want_write()); + int result = adapter->Send(); + EXPECT_EQ(0, result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS, + SpdyFrameType::HEADERS, SpdyFrameType::DATA})); + + visitor.Clear(); + + // Final response data and fin for stream 1. + visitor.AppendPayloadForStream(1, "Last data!"); + visitor.SetEndData(1, true); + adapter->ResumeStream(1); + + const std::string client_fin = + TestFrameSequence().Data(1, "ay!", /*fin=*/true).Serialize(); + + // The library does not deliver the actual data or fin from the client to the + // visitor. + EXPECT_CALL(visitor, OnFrameHeader(1, 3, DATA, 0x1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, 3)); + + const int64_t fin_result = adapter->ProcessBytes(client_fin); + ASSERT_EQ(client_fin.size(), fin_result); + + // The library sends the RST_STREAM but not the end of data. + EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, _, 0x0)); + EXPECT_CALL(visitor, + OnFrameSent(RST_STREAM, 1, _, 0x0, + static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR))); + EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR)); + result = adapter->Send(); + EXPECT_EQ(0, result); +} + TEST(OgHttp2AdapterTest, ServerHandlesAsteriskPathForOptions) { TestVisitor visitor; OgHttp2Adapter::Options options;