Adds a method to the Http2Adapter API to retrieve the send window size for a given stream.

PiperOrigin-RevId: 378868483
diff --git a/http2/adapter/http2_adapter.h b/http2/adapter/http2_adapter.h
index 51edda3..4f2b3f3 100644
--- a/http2/adapter/http2_adapter.h
+++ b/http2/adapter/http2_adapter.h
@@ -72,6 +72,9 @@
   // Returns the connection-level flow control window advertised by the peer.
   virtual int GetSendWindowSize() const = 0;
 
+  // Returns the stream-level flow control window advertised by the peer.
+  virtual int GetStreamSendWindowSize(Http2StreamId stream_id) const = 0;
+
   // Returns the current upper bound on the flow control receive window for this
   // stream. This value does not account for data received from the peer.
   virtual int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const = 0;
diff --git a/http2/adapter/nghttp2_adapter.cc b/http2/adapter/nghttp2_adapter.cc
index e932838..43d81cf 100644
--- a/http2/adapter/nghttp2_adapter.cc
+++ b/http2/adapter/nghttp2_adapter.cc
@@ -106,6 +106,11 @@
   return session_->GetRemoteWindowSize();
 }
 
+int NgHttp2Adapter::GetStreamSendWindowSize(Http2StreamId stream_id) const {
+  return nghttp2_session_get_stream_remote_window_size(session_->raw_ptr(),
+                                                       stream_id);
+}
+
 int NgHttp2Adapter::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const {
   return nghttp2_session_get_stream_effective_local_window_size(
       session_->raw_ptr(), stream_id);
diff --git a/http2/adapter/nghttp2_adapter.h b/http2/adapter/nghttp2_adapter.h
index 26b41e6..6f8f703 100644
--- a/http2/adapter/nghttp2_adapter.h
+++ b/http2/adapter/nghttp2_adapter.h
@@ -50,6 +50,8 @@
   void Send() override;
 
   int GetSendWindowSize() const override;
+  int GetStreamSendWindowSize(Http2StreamId stream_id) const override;
+
   int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const override;
   int GetStreamReceiveWindowSize(Http2StreamId stream_id) const override;
   int GetReceiveWindowSize() const override;
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index 59ec4ea..0ff57af 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -290,10 +290,41 @@
   EXPECT_EQ(kInitialFlowControlWindowSize,
             adapter->GetStreamReceiveWindowLimit(stream_id));
 
+  // Some data was sent, so the remaining send window size should be less than
+  // the default.
+  EXPECT_LT(adapter->GetStreamSendWindowSize(stream_id),
+            kInitialFlowControlWindowSize);
+  EXPECT_GT(adapter->GetStreamSendWindowSize(stream_id), 0);
+  // Send window for a nonexistent stream is not available.
+  EXPECT_EQ(-1, adapter->GetStreamSendWindowSize(stream_id + 2));
+
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
                                             spdy::SpdyFrameType::DATA}));
   EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody));
+  visitor.Clear();
   EXPECT_FALSE(adapter->session().want_write());
+
+  stream_id =
+      adapter->SubmitRequest(ToHeaders({{":method", "POST"},
+                                        {":scheme", "http"},
+                                        {":authority", "example.com"},
+                                        {":path", "/this/is/request/one"}}),
+                             nullptr, nullptr);
+  EXPECT_GT(stream_id, 0);
+  EXPECT_TRUE(adapter->session().want_write());
+  const char* kSentinel2 = "arbitrary pointer 2";
+  EXPECT_EQ(nullptr, adapter->GetStreamUserData(stream_id));
+  adapter->SetStreamUserData(stream_id, const_cast<char*>(kSentinel2));
+
+  adapter->Send();
+  EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+
+  EXPECT_EQ(kSentinel2, adapter->GetStreamUserData(stream_id));
+
+  // No data was sent (just HEADERS), so the remaining send window size should
+  // still be the default.
+  EXPECT_EQ(adapter->GetStreamSendWindowSize(stream_id),
+            kInitialFlowControlWindowSize);
 }
 
 // This is really a test of the MakeZeroCopyDataFrameSource adapter, but I
@@ -604,7 +635,7 @@
 
   EXPECT_FALSE(adapter->session().want_write());
   const absl::string_view kBody = "This is an example response body.";
-  TestDataFrameSource body1(visitor, kBody);
+  TestDataFrameSource body1(visitor, kBody, /*has_fin=*/false);
   int submit_result = adapter->SubmitResponse(
       1,
       ToHeaders({{":status", "404"},
@@ -618,13 +649,19 @@
   adapter->SetStreamUserData(1, nullptr);
   EXPECT_EQ(nullptr, adapter->GetStreamUserData(1));
 
-  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
   adapter->Send();
 
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS,
                                             spdy::SpdyFrameType::DATA}));
   EXPECT_THAT(visitor.data(), testing::HasSubstr(kBody));
   EXPECT_FALSE(adapter->session().want_write());
+
+  // Some data was sent, so the remaining send window size should be less than
+  // the default.
+  EXPECT_LT(adapter->GetStreamSendWindowSize(1), kInitialFlowControlWindowSize);
+  EXPECT_GT(adapter->GetStreamSendWindowSize(1), 0);
+  // Send window for a nonexistent stream is not available.
+  EXPECT_EQ(adapter->GetStreamSendWindowSize(3), -1);
 }
 
 // Should also test: client attempts shutdown, server attempts shutdown after an
diff --git a/http2/adapter/oghttp2_adapter.cc b/http2/adapter/oghttp2_adapter.cc
index d507c0c..e5b89e5 100644
--- a/http2/adapter/oghttp2_adapter.cc
+++ b/http2/adapter/oghttp2_adapter.cc
@@ -87,6 +87,10 @@
   return session_->GetRemoteWindowSize();
 }
 
+int OgHttp2Adapter::GetStreamSendWindowSize(Http2StreamId stream_id) const {
+  return session_->GetStreamSendWindowSize(stream_id);
+}
+
 int OgHttp2Adapter::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const {
   return session_->GetStreamReceiveWindowLimit(stream_id);
 }
diff --git a/http2/adapter/oghttp2_adapter.h b/http2/adapter/oghttp2_adapter.h
index 7e9ee7b..c15f20e 100644
--- a/http2/adapter/oghttp2_adapter.h
+++ b/http2/adapter/oghttp2_adapter.h
@@ -36,6 +36,7 @@
   void SubmitMetadata(Http2StreamId stream_id, bool fin) override;
   void Send() override;
   int GetSendWindowSize() const override;
+  int GetStreamSendWindowSize(Http2StreamId stream_id) const override;
   int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const override;
   int GetStreamReceiveWindowSize(Http2StreamId stream_id) const override;
   int GetReceiveWindowSize() const override;
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 27bca42..e8b85fa 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -66,6 +66,14 @@
   return true;
 }
 
+int OgHttp2Session::GetStreamSendWindowSize(Http2StreamId stream_id) const {
+  auto it = stream_map_.find(stream_id);
+  if (it != stream_map_.end()) {
+    return it->second.send_window;
+  }
+  return -1;
+}
+
 int OgHttp2Session::GetStreamReceiveWindowLimit(Http2StreamId stream_id) const {
   auto it = stream_map_.find(stream_id);
   if (it != stream_map_.end()) {
@@ -329,6 +337,8 @@
     if (iter->second.half_closed_remote) {
       visitor_.OnCloseStream(stream_id, Http2ErrorCode::NO_ERROR);
     }
+    // TODO(birenroy): the server adapter should probably delete stream state
+    // when calling visitor_.OnCloseStream.
   } else {
     // Add data source to stream state
     iter->second.outbound_body = data_source;
@@ -587,6 +597,8 @@
           stream_id, spdy::SpdyErrorCode::ERROR_CODE_NO_ERROR));
     }
     visitor_.OnCloseStream(stream_id, Http2ErrorCode::NO_ERROR);
+    // TODO(birenroy): the server adapter should probably delete stream state
+    // when calling visitor_.OnCloseStream.
   }
 }
 
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index f325f10..68dca30 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -57,6 +57,9 @@
   // Resumes a stream that was previously blocked. Returns true on success.
   bool ResumeStream(Http2StreamId stream_id);
 
+  // Returns the peer's outstanding stream receive window for the given stream.
+  int GetStreamSendWindowSize(Http2StreamId stream_id) const;
+
   // Returns the current upper bound on the flow control receive window for this
   // stream.
   int GetStreamReceiveWindowLimit(Http2StreamId stream_id) const;
diff --git a/http2/adapter/oghttp2_session_test.cc b/http2/adapter/oghttp2_session_test.cc
index 40f279d..ae18323 100644
--- a/http2/adapter/oghttp2_session_test.cc
+++ b/http2/adapter/oghttp2_session_test.cc
@@ -230,6 +230,14 @@
   visitor.Clear();
   EXPECT_FALSE(session.want_write());
 
+  // Some data was sent, so the remaining send window size should be less than
+  // the default.
+  EXPECT_LT(session.GetStreamSendWindowSize(stream_id),
+            kInitialFlowControlWindowSize);
+  EXPECT_GT(session.GetStreamSendWindowSize(stream_id), 0);
+  // Send window for a nonexistent stream is not available.
+  EXPECT_EQ(-1, session.GetStreamSendWindowSize(stream_id + 2));
+
   stream_id =
       session.SubmitRequest(ToHeaders({{":method", "POST"},
                                        {":scheme", "http"},
@@ -245,6 +253,11 @@
 
   session.Send();
   EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::HEADERS}));
+
+  // No data was sent (just HEADERS), so the remaining send window size should
+  // still be the default.
+  EXPECT_EQ(session.GetStreamSendWindowSize(stream_id),
+            kInitialFlowControlWindowSize);
 }
 
 // This test exercises the case where the client request body source is read
@@ -536,7 +549,8 @@
   visitor.Clear();
 
   EXPECT_FALSE(session.want_write());
-  TestDataFrameSource body1(visitor, "This is an example response body.");
+  TestDataFrameSource body1(visitor, "This is an example response body.",
+                            /*has_fin=*/false);
   int submit_result = session.SubmitResponse(
       1,
       ToHeaders({{":status", "404"},
@@ -550,11 +564,16 @@
   session.SetStreamUserData(1, nullptr);
   EXPECT_EQ(nullptr, session.GetStreamUserData(1));
 
-  EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::NO_ERROR));
   session.Send();
   EXPECT_THAT(visitor.data(),
               EqualsFrames({SpdyFrameType::HEADERS, SpdyFrameType::DATA}));
   EXPECT_FALSE(session.want_write());
+
+  // Some data was sent, so the remaining send window size should be less than
+  // the default.
+  EXPECT_LT(session.GetStreamSendWindowSize(1), kInitialFlowControlWindowSize);
+  // Send window for a nonexistent stream is not available.
+  EXPECT_EQ(session.GetStreamSendWindowSize(3), -1);
 }
 
 TEST(OgHttp2SessionTest, ServerStartShutdown) {