Implement restrictions at https://tools.ietf.org/html/draft-ietf-quic-transport-27#section-7.3.1.

This prepares for 0-rtt on the client side.

This change should be no-op for current deployment in gfe because config is currently only set once.

gfe-relnote: unused code. not protected.
PiperOrigin-RevId: 308861599
Change-Id: I0c8182f3d11330f40024dca76de9fed6d383f8d1
diff --git a/quic/core/quic_flow_controller.h b/quic/core/quic_flow_controller.h
index e627c8c..29c3c02 100644
--- a/quic/core/quic_flow_controller.h
+++ b/quic/core/quic_flow_controller.h
@@ -91,6 +91,8 @@
 
   QuicByteCount bytes_consumed() const { return bytes_consumed_; }
 
+  QuicStreamOffset send_window_offset() const { return send_window_offset_; }
+
   QuicStreamOffset highest_received_byte_offset() const {
     return highest_received_byte_offset_;
   }
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 4862d50..3524242 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -1119,6 +1119,18 @@
     QUIC_DVLOG(1) << ENDPOINT
                   << "Setting Bidirectional outgoing_max_streams_ to "
                   << max_streams;
+    if (perspective_ == Perspective::IS_CLIENT &&
+        max_streams <
+            v99_streamid_manager_.max_outgoing_bidirectional_streams()) {
+      connection_->CloseConnection(
+          QUIC_MAX_STREAMS_ERROR,
+          quiche::QuicheStrCat(
+              "new bidirectional limit ", max_streams,
+              " decreases the current limit: ",
+              v99_streamid_manager_.max_outgoing_bidirectional_streams()),
+          ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+      return;
+    }
     if (v99_streamid_manager_.MaybeAllowNewOutgoingBidirectionalStreams(
             max_streams)) {
       OnCanCreateNewOutgoingStream(/*unidirectional = */ false);
@@ -1128,6 +1140,8 @@
     if (config_.HasReceivedMaxUnidirectionalStreams()) {
       max_streams = config_.ReceivedMaxUnidirectionalStreams();
     }
+    // TODO(b/153726130): remove this check and
+    // num_expected_unidirectional_static_streams_.
     if (max_streams < num_expected_unidirectional_static_streams_) {
       QUIC_DLOG(ERROR) << "Received unidirectional stream limit of "
                        << max_streams << " < "
@@ -1138,6 +1152,18 @@
           ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
       return;
     }
+    if (perspective_ == Perspective::IS_CLIENT &&
+        max_streams <
+            v99_streamid_manager_.max_outgoing_unidirectional_streams()) {
+      connection_->CloseConnection(
+          QUIC_MAX_STREAMS_ERROR,
+          quiche::QuicheStrCat(
+              "new unidirectional limit ", max_streams,
+              " decreases the current limit: ",
+              v99_streamid_manager_.max_outgoing_unidirectional_streams()),
+          ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+      return;
+    }
     QUIC_DVLOG(1) << ENDPOINT
                   << "Setting Unidirectional outgoing_max_streams_ to "
                   << max_streams;
@@ -1310,6 +1336,7 @@
 }
 
 void QuicSession::OnNewStreamFlowControlWindow(QuicStreamOffset new_window) {
+  DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_QUIC_CRYPTO);
   QUIC_DVLOG(1) << ENDPOINT << "OnNewStreamFlowControlWindow " << new_window;
   if (new_window < kMinimumFlowControlSendWindow &&
       !connection_->version().AllowsLowFlowControlLimits()) {
@@ -1326,19 +1353,22 @@
   for (auto const& kv : stream_map_) {
     QUIC_DVLOG(1) << ENDPOINT << "Informing stream " << kv.first
                   << " of new stream flow control window " << new_window;
-    kv.second->UpdateSendWindowOffset(new_window);
+    if (!kv.second->ConfigSendWindowOffset(new_window)) {
+      return;
+    }
   }
   if (!QuicVersionUsesCryptoFrames(transport_version())) {
     QUIC_DVLOG(1)
         << ENDPOINT
         << "Informing crypto stream of new stream flow control window "
         << new_window;
-    GetMutableCryptoStream()->UpdateSendWindowOffset(new_window);
+    GetMutableCryptoStream()->ConfigSendWindowOffset(new_window);
   }
 }
 
 void QuicSession::OnNewStreamUnidirectionalFlowControlWindow(
     QuicStreamOffset new_window) {
+  DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_TLS1_3);
   QUIC_DVLOG(1) << ENDPOINT << "OnNewStreamUnidirectionalFlowControlWindow "
                 << new_window;
   // Inform all existing outgoing unidirectional streams about the new window.
@@ -1353,12 +1383,15 @@
     }
     QUIC_DVLOG(1) << ENDPOINT << "Informing unidirectional stream " << id
                   << " of new stream flow control window " << new_window;
-    kv.second->UpdateSendWindowOffset(new_window);
+    if (!kv.second->ConfigSendWindowOffset(new_window)) {
+      return;
+    }
   }
 }
 
 void QuicSession::OnNewStreamOutgoingBidirectionalFlowControlWindow(
     QuicStreamOffset new_window) {
+  DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_TLS1_3);
   QUIC_DVLOG(1) << ENDPOINT
                 << "OnNewStreamOutgoingBidirectionalFlowControlWindow "
                 << new_window;
@@ -1374,12 +1407,15 @@
     }
     QUIC_DVLOG(1) << ENDPOINT << "Informing outgoing bidirectional stream "
                   << id << " of new stream flow control window " << new_window;
-    kv.second->UpdateSendWindowOffset(new_window);
+    if (!kv.second->ConfigSendWindowOffset(new_window)) {
+      return;
+    }
   }
 }
 
 void QuicSession::OnNewStreamIncomingBidirectionalFlowControlWindow(
     QuicStreamOffset new_window) {
+  DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_TLS1_3);
   QUIC_DVLOG(1) << ENDPOINT
                 << "OnNewStreamIncomingBidirectionalFlowControlWindow "
                 << new_window;
@@ -1395,19 +1431,36 @@
     }
     QUIC_DVLOG(1) << ENDPOINT << "Informing incoming bidirectional stream "
                   << id << " of new stream flow control window " << new_window;
-    kv.second->UpdateSendWindowOffset(new_window);
+    if (!kv.second->ConfigSendWindowOffset(new_window)) {
+      return;
+    }
   }
 }
 
 void QuicSession::OnNewSessionFlowControlWindow(QuicStreamOffset new_window) {
   QUIC_DVLOG(1) << ENDPOINT << "OnNewSessionFlowControlWindow " << new_window;
-  if (new_window < kMinimumFlowControlSendWindow &&
-      !connection_->version().AllowsLowFlowControlLimits()) {
+  bool close_connection = false;
+  if (!connection_->version().AllowsLowFlowControlLimits()) {
+    if (new_window < kMinimumFlowControlSendWindow) {
+      close_connection = true;
+      QUIC_LOG_FIRST_N(ERROR, 1)
+          << "Peer sent us an invalid session flow control send window: "
+          << new_window << ", below default: " << kMinimumFlowControlSendWindow;
+    }
+  } else if (perspective_ == Perspective::IS_CLIENT &&
+             new_window < flow_controller_.send_window_offset()) {
+    // The client receives a lower limit than remembered, violating
+    // https://tools.ietf.org/html/draft-ietf-quic-transport-27#section-7.3.1
     QUIC_LOG_FIRST_N(ERROR, 1)
         << "Peer sent us an invalid session flow control send window: "
-        << new_window << ", below default: " << kMinimumFlowControlSendWindow;
+        << new_window
+        << ", below current: " << flow_controller_.send_window_offset();
+    close_connection = true;
+  }
+  if (close_connection) {
     connection_->CloseConnection(
-        QUIC_FLOW_CONTROL_INVALID_WINDOW, "New connection window too low",
+        QUIC_FLOW_CONTROL_INVALID_WINDOW,
+        quiche::QuicheStrCat("New connection window too low: ", new_window),
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
     return;
   }
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 372fa07..db24eca 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -1776,21 +1776,6 @@
   session_.OnConfigNegotiated();
 }
 
-TEST_P(QuicSessionTestServer, InvalidSessionFlowControlWindowInHandshake) {
-  // Test that receipt of an invalid (< default) session flow control window
-  // from the peer results in the connection being torn down.
-  const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1;
-  QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(),
-                                                             kInvalidWindow);
-  if (!connection_->version().AllowsLowFlowControlLimits()) {
-    EXPECT_CALL(*connection_,
-                CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
-  } else {
-    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
-  }
-  session_.OnConfigNegotiated();
-}
-
 // Test negotiation of custom server initial flow control window.
 TEST_P(QuicSessionTestServer, CustomFlowControlWindow) {
   QuicTagVector copt;
@@ -2053,6 +2038,57 @@
       &session_, GetNthClientInitiatedBidirectionalId(1)));
 }
 
+TEST_P(QuicSessionTestClient, InvalidSessionFlowControlWindowInHandshake) {
+  // Test that receipt of an invalid (< default for gQUIC, < current for TLS)
+  // session flow control window from the peer results in the connection being
+  // torn down.
+  const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1;
+  QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(),
+                                                             kInvalidWindow);
+  EXPECT_CALL(*connection_,
+              CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
+  session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSessionTestClient, InvalidBidiStreamLimitInHandshake) {
+  // IETF QUIC only feature.
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+  QuicConfigPeer::SetReceivedMaxBidirectionalStreams(
+      session_.config(), kDefaultMaxStreamsPerConnection - 1);
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAMS_ERROR, _, _));
+  session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSessionTestClient, InvalidUniStreamLimitInHandshake) {
+  // IETF QUIC only feature.
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+  QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(
+      session_.config(), kDefaultMaxStreamsPerConnection - 1);
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAMS_ERROR, _, _));
+  session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSessionTestClient, InvalidStreamFlowControlWindowInHandshake) {
+  // IETF QUIC only feature.
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+  session_.CreateOutgoingBidirectionalStream();
+  session_.CreateOutgoingBidirectionalStream();
+  QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+      session_.config(), kMinimumFlowControlSendWindow - 1);
+
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _))
+      .WillOnce(
+          Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+  EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _));
+  session_.OnConfigNegotiated();
+}
+
 TEST_P(QuicSessionTestClient, OnMaxStreamFrame) {
   if (!VersionUsesHttp3(transport_version())) {
     return;
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc
index 15212f9..c43d914 100644
--- a/quic/core/quic_stream.cc
+++ b/quic/core/quic_stream.cc
@@ -904,11 +904,22 @@
   }
 }
 
-void QuicStream::UpdateSendWindowOffset(QuicStreamOffset new_window) {
-  if (flow_controller_->UpdateSendWindowOffset(new_window)) {
+bool QuicStream::ConfigSendWindowOffset(QuicStreamOffset new_offset) {
+  if (perspective_ == Perspective::IS_CLIENT &&
+      session()->version().AllowsLowFlowControlLimits() &&
+      new_offset < flow_controller_->send_window_offset()) {
+    OnUnrecoverableError(
+        QUIC_FLOW_CONTROL_INVALID_WINDOW,
+        quiche::QuicheStrCat("New stream max data ", new_offset,
+                             " decreases current limit: ",
+                             flow_controller_->send_window_offset()));
+    return false;
+  }
+  if (flow_controller_->UpdateSendWindowOffset(new_offset)) {
     // Let session unblock this stream.
     session_->MarkConnectionLevelWriteBlocked(id_);
   }
+  return true;
 }
 
 void QuicStream::AddRandomPaddingAfterFin() {
diff --git a/quic/core/quic_stream.h b/quic/core/quic_stream.h
index 90dac69..cc7ce4d 100644
--- a/quic/core/quic_stream.h
+++ b/quic/core/quic_stream.h
@@ -244,9 +244,8 @@
   // Returns true if the highest offset did increase.
   bool MaybeIncreaseHighestReceivedOffset(QuicStreamOffset new_offset);
 
-  // Updates the flow controller's send window offset and calls OnCanWrite if
-  // it was blocked before.
-  void UpdateSendWindowOffset(QuicStreamOffset new_offset);
+  // Set the flow controller's send window offset from session config.
+  bool ConfigSendWindowOffset(QuicStreamOffset new_offset);
 
   // Returns true if the stream has received either a RST_STREAM or a FIN -
   // either of which gives a definitive number of bytes which the peer has