Adds a test case demonstrating that OgHttp2Adapter does not consider DATA frame padding as "consumed". In the test, this shows up as a lack of WINDOW_UPDATE frames in the adapter's response. This bug could be implicated in https://github.com/envoyproxy/envoy/issues/32401. PiperOrigin-RevId: 607420483
diff --git a/quiche/http2/adapter/nghttp2_adapter_test.cc b/quiche/http2/adapter/nghttp2_adapter_test.cc index 0493c87..d118ed8 100644 --- a/quiche/http2/adapter/nghttp2_adapter_test.cc +++ b/quiche/http2/adapter/nghttp2_adapter_test.cc
@@ -7250,6 +7250,62 @@ SpdyFrameType::RST_STREAM, SpdyFrameType::RST_STREAM})); } +TEST(OgHttp2AdapterTest, ServerConsumesDataWithPadding) { + DataSavingVisitor visitor; + auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor); + + TestFrameSequence seq = std::move(TestFrameSequence().ClientPreface().Headers( + 1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/false)); + // Generates a bunch of DATA frames, with the bulk of the payloads consisting + // of padding. + size_t total_size = 0; + while (total_size < 62 * 1024) { + seq.Data(1, "a", /*fin=*/false, /*padding=*/254); + total_size += 255; + } + const std::string frames = seq.Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0x8)) + .Times(testing::AtLeast(1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)).Times(testing::AtLeast(1)); + EXPECT_CALL(visitor, OnDataForStream(1, "a")).Times(testing::AtLeast(1)); + EXPECT_CALL(visitor, OnDataPaddingLength(1, _)).Times(testing::AtLeast(1)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(result, frames.size()); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, ACK_FLAG)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, ACK_FLAG, 0)); + // Since most of the flow control window consumed is padding, the adapter + // generates window updates. + EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 1, _, 0x0)).Times(1); + EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 1, _, 0x0, 0)).Times(1); + EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 0, _, 0x0)).Times(1); + EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 0, _, 0x0, 0)).Times(1); + + const int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS, + SpdyFrameType::WINDOW_UPDATE, + SpdyFrameType::WINDOW_UPDATE})); +} + TEST(NgHttp2AdapterTest, NegativeFlowControlStreamResumption) { DataSavingVisitor 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 6b570e2..be2b1ce 100644 --- a/quiche/http2/adapter/oghttp2_adapter_test.cc +++ b/quiche/http2/adapter/oghttp2_adapter_test.cc
@@ -8084,6 +8084,66 @@ SpdyFrameType::WINDOW_UPDATE})); } +TEST(OgHttp2AdapterTest, ServerConsumesDataWithPadding) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options; + options.perspective = Perspective::kServer; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + TestFrameSequence seq = std::move(TestFrameSequence().ClientPreface().Headers( + 1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}}, + /*fin=*/false)); + // Generates a bunch of DATA frames, with the bulk of the payloads consisting + // of padding. + size_t total_size = 0; + while (total_size < 62 * 1024) { + seq.Data(1, "a", /*fin=*/false, /*padding=*/254); + total_size += 255; + } + const std::string frames = seq.Serialize(); + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(visitor, OnFrameHeader(1, _, DATA, 0x8)) + .Times(testing::AtLeast(1)); + EXPECT_CALL(visitor, OnBeginDataForStream(1, _)).Times(testing::AtLeast(1)); + EXPECT_CALL(visitor, OnDataForStream(1, "a")).Times(testing::AtLeast(1)); + EXPECT_CALL(visitor, OnDataPaddingLength(1, _)).Times(testing::AtLeast(1)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(result, frames.size()); + EXPECT_LT(adapter->GetReceiveWindowSize(), 2048); + + EXPECT_TRUE(adapter->want_write()); + + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, ACK_FLAG)); + EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, ACK_FLAG, 0)); + // BUG: Most of the flow control window consumed is padding, so the adapter + // should have generated window updates. + EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 1, _, 0x0)).Times(0); + EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 1, _, 0x0, 0)).Times(0); + EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 0, _, 0x0)).Times(0); + EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 0, _, 0x0, 0)).Times(0); + + const int send_result = adapter->Send(); + EXPECT_EQ(0, send_result); + EXPECT_THAT(visitor.data(), + EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS})); +} + // Verifies that NoopHeaderValidator allows several header combinations that // would otherwise be invalid. TEST(OgHttp2AdapterTest, NoopHeaderValidatorTest) {
diff --git a/quiche/http2/adapter/test_frame_sequence.h b/quiche/http2/adapter/test_frame_sequence.h index 47f8199..93c5536 100644 --- a/quiche/http2/adapter/test_frame_sequence.h +++ b/quiche/http2/adapter/test_frame_sequence.h
@@ -22,6 +22,9 @@ public: TestFrameSequence() = default; + TestFrameSequence(TestFrameSequence&& other) = default; + TestFrameSequence& operator=(TestFrameSequence&& other) = default; + TestFrameSequence& ClientPreface( absl::Span<const Http2Setting> settings = {}); TestFrameSequence& ServerPreface(