Support local vs remote flow control with TLS, and fix initial limits

This CL fixes a bug found during interop testing where we were failing to communicate with clients that do not send initial_max_stream_data_bidi_local. (When sent by clients, this is the transport parameter that governs the server-initiated streams which are not used by HTTP/3.) All of our tests sent the same value for _local and _remote so we did not notice that we had them backwards. This CL fixes the issue and adds tests to prevent regressions. This CL also fixes an issue where the initial limits were incorrectly set for versions that AllowsLowFlowControlLimits().

gfe-relnote: change QUIC flow control transport parameters, protected by disabled TLS flag.
PiperOrigin-RevId: 273843552
Change-Id: Id8bb3a59029ee9442ebf163fd658f38c47024950
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index be5c5ee..307b02f 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -314,28 +314,48 @@
   }
 
   void set_client_initial_stream_flow_control_receive_window(uint32_t window) {
-    CHECK(client_ == nullptr);
+    ASSERT_TRUE(client_ == nullptr);
     QUIC_DLOG(INFO) << "Setting client initial stream flow control window: "
                     << window;
     client_config_.SetInitialStreamFlowControlWindowToSend(window);
   }
 
   void set_client_initial_session_flow_control_receive_window(uint32_t window) {
-    CHECK(client_ == nullptr);
+    ASSERT_TRUE(client_ == nullptr);
     QUIC_DLOG(INFO) << "Setting client initial session flow control window: "
                     << window;
     client_config_.SetInitialSessionFlowControlWindowToSend(window);
   }
 
+  void set_client_initial_max_stream_data_incoming_bidirectional(
+      uint32_t window) {
+    ASSERT_TRUE(client_ == nullptr);
+    QUIC_DLOG(INFO)
+        << "Setting client initial max stream data incoming bidirectional: "
+        << window;
+    client_config_.SetInitialMaxStreamDataBytesIncomingBidirectionalToSend(
+        window);
+  }
+
+  void set_server_initial_max_stream_data_outgoing_bidirectional(
+      uint32_t window) {
+    ASSERT_TRUE(client_ == nullptr);
+    QUIC_DLOG(INFO)
+        << "Setting server initial max stream data outgoing bidirectional: "
+        << window;
+    server_config_.SetInitialMaxStreamDataBytesOutgoingBidirectionalToSend(
+        window);
+  }
+
   void set_server_initial_stream_flow_control_receive_window(uint32_t window) {
-    CHECK(server_thread_ == nullptr);
+    ASSERT_TRUE(server_thread_ == nullptr);
     QUIC_DLOG(INFO) << "Setting server initial stream flow control window: "
                     << window;
     server_config_.SetInitialStreamFlowControlWindowToSend(window);
   }
 
   void set_server_initial_session_flow_control_receive_window(uint32_t window) {
-    CHECK(server_thread_ == nullptr);
+    ASSERT_TRUE(server_thread_ == nullptr);
     QUIC_DLOG(INFO) << "Setting server initial session flow control window: "
                     << window;
     server_config_.SetInitialSessionFlowControlWindowToSend(window);
@@ -983,6 +1003,31 @@
   EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
 }
 
+TEST_P(EndToEndTestWithTls,
+       ClientDoesNotAllowServerDataOnServerInitiatedBidirectionalStreams) {
+  set_client_initial_max_stream_data_incoming_bidirectional(0);
+  ASSERT_TRUE(Initialize());
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls,
+       ServerDoesNotAllowClientDataOnServerInitiatedBidirectionalStreams) {
+  set_server_initial_max_stream_data_outgoing_bidirectional(0);
+  ASSERT_TRUE(Initialize());
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
+TEST_P(EndToEndTestWithTls,
+       BothEndpointsDisallowDataOnServerInitiatedBidirectionalStreams) {
+  set_client_initial_max_stream_data_incoming_bidirectional(0);
+  set_server_initial_max_stream_data_outgoing_bidirectional(0);
+  ASSERT_TRUE(Initialize());
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+  EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+}
+
 // Regression test for a bug where we would always fail to decrypt the first
 // initial packet. Undecryptable packets can be seen after the handshake
 // is complete due to dropping the initial keys at that point, so we only test
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc
index 3d9de83..4d3c0e8 100644
--- a/quic/core/http/quic_send_control_stream_test.cc
+++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -78,6 +78,8 @@
     send_control_stream_ = QuicSpdySessionPeer::GetSendControlStream(&session_);
     QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
         session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
     QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
         session_.config(), 3);
     session_.OnConfigNegotiated();
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 5ffa216..0f73e62 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -335,6 +335,12 @@
     }
     QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
         session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesIncomingBidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
     session_.OnConfigNegotiated();
     connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
     TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 327b952..5e88aba 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -213,6 +213,12 @@
     session_->ActivateStream(QuicWrapUnique(stream2_));
     QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
         session_->config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
+        session_->config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesIncomingBidirectional(
+        session_->config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+        session_->config(), kMinimumFlowControlSendWindow);
     QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
         session_->config(), 10);
     session_->OnConfigNegotiated();
diff --git a/quic/core/qpack/qpack_send_stream_test.cc b/quic/core/qpack/qpack_send_stream_test.cc
index 241c0c5..092aa12 100644
--- a/quic/core/qpack/qpack_send_stream_test.cc
+++ b/quic/core/qpack/qpack_send_stream_test.cc
@@ -59,6 +59,8 @@
     session_.Initialize();
     QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
         session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
     QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
         session_.config(), 3);
     session_.OnConfigNegotiated();
diff --git a/quic/core/quic_config.cc b/quic/core/quic_config.cc
index 65e2c58..1b529fa 100644
--- a/quic/core/quic_config.cc
+++ b/quic/core/quic_config.cc
@@ -624,6 +624,19 @@
     window_bytes = kMinimumFlowControlSendWindow;
   }
   initial_stream_flow_control_window_bytes_.SetSendValue(window_bytes);
+
+  // If the IETF flow control configs have not been set yet, set them.
+  if (!initial_max_stream_data_bytes_incoming_bidirectional_.HasSendValue()) {
+    initial_max_stream_data_bytes_incoming_bidirectional_.SetSendValue(
+        window_bytes);
+  }
+  if (!initial_max_stream_data_bytes_outgoing_bidirectional_.HasSendValue()) {
+    initial_max_stream_data_bytes_outgoing_bidirectional_.SetSendValue(
+        window_bytes);
+  }
+  if (!initial_max_stream_data_bytes_unidirectional_.HasSendValue()) {
+    initial_max_stream_data_bytes_unidirectional_.SetSendValue(window_bytes);
+  }
 }
 
 uint32_t QuicConfig::GetInitialStreamFlowControlWindowToSend() const {
@@ -640,12 +653,6 @@
 
 void QuicConfig::SetInitialMaxStreamDataBytesIncomingBidirectionalToSend(
     uint32_t window_bytes) {
-  if (window_bytes < kMinimumFlowControlSendWindow) {
-    QUIC_BUG << "Initial stream flow control receive window (" << window_bytes
-             << ") cannot be set lower than minimum ("
-             << kMinimumFlowControlSendWindow << ").";
-    window_bytes = kMinimumFlowControlSendWindow;
-  }
   initial_max_stream_data_bytes_incoming_bidirectional_.SetSendValue(
       window_bytes);
 }
@@ -669,12 +676,6 @@
 
 void QuicConfig::SetInitialMaxStreamDataBytesOutgoingBidirectionalToSend(
     uint32_t window_bytes) {
-  if (window_bytes < kMinimumFlowControlSendWindow) {
-    QUIC_BUG << "Initial stream flow control receive window (" << window_bytes
-             << ") cannot be set lower than minimum ("
-             << kMinimumFlowControlSendWindow << ").";
-    window_bytes = kMinimumFlowControlSendWindow;
-  }
   initial_max_stream_data_bytes_outgoing_bidirectional_.SetSendValue(
       window_bytes);
 }
@@ -698,12 +699,6 @@
 
 void QuicConfig::SetInitialMaxStreamDataBytesUnidirectionalToSend(
     uint32_t window_bytes) {
-  if (window_bytes < kMinimumFlowControlSendWindow) {
-    QUIC_BUG << "Initial stream flow control receive window (" << window_bytes
-             << ") cannot be set lower than minimum ("
-             << kMinimumFlowControlSendWindow << ").";
-    window_bytes = kMinimumFlowControlSendWindow;
-  }
   initial_max_stream_data_bytes_unidirectional_.SetSendValue(window_bytes);
 }
 
@@ -939,10 +934,16 @@
   params->max_packet_size.set_value(kMaxIncomingPacketSize);
   params->initial_max_data.set_value(
       initial_session_flow_control_window_bytes_.GetSendValue());
+  // The max stream data bidirectional transport parameters can be either local
+  // or remote. A stream is local iff it is initiated by the endpoint that sent
+  // the transport parameter (see the Transport Parameter Definitions section of
+  // draft-ietf-quic-transport). In this function we are sending transport
+  // parameters, so a local stream is one we initiated, which means an outgoing
+  // stream.
   params->initial_max_stream_data_bidi_local.set_value(
-      initial_max_stream_data_bytes_incoming_bidirectional_.GetSendValue());
-  params->initial_max_stream_data_bidi_remote.set_value(
       initial_max_stream_data_bytes_outgoing_bidirectional_.GetSendValue());
+  params->initial_max_stream_data_bidi_remote.set_value(
+      initial_max_stream_data_bytes_incoming_bidirectional_.GetSendValue());
   params->initial_max_stream_data_uni.set_value(
       initial_max_stream_data_bytes_unidirectional_.GetSendValue());
   params->initial_max_streams_bidi.set_value(
@@ -1033,6 +1034,12 @@
       std::min<uint64_t>(params.initial_max_streams_uni.value(),
                          std::numeric_limits<uint32_t>::max()));
 
+  // The max stream data bidirectional transport parameters can be either local
+  // or remote. A stream is local iff it is initiated by the endpoint that sent
+  // the transport parameter (see the Transport Parameter Definitions section of
+  // draft-ietf-quic-transport). However in this function we are processing
+  // received transport parameters, so a local stream is one initiated by our
+  // peer, which means an incoming stream.
   initial_max_stream_data_bytes_incoming_bidirectional_.SetReceivedValue(
       std::min<uint64_t>(params.initial_max_stream_data_bidi_local.value(),
                          std::numeric_limits<uint32_t>::max()));
diff --git a/quic/core/quic_config.h b/quic/core/quic_config.h
index 25522b3..b2316bb 100644
--- a/quic/core/quic_config.h
+++ b/quic/core/quic_config.h
@@ -314,6 +314,7 @@
   // There are two sets, one for unidirectional streams and one for
   // bidirectional. The bidirectional set also covers Google-QUICs
   // dynamic stream count (which are bidirectional streams).
+  // TODO(b/142351095) rename these to improve clarity.
   void SetMaxIncomingBidirectionalStreamsToSend(uint32_t max_streams);
   uint32_t GetMaxIncomingBidirectionalStreamsToSend();
   bool HasReceivedMaxIncomingBidirectionalStreams();
@@ -376,13 +377,16 @@
   uint32_t GetInitialRoundTripTimeUsToSend() const;
 
   // Sets an initial stream flow control window size to transmit to the peer.
+  // This sets the Google QUIC flow control window size, and additionally,
+  // if they are not yet set, sets the three IETF stream max data limits.
   void SetInitialStreamFlowControlWindowToSend(uint32_t window_bytes);
   uint32_t GetInitialStreamFlowControlWindowToSend() const;
   bool HasReceivedInitialStreamFlowControlWindowBytes() const;
   uint32_t ReceivedInitialStreamFlowControlWindowBytes() const;
 
   // Specifies the initial flow control window (max stream data) for
-  // incoming bidirectional streams.
+  // incoming bidirectional streams. Incoming means streams initiated by our
+  // peer.
   void SetInitialMaxStreamDataBytesIncomingBidirectionalToSend(
       uint32_t window_bytes);
   uint32_t GetInitialMaxStreamDataBytesIncomingBidirectionalToSend() const;
@@ -390,7 +394,7 @@
   uint32_t ReceivedInitialMaxStreamDataBytesIncomingBidirectional() const;
 
   // Specifies the initial flow control window (max stream data) for
-  // outgoing bidirectional streams.
+  // outgoing bidirectional streams. Outgoing means streams initiated by us.
   void SetInitialMaxStreamDataBytesOutgoingBidirectionalToSend(
       uint32_t window_bytes);
   uint32_t GetInitialMaxStreamDataBytesOutgoingBidirectionalToSend() const;
diff --git a/quic/core/quic_config_test.cc b/quic/core/quic_config_test.cc
index 2f5aa7a..070e809 100644
--- a/quic/core/quic_config_test.cc
+++ b/quic/core/quic_config_test.cc
@@ -366,9 +366,9 @@
   config_.FillTransportParameters(&params);
 
   EXPECT_EQ(2 * kMinimumFlowControlSendWindow,
-            params.initial_max_stream_data_bidi_local.value());
-  EXPECT_EQ(3 * kMinimumFlowControlSendWindow,
             params.initial_max_stream_data_bidi_remote.value());
+  EXPECT_EQ(3 * kMinimumFlowControlSendWindow,
+            params.initial_max_stream_data_bidi_local.value());
   EXPECT_EQ(4 * kMinimumFlowControlSendWindow,
             params.initial_max_stream_data_uni.value());
 
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index cfe7b3f..f4e70d0 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -1042,12 +1042,30 @@
     stream_id_manager_.set_max_open_incoming_streams(max_incoming_streams);
   }
 
-  if (config_.HasReceivedInitialStreamFlowControlWindowBytes()) {
-    // Streams which were created before the SHLO was received (0-RTT
-    // requests) are now informed of the peer's initial flow control window.
-    OnNewStreamFlowControlWindow(
-        config_.ReceivedInitialStreamFlowControlWindowBytes());
+  if (connection_->version().handshake_protocol == PROTOCOL_TLS1_3) {
+    // When using IETF-style TLS transport parameters, inform existing streams
+    // of new flow-control limits.
+    if (config_.HasReceivedInitialMaxStreamDataBytesOutgoingBidirectional()) {
+      OnNewStreamOutgoingBidirectionalFlowControlWindow(
+          config_.ReceivedInitialMaxStreamDataBytesOutgoingBidirectional());
+    }
+    if (config_.HasReceivedInitialMaxStreamDataBytesIncomingBidirectional()) {
+      OnNewStreamIncomingBidirectionalFlowControlWindow(
+          config_.ReceivedInitialMaxStreamDataBytesIncomingBidirectional());
+    }
+    if (config_.HasReceivedInitialMaxStreamDataBytesUnidirectional()) {
+      OnNewStreamUnidirectionalFlowControlWindow(
+          config_.ReceivedInitialMaxStreamDataBytesUnidirectional());
+    }
+  } else {  // The version uses Google QUIC Crypto.
+    if (config_.HasReceivedInitialStreamFlowControlWindowBytes()) {
+      // Streams which were created before the SHLO was received (0-RTT
+      // requests) are now informed of the peer's initial flow control window.
+      OnNewStreamFlowControlWindow(
+          config_.ReceivedInitialStreamFlowControlWindowBytes());
+    }
   }
+
   if (config_.HasReceivedInitialSessionFlowControlWindowBytes()) {
     OnNewSessionFlowControlWindow(
         config_.ReceivedInitialSessionFlowControlWindowBytes());
@@ -1061,6 +1079,11 @@
     QuicConnection::ScopedPacketFlusher flusher(connection());
     v99_streamid_manager_.OnConfigNegotiated();
   }
+
+  // Ask flow controllers to try again since the config could have unblocked us.
+  if (connection_->version().AllowsLowFlowControlLimits()) {
+    OnCanWrite();
+  }
 }
 
 void QuicSession::AdjustInitialFlowControlWindows(size_t stream_window) {
@@ -1132,6 +1155,68 @@
   }
 }
 
+void QuicSession::OnNewStreamUnidirectionalFlowControlWindow(
+    QuicStreamOffset new_window) {
+  QUIC_DVLOG(1) << ENDPOINT << "OnNewStreamUnidirectionalFlowControlWindow "
+                << new_window;
+  // Inform all existing outgoing unidirectional streams about the new window.
+  for (auto const& kv : stream_map_) {
+    const QuicStreamId id = kv.first;
+    if (QuicUtils::IsBidirectionalStreamId(id)) {
+      continue;
+    }
+    if (!QuicUtils::IsOutgoingStreamId(connection_->version(), id,
+                                       perspective())) {
+      continue;
+    }
+    QUIC_DVLOG(1) << ENDPOINT << "Informing unidirectional stream " << id
+                  << " of new stream flow control window " << new_window;
+    kv.second->UpdateSendWindowOffset(new_window);
+  }
+}
+
+void QuicSession::OnNewStreamOutgoingBidirectionalFlowControlWindow(
+    QuicStreamOffset new_window) {
+  QUIC_DVLOG(1) << ENDPOINT
+                << "OnNewStreamOutgoingBidirectionalFlowControlWindow "
+                << new_window;
+  // Inform all existing outgoing bidirectional streams about the new window.
+  for (auto const& kv : stream_map_) {
+    const QuicStreamId id = kv.first;
+    if (!QuicUtils::IsBidirectionalStreamId(id)) {
+      continue;
+    }
+    if (!QuicUtils::IsOutgoingStreamId(connection_->version(), id,
+                                       perspective())) {
+      continue;
+    }
+    QUIC_DVLOG(1) << ENDPOINT << "Informing outgoing bidirectional stream "
+                  << id << " of new stream flow control window " << new_window;
+    kv.second->UpdateSendWindowOffset(new_window);
+  }
+}
+
+void QuicSession::OnNewStreamIncomingBidirectionalFlowControlWindow(
+    QuicStreamOffset new_window) {
+  QUIC_DVLOG(1) << ENDPOINT
+                << "OnNewStreamIncomingBidirectionalFlowControlWindow "
+                << new_window;
+  // Inform all existing incoming bidirectional streams about the new window.
+  for (auto const& kv : stream_map_) {
+    const QuicStreamId id = kv.first;
+    if (!QuicUtils::IsBidirectionalStreamId(id)) {
+      continue;
+    }
+    if (QuicUtils::IsOutgoingStreamId(connection_->version(), id,
+                                      perspective())) {
+      continue;
+    }
+    QUIC_DVLOG(1) << ENDPOINT << "Informing incoming bidirectional stream "
+                  << id << " of new stream flow control window " << new_window;
+    kv.second->UpdateSendWindowOffset(new_window);
+  }
+}
+
 void QuicSession::OnNewSessionFlowControlWindow(QuicStreamOffset new_window) {
   if (new_window < kMinimumFlowControlSendWindow &&
       !connection_->version().AllowsLowFlowControlLimits()) {
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 1805387..59b3ee2 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -605,6 +605,20 @@
   // control window in a negotiated config. Closes the connection if invalid.
   void OnNewStreamFlowControlWindow(QuicStreamOffset new_window);
 
+  // Called in OnConfigNegotiated when we receive a new unidirectional stream
+  // flow control window in a negotiated config.
+  void OnNewStreamUnidirectionalFlowControlWindow(QuicStreamOffset new_window);
+
+  // Called in OnConfigNegotiated when we receive a new outgoing bidirectional
+  // stream flow control window in a negotiated config.
+  void OnNewStreamOutgoingBidirectionalFlowControlWindow(
+      QuicStreamOffset new_window);
+
+  // Called in OnConfigNegotiated when we receive a new incoming bidirectional
+  // stream flow control window in a negotiated config.
+  void OnNewStreamIncomingBidirectionalFlowControlWindow(
+      QuicStreamOffset new_window);
+
   // Called in OnConfigNegotiated when we receive a new connection level flow
   // control window in a negotiated config. Closes the connection if invalid.
   void OnNewSessionFlowControlWindow(QuicStreamOffset new_window);
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index b2ea0b2..a3069ec 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -347,14 +347,20 @@
     session_.config()->SetInitialSessionFlowControlWindowToSend(
         kInitialSessionFlowControlWindowForTest);
 
-    QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(
-        session_.config(), kDefaultMaxStreamsPerConnection);
-    QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
-        session_.config(), kDefaultMaxStreamsPerConnection);
-    QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
-        session_.config(), kMinimumFlowControlSendWindow);
-    connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
     if (configure_session) {
+      QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(
+          session_.config(), kDefaultMaxStreamsPerConnection);
+      QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
+          session_.config(), kDefaultMaxStreamsPerConnection);
+      QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
+          session_.config(), kMinimumFlowControlSendWindow);
+      QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesIncomingBidirectional(
+          session_.config(), kMinimumFlowControlSendWindow);
+      QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+          session_.config(), kMinimumFlowControlSendWindow);
+      QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
+          session_.config(), kMinimumFlowControlSendWindow);
+      connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
       session_.OnConfigNegotiated();
     }
     TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
@@ -1711,7 +1717,7 @@
   QuicConfigPeer::SetReceivedInitialStreamFlowControlWindow(session_.config(),
                                                             kInvalidWindow);
 
-  if (!connection_->version().AllowsLowFlowControlLimits()) {
+  if (connection_->version().handshake_protocol != PROTOCOL_TLS1_3) {
     EXPECT_CALL(*connection_,
                 CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
   } else {
@@ -2869,6 +2875,30 @@
   EXPECT_TRUE(session_.CanOpenNextOutgoingUnidirectionalStream());
 }
 
+TEST_P(QuicSessionTestClientUnconfigured, StreamInitiallyBlockedThenUnblocked) {
+  if (!connection_->version().AllowsLowFlowControlLimits()) {
+    return;
+  }
+  // Create a stream before negotiating the config and verify it starts off
+  // blocked.
+  TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
+  EXPECT_TRUE(stream2->flow_controller()->IsBlocked());
+  EXPECT_TRUE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
+
+  // Negotiate the config with higher received limits.
+  QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+      session_.config(), kMinimumFlowControlSendWindow);
+  QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
+      session_.config(), kMinimumFlowControlSendWindow);
+  session_.OnConfigNegotiated();
+
+  // Stream is now unblocked.
+  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+  EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
+  EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc
index ae302b9..d708b13 100644
--- a/quic/core/quic_stream.cc
+++ b/quic/core/quic_stream.cc
@@ -25,6 +25,13 @@
 
 namespace {
 
+size_t DefaultFlowControlWindow(ParsedQuicVersion version) {
+  if (!version.AllowsLowFlowControlLimits()) {
+    return kDefaultFlowControlSendWindow;
+  }
+  return 0;
+}
+
 size_t GetInitialStreamFlowControlWindowToSend(QuicSession* session,
                                                QuicStreamId stream_id) {
   ParsedQuicVersion version = session->connection()->version();
@@ -39,9 +46,8 @@
         ->GetInitialMaxStreamDataBytesUnidirectionalToSend();
   }
 
-  if (session->perspective() == Perspective::IS_SERVER &&
-      QuicUtils::IsServerInitiatedStreamId(version.transport_version,
-                                           stream_id)) {
+  if (QuicUtils::IsOutgoingStreamId(version, stream_id,
+                                    session->perspective())) {
     return session->config()
         ->GetInitialMaxStreamDataBytesOutgoingBidirectionalToSend();
   }
@@ -58,7 +64,7 @@
       return session->config()->ReceivedInitialStreamFlowControlWindowBytes();
     }
 
-    return kDefaultFlowControlSendWindow;
+    return DefaultFlowControlWindow(version);
   }
 
   // Unidirectional streams (v99 only).
@@ -69,27 +75,28 @@
       return session->config()
           ->ReceivedInitialMaxStreamDataBytesUnidirectional();
     }
-    return kDefaultFlowControlSendWindow;
+
+    return DefaultFlowControlWindow(version);
   }
 
-  if (session->perspective() == Perspective::IS_SERVER &&
-      QuicUtils::IsServerInitiatedStreamId(version.transport_version,
-                                           stream_id)) {
+  if (QuicUtils::IsOutgoingStreamId(version, stream_id,
+                                    session->perspective())) {
     if (session->config()
-            ->HasReceivedInitialMaxStreamDataBytesIncomingBidirectional()) {
+            ->HasReceivedInitialMaxStreamDataBytesOutgoingBidirectional()) {
       return session->config()
-          ->ReceivedInitialMaxStreamDataBytesIncomingBidirectional();
+          ->ReceivedInitialMaxStreamDataBytesOutgoingBidirectional();
     }
-    return kDefaultFlowControlSendWindow;
+
+    return DefaultFlowControlWindow(version);
   }
 
   if (session->config()
-          ->HasReceivedInitialMaxStreamDataBytesOutgoingBidirectional()) {
+          ->HasReceivedInitialMaxStreamDataBytesIncomingBidirectional()) {
     return session->config()
-        ->ReceivedInitialMaxStreamDataBytesOutgoingBidirectional();
+        ->ReceivedInitialMaxStreamDataBytesIncomingBidirectional();
   }
 
-  return kDefaultFlowControlSendWindow;
+  return DefaultFlowControlWindow(version);
 }
 
 }  // namespace
diff --git a/quic/core/quic_stream_test.cc b/quic/core/quic_stream_test.cc
index 54ba983..3719638 100644
--- a/quic/core/quic_stream_test.cc
+++ b/quic/core/quic_stream_test.cc
@@ -88,6 +88,12 @@
 
     QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
         session_->config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
+        session_->config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesIncomingBidirectional(
+        session_->config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+        session_->config(), kMinimumFlowControlSendWindow);
     QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
         session_->config(), 10);
     session_->OnConfigNegotiated();
diff --git a/quic/core/quic_utils.cc b/quic/core/quic_utils.cc
index 70692f5..d0d93ff 100644
--- a/quic/core/quic_utils.cc
+++ b/quic/core/quic_utils.cc
@@ -397,6 +397,19 @@
 }
 
 // static
+bool QuicUtils::IsOutgoingStreamId(ParsedQuicVersion version,
+                                   QuicStreamId id,
+                                   Perspective perspective) {
+  // Streams are outgoing streams, iff:
+  // - we are the server and the stream is server-initiated
+  // - we are the client and the stream is client-initiated.
+  const bool perspective_is_server = perspective == Perspective::IS_SERVER;
+  const bool stream_is_server =
+      QuicUtils::IsServerInitiatedStreamId(version.transport_version, id);
+  return perspective_is_server == stream_is_server;
+}
+
+// static
 bool QuicUtils::IsBidirectionalStreamId(QuicStreamId id) {
   return id % 4 < 2;
 }
diff --git a/quic/core/quic_utils.h b/quic/core/quic_utils.h
index 25ab7bb..f04dace 100644
--- a/quic/core/quic_utils.h
+++ b/quic/core/quic_utils.h
@@ -133,6 +133,12 @@
   static bool IsServerInitiatedStreamId(QuicTransportVersion version,
                                         QuicStreamId id);
 
+  // Returns true if the stream ID represents a stream initiated by the
+  // provided perspective.
+  static bool IsOutgoingStreamId(ParsedQuicVersion version,
+                                 QuicStreamId id,
+                                 Perspective perspective);
+
   // Returns true if |id| is considered as bidirectional stream ID. Only used in
   // v99.
   static bool IsBidirectionalStreamId(QuicStreamId id);
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index 1116cc9..c5ce7a3 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -566,8 +566,10 @@
         QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
             &config_, kStreamFlowControlWindowSize);
       } else {
+        // In this version, push streams are server-initiated bidirectional
+        // streams, which are outgoing since we are the server here.
         QuicConfigPeer::
-            SetReceivedInitialMaxStreamDataBytesIncomingBidirectional(
+            SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
                 &config_, kStreamFlowControlWindowSize);
       }
     } else {
diff --git a/quic/tools/quic_simple_server_stream_test.cc b/quic/tools/quic_simple_server_stream_test.cc
index f738db6..533ab4e 100644
--- a/quic/tools/quic_simple_server_stream_test.cc
+++ b/quic/tools/quic_simple_server_stream_test.cc
@@ -214,6 +214,12 @@
     session_.ActivateStream(QuicWrapUnique(stream_));
     QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
         session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesUnidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesIncomingBidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
+    QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+        session_.config(), kMinimumFlowControlSendWindow);
     QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
         session_.config(), 10);
     session_.OnConfigNegotiated();