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 =