Prevent IETF QUIC Frame transmission prior to config

A number of IETF QUIC fames, including MAX_STREAMS and STREAMS_BLOCKED, should not be sent prior to having the session configured.

gfe-relnote: N/A IETF QUIC, protected via V99 flag.
PiperOrigin-RevId: 269611696
Change-Id: Ibed580f9bff8f716f3f78481022b5c929f5634ef
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 310081c..8ea04f8 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -1645,6 +1645,9 @@
   // it) does not count against the open quota (because it is closed from the
   // protocol point of view).
   if (VersionHasIetfQuicFrames(transport_version())) {
+    // Simulate receiving a config. so that MAX_STREAMS/etc frames may
+    // be transmitted
+    QuicSessionPeer::set_is_configured(&session_, true);
     // Version 99 will result in a MAX_STREAMS frame as streams are consumed
     // (via the OnStreamFrame call) and then released (via
     // StreamDraining). Eventually this node will believe that the peer is
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 9ede3f8..23873d0 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -1044,6 +1044,13 @@
         config_.ReceivedInitialSessionFlowControlWindowBytes());
   }
   is_configured_ = true;
+
+  // Inform stream ID manager so that it can reevaluate any deferred
+  // STREAMS_BLOCKED or MAX_STREAMS frames against the config and either send
+  // the frames or discard them.
+  if (VersionHasIetfQuicFrames(connection_->transport_version())) {
+    v99_streamid_manager_.OnConfigNegotiated();
+  }
 }
 
 void QuicSession::AdjustInitialFlowControlWindows(size_t stream_window) {
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 004af59..60178fe 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -32,6 +32,7 @@
 #include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_id_manager_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_stream_send_buffer_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
@@ -155,7 +156,6 @@
   }
 
   ~TestSession() override {
-    EXPECT_TRUE(is_configured());
     delete connection();
   }
 
@@ -284,14 +284,14 @@
     return consumed;
   }
 
+  const QuicFrame& save_frame() { return save_frame_; }
+
   bool SaveFrame(const QuicFrame& frame) {
     save_frame_ = frame;
     DeleteFrame(&const_cast<QuicFrame&>(frame));
     return true;
   }
 
-  const QuicFrame& save_frame() { return save_frame_; }
-
   QuicConsumedData SendLargeFakeData(QuicStream* stream, int bytes) {
     DCHECK(writev_consumes_all_data_);
     return WritevData(stream, stream->id(), bytes, 0, FIN);
@@ -308,6 +308,7 @@
   }
 
   using QuicSession::ActivateStream;
+  using QuicSession::CanOpenNextOutgoingUnidirectionalStream;
   using QuicSession::closed_streams;
   using QuicSession::zombie_streams;
 
@@ -322,13 +323,14 @@
 
 class QuicSessionTestBase : public QuicTestWithParam<ParsedQuicVersion> {
  protected:
-  explicit QuicSessionTestBase(Perspective perspective)
+  QuicSessionTestBase(Perspective perspective, bool configure_session)
       : connection_(
             new StrictMock<MockQuicConnection>(&helper_,
                                                &alarm_factory_,
                                                perspective,
                                                SupportedVersions(GetParam()))),
-        session_(connection_, &session_visitor_) {
+        session_(connection_, &session_visitor_),
+        configure_session_(configure_session) {
     session_.config()->SetInitialStreamFlowControlWindowToSend(
         kInitialStreamFlowControlWindowForTest);
     session_.config()->SetInitialSessionFlowControlWindowToSend(
@@ -341,12 +343,20 @@
     QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
         session_.config(), kMinimumFlowControlSendWindow);
     connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
-    session_.OnConfigNegotiated();
+    if (configure_session) {
+      session_.OnConfigNegotiated();
+    }
     TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
     EXPECT_CALL(*crypto_stream, HasPendingRetransmission())
         .Times(testing::AnyNumber());
   }
 
+  ~QuicSessionTestBase() {
+    if (configure_session_) {
+      EXPECT_TRUE(session_.is_configured());
+    }
+  }
+
   void CheckClosedStreams() {
     QuicStreamId first_stream_id = QuicUtils::GetFirstBidirectionalStreamId(
         connection_->transport_version(), Perspective::IS_CLIENT);
@@ -440,6 +450,7 @@
   StrictMock<MockQuicConnection>* connection_;
   TestSession session_;
   std::set<QuicStreamId> closed_streams_;
+  bool configure_session_;
 };
 
 class QuicSessionTestServer : public QuicSessionTestBase {
@@ -479,7 +490,7 @@
 
  protected:
   QuicSessionTestServer()
-      : QuicSessionTestBase(Perspective::IS_SERVER),
+      : QuicSessionTestBase(Perspective::IS_SERVER, /*configure_session=*/true),
         path_frame_buffer1_({0, 1, 2, 3, 4, 5, 6, 7}),
         path_frame_buffer2_({8, 9, 10, 11, 12, 13, 14, 15}),
         client_framer_(SupportedVersions(GetParam()),
@@ -1943,7 +1954,9 @@
 
 class QuicSessionTestClient : public QuicSessionTestBase {
  protected:
-  QuicSessionTestClient() : QuicSessionTestBase(Perspective::IS_CLIENT) {}
+  QuicSessionTestClient()
+      : QuicSessionTestBase(Perspective::IS_CLIENT,
+                            /*configure_session=*/true) {}
 };
 
 INSTANTIATE_TEST_SUITE_P(Tests,
@@ -2553,6 +2566,7 @@
     // Applicable only to IETF QUIC
     return;
   }
+
   QuicStreamId bidirectional_stream_id = StreamCountToId(
       QuicSessionPeer::v99_streamid_manager(&session_)
               ->advertised_max_allowed_incoming_bidirectional_streams() +
@@ -2729,6 +2743,113 @@
   }
 }
 
+// A client test class that can be used when the automatic configuration is not
+// desired.
+class QuicSessionTestClientUnconfigured : public QuicSessionTestBase {
+ protected:
+  QuicSessionTestClientUnconfigured()
+      : QuicSessionTestBase(Perspective::IS_CLIENT,
+                            /*configure_session=*/false) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+                         QuicSessionTestClientUnconfigured,
+                         ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSessionTestClientUnconfigured, HoldMaxStreamsFrame) {
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+  QuicStreamIdManager* stream_id_manager =
+      QuicSessionPeer::v99_unidirectional_stream_id_manager(&session_);
+
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  QuicStreamsBlockedFrame frame(1u, 0u, /*unidirectional=*/true);
+  session_.OnStreamsBlockedFrame(frame);
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .Times(1)
+      .WillRepeatedly(Invoke(&session_, &TestSession::SaveFrame));
+  session_.OnConfigNegotiated();
+  EXPECT_EQ(MAX_STREAMS_FRAME, session_.save_frame().type);
+  EXPECT_EQ(stream_id_manager->incoming_actual_max_streams(),
+            session_.save_frame().max_streams_frame.stream_count);
+  EXPECT_EQ(1u, session_.save_frame().max_streams_frame.control_frame_id);
+}
+
+TEST_P(QuicSessionTestClientUnconfigured, HoldStreamsBlockedFrameXmit) {
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    // Applicable only to IETF QUIC
+    return;
+  }
+  QuicStreamIdManager* stream_id_manager =
+      QuicSessionPeer::v99_unidirectional_stream_id_manager(&session_);
+
+  // Set the stream limit to 0 which will cause
+  // CanOpenNextOutgoingUnidirectionalStream()
+  // to generated a STREAMS_BLOCKED frame.
+  QuicStreamIdManagerPeer::set_outgoing_max_streams(stream_id_manager, 0);
+
+  // Since the stream limit is 0 and no sreams can be created this should return
+  // false and have forced a streams-blocked to be queued up, with the
+  // blocked stream id == 0.
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  EXPECT_FALSE(session_.CanOpenNextOutgoingUnidirectionalStream());
+
+  // We will expect two calls to SendControlFrame: The first is because
+  // OnConfigNegotiated does not increase the limit, so the app still can not
+  // create new streams (and therefore needs the STREAMS-BLOCKED to go out). The
+  // second is because the ensuing CanOpenNext.. call will fail (this test not
+  // actually increasing the limit) and that will send another STREAMS-BLOCKED.
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .Times(2)
+      .WillRepeatedly(Invoke(&session_, &TestSession::SaveFrame));
+  // Set configuration data so that when the config happens, the stream limit is
+  // not increased and another STREAMS-BLOCKED will be needed..
+  QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(session_.config(),
+                                                              0);
+
+  session_.OnConfigNegotiated();
+
+  EXPECT_EQ(STREAMS_BLOCKED_FRAME, session_.save_frame().type);
+  EXPECT_EQ(0u, session_.save_frame().streams_blocked_frame.stream_count);
+  EXPECT_EQ(1u, session_.save_frame().streams_blocked_frame.control_frame_id);
+
+  EXPECT_FALSE(session_.CanOpenNextOutgoingUnidirectionalStream());
+  EXPECT_EQ(STREAMS_BLOCKED_FRAME, session_.save_frame().type);
+  EXPECT_EQ(0u, session_.save_frame().streams_blocked_frame.stream_count);
+  EXPECT_EQ(2u, session_.save_frame().streams_blocked_frame.control_frame_id);
+}
+
+TEST_P(QuicSessionTestClientUnconfigured, HoldStreamsBlockedFrameNoXmit) {
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+  QuicStreamIdManager* stream_id_manager =
+      QuicSessionPeer::v99_unidirectional_stream_id_manager(&session_);
+
+  // Set the stream limit to 0 which will cause
+  // CanOpenNextOutgoingUnidirectionalStream()
+  // to generated a STREAMS_BLOCKED frame.
+  QuicStreamIdManagerPeer::set_outgoing_max_streams(stream_id_manager, 0);
+
+  // Since the stream limit is 0 and no streams can be created this should
+  // return false and have forced a streams-blocked to be queued up, with the
+  // blocked stream id == 0.
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  EXPECT_FALSE(session_.CanOpenNextOutgoingUnidirectionalStream());
+
+  // Set configuration data so that when the config happens, the stream limit is
+  // increased.
+  QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(session_.config(),
+                                                              10);
+
+  // STREAMS_BLOCKED frame should not be sent because streams can now be
+  // created.
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  session_.OnConfigNegotiated();
+  EXPECT_TRUE(session_.CanOpenNextOutgoingUnidirectionalStream());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_stream_id_manager.cc b/quic/core/quic_stream_id_manager.cc
index c400d77..9d94157 100644
--- a/quic/core/quic_stream_id_manager.cc
+++ b/quic/core/quic_stream_id_manager.cc
@@ -39,7 +39,10 @@
       incoming_stream_count_(0),
       largest_peer_created_stream_id_(
           QuicUtils::GetInvalidStreamId(transport_version())),
-      max_streams_window_(0) {
+      max_streams_window_(0),
+      pending_max_streams_(false),
+      pending_streams_blocked_(
+          QuicUtils::GetInvalidStreamId(transport_version())) {
   CalculateIncomingMaxStreamsWindow();
 }
 
@@ -50,20 +53,11 @@
   // Ensure that the frame has the correct directionality.
   DCHECK_EQ(frame.unidirectional, unidirectional_);
   QUIC_CODE_COUNT_N(quic_max_streams_received, 2, 2);
-  const QuicStreamCount current_outgoing_max_streams = outgoing_max_streams_;
 
   // Set the limit to be exactly the stream count in the frame.
-  if (!SetMaxOpenOutgoingStreams(frame.stream_count)) {
-    return false;
-  }
-  // If we were at the previous limit and this MAX_STREAMS frame
-  // increased the limit, inform the application that new streams are
-  // available.
-  if (outgoing_stream_count_ == current_outgoing_max_streams &&
-      current_outgoing_max_streams < outgoing_max_streams_) {
-    session_->OnCanCreateNewOutgoingStream(unidirectional_);
-  }
-  return true;
+  // Also informs the higher layers that they can create more
+  // streams if the limit is increased.
+  return SetMaxOpenOutgoingStreams(frame.stream_count);
 }
 
 // The peer sends a streams blocked frame when it can not open any more
@@ -140,6 +134,11 @@
   outgoing_max_streams_ = std::min<size_t>(
       max_open_streams,
       QuicUtils::GetMaxStreamCount(unidirectional_, session_->perspective()));
+
+  // Inform the higher layers that the stream limit has increased and that
+  // new streams may be created.
+  session_->OnCanCreateNewOutgoingStream(unidirectional_);
+
   return true;
 }
 
@@ -171,6 +170,13 @@
 }
 
 void QuicStreamIdManager::SendMaxStreamsFrame() {
+  if (!session_->is_configured()) {
+    // Session not configured, so we can not send the MAX STREAMS frame yet.
+    // Remember that we would have sent one and then return. A new frame will
+    // be generated once the configuration is received.
+    pending_max_streams_ = true;
+    return;
+  }
   incoming_advertised_max_streams_ = incoming_actual_max_streams_;
   session_->SendMaxStreams(incoming_advertised_max_streams_, unidirectional_);
 }
@@ -213,6 +219,15 @@
     return true;
   }
   // Next stream ID would exceed the limit, need to inform the peer.
+
+  if (!session_->is_configured()) {
+    // Session not configured, so we can not send the STREAMS_BLOCKED frame yet.
+    // Remember that we would have sent one, and what the limit was when we were
+    // blocked, and return.
+    pending_streams_blocked_ = outgoing_max_streams_;
+    return false;
+  }
+
   session_->SendStreamsBlocked(outgoing_max_streams_, unidirectional_);
   QUIC_CODE_COUNT(quic_reached_outgoing_stream_id_limit);
   return false;
@@ -342,4 +357,25 @@
   }
 }
 
+void QuicStreamIdManager::OnConfigNegotiated() {
+  QuicConnection::ScopedPacketFlusher flusher(session_->connection());
+  // If a STREAMS_BLOCKED or MAX_STREAMS is pending, send it and clear
+  // the pending state.
+  if (pending_streams_blocked_ !=
+      QuicUtils::GetInvalidStreamId(transport_version())) {
+    if (pending_streams_blocked_ >= outgoing_max_streams_) {
+      // There is a pending STREAMS_BLOCKED frame and the current limit does not
+      // let new streams be formed. Regenerate and send the frame.
+      session_->SendStreamsBlocked(outgoing_max_streams_, unidirectional_);
+    }
+    pending_streams_blocked_ =
+        QuicUtils::GetInvalidStreamId(transport_version());
+  }
+  if (pending_max_streams_) {
+    // Generate a MAX_STREAMS using the current stream limits.
+    SendMaxStreamsFrame();
+    pending_max_streams_ = false;
+  }
+}
+
 }  // namespace quic
diff --git a/quic/core/quic_stream_id_manager.h b/quic/core/quic_stream_id_manager.h
index baba5ac..51524ba 100644
--- a/quic/core/quic_stream_id_manager.h
+++ b/quic/core/quic_stream_id_manager.h
@@ -149,6 +149,10 @@
 
   QuicTransportVersion transport_version() const;
 
+  // Called when session has been configured. Causes the Stream ID manager to
+  // send out any pending MAX_STREAMS and STREAMS_BLOCKED frames.
+  void OnConfigNegotiated();
+
  private:
   friend class test::QuicSessionPeer;
   friend class test::QuicStreamIdManagerPeer;
@@ -226,6 +230,13 @@
   // max_streams_window_ is set to 1/2 of the initial number of incoming streams
   // that are allowed (as set in the constructor).
   QuicStreamId max_streams_window_;
+
+  // MAX_STREAMS and STREAMS_BLOCKED frames are not sent before the session has
+  // been configured. Instead, the relevant information is stored in
+  // |pending_max_streams_| and |pending_streams_blocked_| and sent when
+  // OnConfigNegotiated() is invoked.
+  bool pending_max_streams_;
+  QuicStreamId pending_streams_blocked_;
 };
 }  // namespace quic
 
diff --git a/quic/core/quic_stream_id_manager_test.cc b/quic/core/quic_stream_id_manager_test.cc
index 06eb141..67f20f4 100644
--- a/quic/core/quic_stream_id_manager_test.cc
+++ b/quic/core/quic_stream_id_manager_test.cc
@@ -56,6 +56,9 @@
  public:
   TestQuicSession(QuicConnection* connection)
       : MockQuicSession(connection, /*create_mock_crypto_stream=*/true) {
+    // Initialize to an invalid frame type to detect cases where the frame type
+    // is not set subsequently.
+    save_frame_.type = static_cast<QuicFrameType>(-1);
     Initialize();
   }
 
@@ -277,6 +280,10 @@
   QuicStreamCount stream_count =
       stream_id_manager_->incoming_initial_max_open_streams() - 1;
   QuicStreamsBlockedFrame frame(0, stream_count, /*unidirectional=*/false);
+
+  // Simulate being configured so that the MAX_STREAMS is transmitted.
+  session_->OnConfigNegotiated();
+
   session_->OnStreamsBlockedFrame(frame);
 
   // We should see a MAX_STREAMS frame.
@@ -395,6 +402,9 @@
   // Get the current maximum allowed incoming stream count.
   QuicStreamCount advertised_stream_count =
       stream_id_manager_->incoming_advertised_max_streams();
+  // Simulate receiving a config to allow frame transmission
+  session_->OnConfigNegotiated();
+
   QuicStreamsBlockedFrame frame;
 
   frame.unidirectional = IsUnidi();
@@ -457,6 +467,15 @@
   // Number of streams we can open and the first one we should get when
   // opening...
   size_t number_of_streams = kDefaultMaxStreamsPerConnection;
+
+  // Set up config to allow the default stream limit and then
+  // simulate receiving a config to allow frame transmission
+  QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
+      session_->config(), kDefaultMaxStreamsPerConnection);
+  QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(
+      session_->config(), kDefaultMaxStreamsPerConnection);
+  session_->OnConfigNegotiated();
+
   QuicStreamId stream_id =
       IsUnidi() ? session_->next_outgoing_unidirectional_stream_id()
                 : session_->next_outgoing_bidirectional_stream_id();
@@ -510,6 +529,9 @@
 
 // Test the MAX STREAMS Window functionality.
 TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerServerMaxStreams) {
+  // Simulate completed config to allow frame transmission
+  session_->OnConfigNegotiated();
+
   // Test that a MAX_STREAMS frame is generated when the peer has less than
   // |max_streams_window_| streams left that it can initiate.
 
@@ -585,6 +607,9 @@
 // Check that edge conditions of the stream count in a STREAMS_BLOCKED frame
 // are. properly handled.
 TEST_P(QuicStreamIdManagerTestClient, StreamsBlockedEdgeConditions) {
+  // Simulate completed config to allow frame transmission
+  session_->OnConfigNegotiated();
+
   QuicStreamsBlockedFrame frame;
   frame.unidirectional = IsUnidi();
 
@@ -607,6 +632,28 @@
   EXPECT_EQ(123u, session_->save_frame().max_streams_frame.stream_count);
 }
 
+TEST_P(QuicStreamIdManagerTestClient, HoldMaxStreamsFrame) {
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+
+  // The session has not been configured so frame transmission will not be
+  // allowed.
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+
+  QuicStreamsBlockedFrame frame(
+      1u, 0u, QuicStreamIdManagerPeer::get_unidirectional(stream_id_manager_));
+  // Should cause change in pending_max_streams.
+  stream_id_manager_->OnStreamsBlockedFrame(frame);
+  // Will do OnConfig. We should see a control frame pop out now.
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+
+  // Now allow frame transmission -- which happens when the QuicSession
+  // receives the configuration and calls
+  // QuicStreamIdManager::OnConfigNegotiated()
+  session_->OnConfigNegotiated();
+}
+
 // Following tests all are server-specific. They depend, in some way, on
 // server-specific attributes, such as the initial stream ID.
 
@@ -616,6 +663,64 @@
       : QuicStreamIdManagerTestBase(Perspective::IS_SERVER) {}
 };
 
+TEST_P(QuicStreamIdManagerTestClient, HoldStreamsBlockedFrameXmit) {
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+
+  // set outgoing limit to 0, will cause the CanOpenNext... to fail
+  // leading to a STREAMS_BLOCKED.
+  QuicStreamIdManagerPeer::set_outgoing_max_streams(stream_id_manager_, 0);
+
+  // We should not see a STREAMS-BLOCKED frame because we're not configured..
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+
+  // Since the stream limit is 0 and no sreams can be created this should return
+  // false and have forced a streams-blocked to be queued up, with the
+  // blocked stream id == 0.
+  EXPECT_FALSE(stream_id_manager_->CanOpenNextOutgoingStream());
+
+  // Simulate receipt of the configuration; This case does not update the
+  // outgoing stream limit, so the on-config should result in a streams-blocked
+  // being sent.
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+  QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
+      session_->config(), 0);
+  QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(session_->config(),
+                                                             0);
+  session_->OnConfigNegotiated();
+}
+
+TEST_P(QuicStreamIdManagerTestClient, HoldStreamsBlockedFrameNoXmit) {
+  if (!VersionHasIetfQuicFrames(transport_version())) {
+    return;
+  }
+  // Set outgoing limit to 0, will cause the CanOpenNext... to fail
+  // leading to a STREAMS_BLOCKED.
+  QuicStreamIdManagerPeer::set_outgoing_max_streams(stream_id_manager_, 0);
+
+  // We should not see a STREAMS-BLOCKED frame because we're not configured..
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+
+  // Since the stream limit is 0 and no sreams can be created this should return
+  // false and have forced a streams-blocked to be queued up, with the
+  // blocked stream id == 0.
+  EXPECT_FALSE(stream_id_manager_->CanOpenNextOutgoingStream());
+
+  // Since the config gives some streams to create, we should not see
+  // a STREAMS-BLOCKED frame.
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+
+  // Do the configuration.  The stream limits are increased, allowing this
+  // node to create more streams, so we should not see the pending
+  // STREAMS-BLOCKED frame get transmitted.
+  QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
+      session_->config(), 10);
+  QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(session_->config(),
+                                                             10);
+  session_->OnConfigNegotiated();
+}
+
 INSTANTIATE_TEST_SUITE_P(Tests, QuicStreamIdManagerTestServer, testing::Bool());
 
 // This test checks that the initialization for the maximum allowed outgoing
@@ -677,6 +782,13 @@
 // Tast that an attempt to create an outgoing stream does not exceed the limit
 // and that it generates an appropriate STREAMS_BLOCKED frame.
 TEST_P(QuicStreamIdManagerTestServer, NewStreamDoesNotExceedLimit) {
+  // Configure with some number of streams, and allow frame transmission
+  QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
+      session_->config(), 100);
+  QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(session_->config(),
+                                                             100);
+  session_->OnConfigNegotiated();
+
   size_t stream_count = stream_id_manager_->outgoing_max_streams();
   EXPECT_NE(0u, stream_count);
   TestQuicStream* stream;
diff --git a/quic/core/uber_quic_stream_id_manager.h b/quic/core/uber_quic_stream_id_manager.h
index 61eaf62..4db221a 100644
--- a/quic/core/uber_quic_stream_id_manager.h
+++ b/quic/core/uber_quic_stream_id_manager.h
@@ -83,6 +83,11 @@
   QuicStreamCount advertised_max_allowed_incoming_unidirectional_streams()
       const;
 
+  void OnConfigNegotiated() {
+    bidirectional_stream_id_manager_.OnConfigNegotiated();
+    unidirectional_stream_id_manager_.OnConfigNegotiated();
+  }
+
  private:
   friend class test::QuicSessionPeer;
   friend class test::UberQuicStreamIdManagerPeer;
diff --git a/quic/core/uber_quic_stream_id_manager_test.cc b/quic/core/uber_quic_stream_id_manager_test.cc
index 6d4990e..3362139 100644
--- a/quic/core/uber_quic_stream_id_manager_test.cc
+++ b/quic/core/uber_quic_stream_id_manager_test.cc
@@ -313,6 +313,8 @@
 }
 
 TEST_P(UberQuicStreamIdManagerTest, OnStreamsBlockedFrame) {
+  // Allow MAX_STREAMS frame transmission
+  QuicSessionPeer::set_is_configured(session_.get(), true);
   // Set up to capture calls to SendControlFrame - when a STREAMS_BLOCKED
   // frame is received, it will result in a a new MAX_STREAMS frame being
   // sent (if new streams can be made available).
diff --git a/quic/test_tools/quic_session_peer.cc b/quic/test_tools/quic_session_peer.cc
index 22ccb28..001f1ca 100644
--- a/quic/test_tools/quic_session_peer.cc
+++ b/quic/test_tools/quic_session_peer.cc
@@ -241,5 +241,10 @@
   return it == session->pending_stream_map_.end() ? nullptr : it->second.get();
 }
 
+// static
+void QuicSessionPeer::set_is_configured(QuicSession* session, bool value) {
+  session->is_configured_ = value;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_session_peer.h b/quic/test_tools/quic_session_peer.h
index 79338fc..eed3bdd 100644
--- a/quic/test_tools/quic_session_peer.h
+++ b/quic/test_tools/quic_session_peer.h
@@ -86,6 +86,7 @@
                                  bool close_write_side_only);
   static PendingStream* GetPendingStream(QuicSession* session,
                                          QuicStreamId stream_id);
+  static void set_is_configured(QuicSession* session, bool value);
 };
 
 }  // namespace test
diff --git a/quic/test_tools/quic_stream_id_manager_peer.cc b/quic/test_tools/quic_stream_id_manager_peer.cc
index 3ce5f1f..6c08ece 100644
--- a/quic/test_tools/quic_stream_id_manager_peer.cc
+++ b/quic/test_tools/quic_stream_id_manager_peer.cc
@@ -20,10 +20,23 @@
 }
 
 // static
+void QuicStreamIdManagerPeer::set_outgoing_max_streams(
+    QuicStreamIdManager* stream_id_manager,
+    QuicStreamCount count) {
+  stream_id_manager->outgoing_max_streams_ = count;
+}
+
+// static
 QuicStreamId QuicStreamIdManagerPeer::GetFirstIncomingStreamId(
     QuicStreamIdManager* stream_id_manager) {
   return stream_id_manager->GetFirstIncomingStreamId();
 }
 
+// static
+bool QuicStreamIdManagerPeer::get_unidirectional(
+    QuicStreamIdManager* stream_id_manager) {
+  return stream_id_manager->unidirectional_;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_stream_id_manager_peer.h b/quic/test_tools/quic_stream_id_manager_peer.h
index cc78aee..4c97512 100644
--- a/quic/test_tools/quic_stream_id_manager_peer.h
+++ b/quic/test_tools/quic_stream_id_manager_peer.h
@@ -22,9 +22,13 @@
   static void set_incoming_actual_max_streams(
       QuicStreamIdManager* stream_id_manager,
       QuicStreamCount count);
+  static void set_outgoing_max_streams(QuicStreamIdManager* stream_id_manager,
+                                       QuicStreamCount count);
 
   static QuicStreamId GetFirstIncomingStreamId(
       QuicStreamIdManager* stream_id_manager);
+
+  static bool get_unidirectional(QuicStreamIdManager* stream_id_manager);
 };
 
 }  // namespace test
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 8ba9615..ba5e1cd 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -632,6 +632,12 @@
   MOCK_CONST_METHOD0(IsCryptoHandshakeConfirmed, bool());
   MOCK_CONST_METHOD0(ShouldKeepConnectionAlive, bool());
   MOCK_METHOD2(SendStopSending, void(uint16_t code, QuicStreamId stream_id));
+#ifdef undefined
+  MOCK_METHOD2(SendMaxStreams,
+               void(QuicStreamCount stream_count, bool unidirectional));
+  MOCK_METHOD2(SendStreamsBlocked,
+               void(QuicStreamCount stream_count, bool unidirectional));
+#endif
   MOCK_METHOD1(OnCryptoHandshakeEvent, void(QuicSession::CryptoHandshakeEvent));
   MOCK_CONST_METHOD0(GetAlpnsToOffer, std::vector<std::string>());
   MOCK_CONST_METHOD1(SelectAlpn,