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(