Adds a test case that demonstrates a significant difference in flow control behavior between nghttp2 and oghttp2.
PiperOrigin-RevId: 481166007
diff --git a/quiche/http2/adapter/adapter_impl_comparison_test.cc b/quiche/http2/adapter/adapter_impl_comparison_test.cc
index cce014b..364e996 100644
--- a/quiche/http2/adapter/adapter_impl_comparison_test.cc
+++ b/quiche/http2/adapter/adapter_impl_comparison_test.cc
@@ -38,6 +38,92 @@
// non-existent streams between nghttp2_adapter and oghttp2_adapter.
}
+TEST(AdapterImplComparisonTest, SubmitWindowUpdateBumpsWindow) {
+ RecordingHttp2Visitor nghttp2_visitor;
+ std::unique_ptr<NgHttp2Adapter> nghttp2_adapter =
+ NgHttp2Adapter::CreateClientAdapter(nghttp2_visitor);
+
+ RecordingHttp2Visitor oghttp2_visitor;
+ OgHttp2Adapter::Options options;
+ options.perspective = Perspective::kClient;
+ std::unique_ptr<OgHttp2Adapter> oghttp2_adapter =
+ OgHttp2Adapter::Create(oghttp2_visitor, options);
+
+ int result;
+
+ const std::vector<Header> request_headers =
+ ToHeaders({{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/"}});
+ const int kInitialFlowControlWindow = 65535;
+ const int kConnectionWindowIncrease = 192 * 1024;
+
+ const int32_t nghttp2_stream_id =
+ nghttp2_adapter->SubmitRequest(request_headers, nullptr, nullptr);
+
+ // Both the connection and stream flow control windows are increased.
+ nghttp2_adapter->SubmitWindowUpdate(0, kConnectionWindowIncrease);
+ nghttp2_adapter->SubmitWindowUpdate(nghttp2_stream_id,
+ kConnectionWindowIncrease);
+ result = nghttp2_adapter->Send();
+ EXPECT_EQ(0, result);
+ int nghttp2_window = nghttp2_adapter->GetReceiveWindowSize();
+ EXPECT_EQ(kInitialFlowControlWindow + kConnectionWindowIncrease,
+ nghttp2_window);
+
+ const int32_t oghttp2_stream_id =
+ oghttp2_adapter->SubmitRequest(request_headers, nullptr, nullptr);
+ // Both the connection and stream flow control windows are increased.
+ oghttp2_adapter->SubmitWindowUpdate(0, kConnectionWindowIncrease);
+ oghttp2_adapter->SubmitWindowUpdate(oghttp2_stream_id,
+ kConnectionWindowIncrease);
+ result = oghttp2_adapter->Send();
+ EXPECT_EQ(0, result);
+ int oghttp2_window = oghttp2_adapter->GetReceiveWindowSize();
+ EXPECT_EQ(kInitialFlowControlWindow + kConnectionWindowIncrease,
+ oghttp2_window);
+
+ // nghttp2 and oghttp2 agree on the advertised window.
+ EXPECT_EQ(nghttp2_window, oghttp2_window);
+
+ ASSERT_EQ(nghttp2_stream_id, oghttp2_stream_id);
+
+ const int kMaxFrameSize = 16 * 1024;
+ const std::string body_chunk(kMaxFrameSize, 'a');
+ auto sequence = TestFrameSequence();
+ sequence.ServerPreface().Headers(nghttp2_stream_id, {{":status", "200"}},
+ /*fin=*/false);
+ // This loop generates enough DATA frames to consume the window increase.
+ const int kNumFrames = kConnectionWindowIncrease / kMaxFrameSize;
+ for (int i = 0; i < kNumFrames; ++i) {
+ sequence.Data(nghttp2_stream_id, body_chunk);
+ }
+ const std::string frames = sequence.Serialize();
+
+ nghttp2_adapter->ProcessBytes(frames);
+ // Marking the data consumed causes a window update, which is reflected in the
+ // advertised window size.
+ nghttp2_adapter->MarkDataConsumedForStream(nghttp2_stream_id,
+ kNumFrames * kMaxFrameSize);
+ result = nghttp2_adapter->Send();
+ EXPECT_EQ(0, result);
+ nghttp2_window = nghttp2_adapter->GetReceiveWindowSize();
+
+ oghttp2_adapter->ProcessBytes(frames);
+ oghttp2_adapter->MarkDataConsumedForStream(oghttp2_stream_id,
+ kNumFrames * kMaxFrameSize);
+ result = oghttp2_adapter->Send();
+ EXPECT_EQ(0, result);
+ oghttp2_window = oghttp2_adapter->GetReceiveWindowSize();
+
+ const int kMinExpectation =
+ (kInitialFlowControlWindow + kConnectionWindowIncrease) / 2;
+ EXPECT_GT(nghttp2_window, kMinExpectation);
+ // BUG! oghttp2 does not maintain the larger window persistently.
+ EXPECT_LT(oghttp2_window, kMinExpectation);
+}
+
TEST(AdapterImplComparisonTest, ServerHandlesFrames) {
RecordingHttp2Visitor nghttp2_visitor;
std::unique_ptr<NgHttp2Adapter> nghttp2_adapter =