In v99, close the connection if the peer sets the number of unidirectional stream less than the minimum required unidirectional streams.

The spec is reflected at https://quicwg.org/base-drafts/draft-ietf-quic-http.html#rfc.section.6.2

gfe-relnote: v99 only, not protected.
PiperOrigin-RevId: 267241053
Change-Id: I0fc9e909e90d023b5b99cc1a5bb6a37d5c592ab6
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 55de8b6..4b14600 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -13,6 +13,7 @@
 #include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h"
 #include "net/third_party/quiche/src/quic/core/http/http_constants.h"
+#include "net/third_party/quiche/src/quic/core/quic_config.h"
 #include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h"
 #include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
 #include "net/third_party/quiche/src/quic/core/quic_packets.h"
@@ -1514,6 +1515,23 @@
   session_.OnConfigNegotiated();
 }
 
+TEST_P(QuicSpdySessionTestServer, TooLowUnidirectionalStreamLimitHttp3) {
+  if (!VersionUsesQpack(transport_version()) ||
+      GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
+    // TODO(nharper, b/112643533): Figure out why this test fails when TLS is
+    // enabled and fix it.
+    return;
+  }
+
+  QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(session_.config(),
+                                                              2u);
+
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(_, "New unidirectional stream limit is too low.", _));
+  session_.OnConfigNegotiated();
+}
+
 // Test negotiation of custom server initial flow control window.
 TEST_P(QuicSpdySessionTestServer, CustomFlowControlWindow) {
   QuicTagVector copt;
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 23935b3..464c4f5 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -963,7 +963,7 @@
 void QuicSession::OnConfigNegotiated() {
   connection_->SetFromConfig(config_);
 
-  if (VersionHasIetfQuicFrames(connection_->transport_version())) {
+  if (VersionHasIetfQuicFrames(transport_version())) {
     uint32_t max_streams = 0;
     if (config_.HasReceivedMaxIncomingBidirectionalStreams()) {
       max_streams = config_.ReceivedMaxIncomingBidirectionalStreams();
diff --git a/quic/core/quic_stream_id_manager.cc b/quic/core/quic_stream_id_manager.cc
index 440b97a..f856ab0 100644
--- a/quic/core/quic_stream_id_manager.cc
+++ b/quic/core/quic_stream_id_manager.cc
@@ -98,6 +98,19 @@
 // Used when configuration has been done and we have an initial
 // maximum stream count from the peer.
 bool QuicStreamIdManager::SetMaxOpenOutgoingStreams(size_t max_open_streams) {
+  if (unidirectional_ &&
+      max_open_streams <
+          session_->num_expected_unidirectional_static_streams()) {
+    // Requirement can be found at
+    // https://tools.ietf.org/html/draft-ietf-quic-http-22#section-6.2.
+    QUIC_DLOG(ERROR) << "Received max unidirectional stream "
+                     << max_open_streams << " < "
+                     << session_->num_expected_unidirectional_static_streams();
+    session_->connection()->CloseConnection(
+        QUIC_MAX_STREAMS_ERROR, "New unidirectional stream limit is too low.",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return false;
+  }
   if (using_default_max_streams_) {
     // This is the first MAX_STREAMS/transport negotiation we've received. Treat
     // this a bit differently than later ones. The difference is that