Updates OgHttp2Session's internal flow control window state when sending outbound WINDOW_UPDATE frames.

Note that this requires a small change to our usage of WindowManager to avoid double-counting in the case of internally generated WINDOW_UPDATE frames.

PiperOrigin-RevId: 429317678
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index 5ff0020..5791692 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -264,6 +264,37 @@
   EXPECT_THAT(visitor.data(), testing::IsEmpty());
 }
 
+TEST(NgHttp2AdapterTest, QueuingWindowUpdateAffectsWindow) {
+  DataSavingVisitor visitor;
+  auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
+
+  EXPECT_EQ(adapter->GetReceiveWindowSize(), kInitialFlowControlWindowSize);
+  adapter->SubmitWindowUpdate(0, 10000);
+  EXPECT_EQ(adapter->GetReceiveWindowSize(),
+            kInitialFlowControlWindowSize + 10000);
+
+  const std::vector<Header> headers =
+      ToHeaders({{":method", "GET"},
+                 {":scheme", "http"},
+                 {":authority", "example.com"},
+                 {":path", "/this/is/request/one"}});
+  const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr);
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 0, 4, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 0, 4, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+  int result = adapter->Send();
+  EXPECT_EQ(0, result);
+
+  EXPECT_EQ(adapter->GetStreamReceiveWindowSize(stream_id),
+            kInitialFlowControlWindowSize);
+  adapter->SubmitWindowUpdate(1, 20000);
+  EXPECT_EQ(adapter->GetStreamReceiveWindowSize(stream_id),
+            kInitialFlowControlWindowSize + 20000);
+}
+
 TEST(NgHttp2AdapterTest, ClientRejects100HeadersWithFin) {
   DataSavingVisitor visitor;
   auto adapter = NgHttp2Adapter::CreateClientAdapter(visitor);
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 2e37880..5b4637b 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -345,6 +345,40 @@
               EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::PING}));
 }
 
+TEST(OgHttp2AdapterTest, QueuingWindowUpdateAffectsWindow) {
+  DataSavingVisitor visitor;
+  OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
+  auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+  EXPECT_EQ(adapter->GetReceiveWindowSize(), kInitialFlowControlWindowSize);
+  adapter->SubmitWindowUpdate(0, 10000);
+  EXPECT_EQ(adapter->GetReceiveWindowSize(),
+            kInitialFlowControlWindowSize + 10000);
+
+  const std::vector<Header> headers =
+      ToHeaders({{":method", "GET"},
+                 {":scheme", "http"},
+                 {":authority", "example.com"},
+                 {":path", "/this/is/request/one"}});
+  const int32_t stream_id = adapter->SubmitRequest(headers, nullptr, nullptr);
+
+  EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(WINDOW_UPDATE, 0, 4, 0x0));
+  EXPECT_CALL(visitor, OnFrameSent(WINDOW_UPDATE, 0, 4, 0x0, 0));
+  EXPECT_CALL(visitor, OnBeforeFrameSent(HEADERS, stream_id, _, 0x5));
+  EXPECT_CALL(visitor, OnFrameSent(HEADERS, stream_id, _, 0x5, 0));
+
+  int result = adapter->Send();
+  EXPECT_EQ(0, result);
+
+  EXPECT_EQ(adapter->GetStreamReceiveWindowSize(stream_id),
+            kInitialFlowControlWindowSize);
+  adapter->SubmitWindowUpdate(1, 20000);
+  EXPECT_EQ(adapter->GetStreamReceiveWindowSize(stream_id),
+            kInitialFlowControlWindowSize + 20000);
+}
+
 TEST(OgHttp2AdapterTest, ClientRejects100HeadersWithFin) {
   DataSavingVisitor visitor;
   OgHttp2Adapter::Options options{.perspective = Perspective::kClient};
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 773199c..b7016a5 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -329,11 +329,12 @@
           []() { return kTraceLoggingEnabled; }, this),
       headers_handler_(*this, visitor),
       noop_headers_handler_(/*listener=*/nullptr),
-      connection_window_manager_(kInitialFlowControlWindowSize,
-                                 [this](size_t window_update_delta) {
-                                   SendWindowUpdate(kConnectionStreamId,
-                                                    window_update_delta);
-                                 }),
+      connection_window_manager_(
+          kInitialFlowControlWindowSize,
+          [this](size_t window_update_delta) {
+            SendWindowUpdate(kConnectionStreamId, window_update_delta);
+          },
+          /*update_window_on_notify=*/false),
       options_(options) {
   decoder_.set_visitor(&receive_logger_);
   decoder_.set_extension_visitor(this);
@@ -539,6 +540,10 @@
       // TODO(diannahu): Condition on existence in the stream map?
       streams_reset_.insert(frame->stream_id());
     }
+  } else if (frame->frame_type() == spdy::SpdyFrameType::WINDOW_UPDATE) {
+    UpdateReceiveWindow(
+        frame->stream_id(),
+        reinterpret_cast<spdy::SpdyWindowUpdateIR&>(*frame).delta());
   }
   if (frame->stream_id() != 0) {
     auto result = queued_frames_.insert({frame->stream_id(), 1});
@@ -1782,6 +1787,18 @@
       stream_id, spdy::ERROR_CODE_PROTOCOL_ERROR));
 }
 
+void OgHttp2Session::UpdateReceiveWindow(Http2StreamId stream_id,
+                                         int32_t delta) {
+  if (stream_id == 0) {
+    connection_window_manager_.IncreaseWindow(delta);
+  } else {
+    auto iter = stream_map_.find(stream_id);
+    if (iter != stream_map_.end()) {
+      iter->second.window_manager.IncreaseWindow(delta);
+    }
+  }
+}
+
 void OgHttp2Session::UpdateInitialWindowSize(uint32_t new_value) {
   const int32_t delta =
       static_cast<int32_t>(new_value) - initial_stream_send_window_;
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index 29bff34..cdffd4f 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -214,7 +214,8 @@
   struct QUICHE_EXPORT_PRIVATE StreamState {
     StreamState(int32_t stream_receive_window, int32_t stream_send_window,
                 WindowManager::WindowUpdateListener listener)
-        : window_manager(stream_receive_window, std::move(listener)),
+        : window_manager(stream_receive_window, std::move(listener),
+                         /*update_window_on_notify=*/false),
           send_window(stream_send_window) {}
 
     WindowManager window_manager;
@@ -401,6 +402,8 @@
 
   void HandleContentLengthError(Http2StreamId stream_id);
 
+  void UpdateReceiveWindow(Http2StreamId stream_id, int32_t delta);
+
   void UpdateInitialWindowSize(uint32_t new_value);
 
   // Receives events when inbound frames are parsed.