Add QuicStreamsBlockedFrame and QuicMaxStreamsFrame classes

There are two main parts to this work
1) connecting up the new frames, replacing the old ones, to move data around the
   system. This also entails a lot of editorial changes (just changing names, comments,
   and so on, without notable logic chanages -- eg, "OnMaxStreamIdFrame" becomes
   "OnMaxStreamsFrame".

2) the second, and more complex, task is revising the stream id manager to work entirely
   with stream counts rather than stream-ids. For example, the logic to check whether a
   new stream can be created checks if the current-stream-count is less than the limit
   or not, rather than if the next stream id to hand out is above the limit or not.
   For all intents and purposes, this completely rewrote the stream ID manager.

   Another big change resulting from keeping track solely of stream counts is that the
   stream ID manager doesn't care whether it is doing unidirectional or bidirectional
   streams, nor does it care whether stream ids are client- or server- initiated.

gfe-relnote: N/A, all changes are for V99/IETF QUIC code only.

LOG_STORAGE_INCREASE(GB/week): 0
TMPLOG_STORAGE_INCREASE(GB): 0

This change neither adds nor deletes data stored. It adds two new codepoints to the QUIC FrameType enum.  These new enums reflect two new frames defined in IETF QUIC, which replace two now-deprecated frames (and their associated type codepoints). This is a name change/type codepoint extension; data is neither added nor deleted.

PiperOrigin-RevId: 244661277
Change-Id: I07cdb79db6bd15e1d5ece97b3aa2d67e94ccf00b
diff --git a/quic/core/chlo_extractor.cc b/quic/core/chlo_extractor.cc
index 4ce5d8c..5c46910 100644
--- a/quic/core/chlo_extractor.cc
+++ b/quic/core/chlo_extractor.cc
@@ -60,8 +60,8 @@
   bool OnPathChallengeFrame(const QuicPathChallengeFrame& frame) override;
   bool OnPathResponseFrame(const QuicPathResponseFrame& frame) override;
   bool OnGoAwayFrame(const QuicGoAwayFrame& frame) override;
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override;
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override;
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override;
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override;
   bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override;
   bool OnBlockedFrame(const QuicBlockedFrame& frame) override;
   bool OnPaddingFrame(const QuicPaddingFrame& frame) override;
@@ -263,12 +263,12 @@
   return false;
 }
 
-bool ChloFramerVisitor::OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) {
+bool ChloFramerVisitor::OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) {
   return true;
 }
 
-bool ChloFramerVisitor::OnStreamIdBlockedFrame(
-    const QuicStreamIdBlockedFrame& frame) {
+bool ChloFramerVisitor::OnStreamsBlockedFrame(
+    const QuicStreamsBlockedFrame& frame) {
   return true;
 }
 
diff --git a/quic/core/frames/quic_frame.cc b/quic/core/frames/quic_frame.cc
index 95fee41..dbc2ff6 100644
--- a/quic/core/frames/quic_frame.cc
+++ b/quic/core/frames/quic_frame.cc
@@ -51,10 +51,10 @@
 QuicFrame::QuicFrame(QuicRetireConnectionIdFrame* frame)
     : type(RETIRE_CONNECTION_ID_FRAME), retire_connection_id_frame(frame) {}
 
-QuicFrame::QuicFrame(QuicMaxStreamIdFrame frame) : max_stream_id_frame(frame) {}
+QuicFrame::QuicFrame(QuicMaxStreamsFrame frame) : max_streams_frame(frame) {}
 
-QuicFrame::QuicFrame(QuicStreamIdBlockedFrame frame)
-    : stream_id_blocked_frame(frame) {}
+QuicFrame::QuicFrame(QuicStreamsBlockedFrame frame)
+    : streams_blocked_frame(frame) {}
 
 QuicFrame::QuicFrame(QuicPathResponseFrame* frame)
     : type(PATH_RESPONSE_FRAME), path_response_frame(frame) {}
@@ -84,9 +84,9 @@
     case PADDING_FRAME:
     case MTU_DISCOVERY_FRAME:
     case PING_FRAME:
-    case MAX_STREAM_ID_FRAME:
+    case MAX_STREAMS_FRAME:
     case STOP_WAITING_FRAME:
-    case STREAM_ID_BLOCKED_FRAME:
+    case STREAMS_BLOCKED_FRAME:
     case STREAM_FRAME:
       break;
     case ACK_FRAME:
@@ -154,8 +154,8 @@
     case GOAWAY_FRAME:
     case WINDOW_UPDATE_FRAME:
     case BLOCKED_FRAME:
-    case STREAM_ID_BLOCKED_FRAME:
-    case MAX_STREAM_ID_FRAME:
+    case STREAMS_BLOCKED_FRAME:
+    case MAX_STREAMS_FRAME:
     case PING_FRAME:
     case STOP_SENDING_FRAME:
       return true;
@@ -174,10 +174,10 @@
       return frame.window_update_frame->control_frame_id;
     case BLOCKED_FRAME:
       return frame.blocked_frame->control_frame_id;
-    case STREAM_ID_BLOCKED_FRAME:
-      return frame.stream_id_blocked_frame.control_frame_id;
-    case MAX_STREAM_ID_FRAME:
-      return frame.max_stream_id_frame.control_frame_id;
+    case STREAMS_BLOCKED_FRAME:
+      return frame.streams_blocked_frame.control_frame_id;
+    case MAX_STREAMS_FRAME:
+      return frame.max_streams_frame.control_frame_id;
     case PING_FRAME:
       return frame.ping_frame.control_frame_id;
     case STOP_SENDING_FRAME:
@@ -204,11 +204,11 @@
     case PING_FRAME:
       frame->ping_frame.control_frame_id = control_frame_id;
       return;
-    case STREAM_ID_BLOCKED_FRAME:
-      frame->stream_id_blocked_frame.control_frame_id = control_frame_id;
+    case STREAMS_BLOCKED_FRAME:
+      frame->streams_blocked_frame.control_frame_id = control_frame_id;
       return;
-    case MAX_STREAM_ID_FRAME:
-      frame->max_stream_id_frame.control_frame_id = control_frame_id;
+    case MAX_STREAMS_FRAME:
+      frame->max_streams_frame.control_frame_id = control_frame_id;
       return;
     case STOP_SENDING_FRAME:
       frame->stop_sending_frame->control_frame_id = control_frame_id;
@@ -240,11 +240,11 @@
     case STOP_SENDING_FRAME:
       copy = QuicFrame(new QuicStopSendingFrame(*frame.stop_sending_frame));
       break;
-    case STREAM_ID_BLOCKED_FRAME:
-      copy = QuicFrame(QuicStreamIdBlockedFrame(frame.stream_id_blocked_frame));
+    case STREAMS_BLOCKED_FRAME:
+      copy = QuicFrame(QuicStreamsBlockedFrame(frame.streams_blocked_frame));
       break;
-    case MAX_STREAM_ID_FRAME:
-      copy = QuicFrame(QuicMaxStreamIdFrame(frame.max_stream_id_frame));
+    case MAX_STREAMS_FRAME:
+      copy = QuicFrame(QuicMaxStreamsFrame(frame.max_streams_frame));
       break;
     default:
       QUIC_BUG << "Try to copy a non-retransmittable control frame: " << frame;
@@ -308,11 +308,11 @@
       os << "type { RETIRE_CONNECTION_ID } "
          << *(frame.retire_connection_id_frame);
       break;
-    case MAX_STREAM_ID_FRAME:
-      os << "type { MAX_STREAM_ID } " << frame.max_stream_id_frame;
+    case MAX_STREAMS_FRAME:
+      os << "type { MAX_STREAMS } " << frame.max_streams_frame;
       break;
-    case STREAM_ID_BLOCKED_FRAME:
-      os << "type { STREAM_ID_BLOCKED } " << frame.stream_id_blocked_frame;
+    case STREAMS_BLOCKED_FRAME:
+      os << "type { STREAMS_BLOCKED } " << frame.streams_blocked_frame;
       break;
     case PATH_RESPONSE_FRAME:
       os << "type { PATH_RESPONSE } " << *(frame.path_response_frame);
diff --git a/quic/core/frames/quic_frame.h b/quic/core/frames/quic_frame.h
index c452bf4..caa5fce 100644
--- a/quic/core/frames/quic_frame.h
+++ b/quic/core/frames/quic_frame.h
@@ -13,7 +13,7 @@
 #include "net/third_party/quiche/src/quic/core/frames/quic_connection_close_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_crypto_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_goaway_frame.h"
-#include "net/third_party/quiche/src/quic/core/frames/quic_max_stream_id_frame.h"
+#include "net/third_party/quiche/src/quic/core/frames/quic_max_streams_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_message_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_mtu_discovery_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_new_connection_id_frame.h"
@@ -27,7 +27,7 @@
 #include "net/third_party/quiche/src/quic/core/frames/quic_stop_sending_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_stop_waiting_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h"
-#include "net/third_party/quiche/src/quic/core/frames/quic_stream_id_blocked_frame.h"
+#include "net/third_party/quiche/src/quic/core/frames/quic_streams_blocked_frame.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_window_update_frame.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
@@ -41,9 +41,9 @@
   explicit QuicFrame(QuicPaddingFrame padding_frame);
   explicit QuicFrame(QuicMtuDiscoveryFrame frame);
   explicit QuicFrame(QuicPingFrame frame);
-  explicit QuicFrame(QuicMaxStreamIdFrame frame);
+  explicit QuicFrame(QuicMaxStreamsFrame frame);
   explicit QuicFrame(QuicStopWaitingFrame frame);
-  explicit QuicFrame(QuicStreamIdBlockedFrame frame);
+  explicit QuicFrame(QuicStreamsBlockedFrame frame);
   explicit QuicFrame(QuicStreamFrame stream_frame);
 
   explicit QuicFrame(QuicAckFrame* frame);
@@ -72,9 +72,9 @@
     QuicPaddingFrame padding_frame;
     QuicMtuDiscoveryFrame mtu_discovery_frame;
     QuicPingFrame ping_frame;
-    QuicMaxStreamIdFrame max_stream_id_frame;
+    QuicMaxStreamsFrame max_streams_frame;
     QuicStopWaitingFrame stop_waiting_frame;
-    QuicStreamIdBlockedFrame stream_id_blocked_frame;
+    QuicStreamsBlockedFrame streams_blocked_frame;
     QuicStreamFrame stream_frame;
 
     // Out of line frames.
diff --git a/quic/core/frames/quic_frames_test.cc b/quic/core/frames/quic_frames_test.cc
index c432657..bf60b7b 100644
--- a/quic/core/frames/quic_frames_test.cc
+++ b/quic/core/frames/quic_frames_test.cc
@@ -101,33 +101,37 @@
   EXPECT_TRUE(IsControlFrame(frame.type));
 }
 
-TEST_F(QuicFramesTest, StreamIdBlockedFrameToString) {
-  QuicStreamIdBlockedFrame stream_id_blocked;
-  QuicFrame frame(stream_id_blocked);
+TEST_F(QuicFramesTest, StreamsBlockedFrameToString) {
+  QuicStreamsBlockedFrame streams_blocked;
+  QuicFrame frame(streams_blocked);
   SetControlFrameId(1, &frame);
   EXPECT_EQ(1u, GetControlFrameId(frame));
-  // QuicStreamIdBlocked is copied into a QuicFrame (as opposed to putting a
+  // QuicStreamsBlocked is copied into a QuicFrame (as opposed to putting a
   // pointer to it into QuicFrame) so need to work with the copy in |frame| and
-  // not the original one, stream_id_blocked.
-  frame.stream_id_blocked_frame.stream_id = 321;
+  // not the original one, streams_blocked.
+  frame.streams_blocked_frame.stream_count = 321;
+  frame.streams_blocked_frame.unidirectional = false;
   std::ostringstream stream;
-  stream << frame.stream_id_blocked_frame;
-  EXPECT_EQ("{ control_frame_id: 1, stream id: 321 }\n", stream.str());
+  stream << frame.streams_blocked_frame;
+  EXPECT_EQ("{ control_frame_id: 1, stream count: 321, bidirectional }\n",
+            stream.str());
   EXPECT_TRUE(IsControlFrame(frame.type));
 }
 
-TEST_F(QuicFramesTest, MaxStreamIdFrameToString) {
-  QuicMaxStreamIdFrame max_stream_id;
-  QuicFrame frame(max_stream_id);
+TEST_F(QuicFramesTest, MaxStreamsFrameToString) {
+  QuicMaxStreamsFrame max_streams;
+  QuicFrame frame(max_streams);
   SetControlFrameId(1, &frame);
   EXPECT_EQ(1u, GetControlFrameId(frame));
-  // QuicMaxStreamId is copied into a QuicFrame (as opposed to putting a
+  // QuicMaxStreams is copied into a QuicFrame (as opposed to putting a
   // pointer to it into QuicFrame) so need to work with the copy in |frame| and
-  // not the original one, max_stream_id.
-  frame.max_stream_id_frame.max_stream_id = 321;
+  // not the original one, max_streams.
+  frame.max_streams_frame.stream_count = 321;
+  frame.max_streams_frame.unidirectional = true;
   std::ostringstream stream;
-  stream << frame.max_stream_id_frame;
-  EXPECT_EQ("{ control_frame_id: 1, stream_id: 321 }\n", stream.str());
+  stream << frame.max_streams_frame;
+  EXPECT_EQ("{ control_frame_id: 1, stream_count: 321, unidirectional }\n",
+            stream.str());
   EXPECT_TRUE(IsControlFrame(frame.type));
 }
 
diff --git a/quic/core/frames/quic_max_streams_frame.cc b/quic/core/frames/quic_max_streams_frame.cc
new file mode 100644
index 0000000..21dce5b
--- /dev/null
+++ b/quic/core/frames/quic_max_streams_frame.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/frames/quic_max_streams_frame.h"
+
+namespace quic {
+
+QuicMaxStreamsFrame::QuicMaxStreamsFrame()
+    : QuicInlinedFrame(MAX_STREAMS_FRAME),
+      control_frame_id(kInvalidControlFrameId),
+      unidirectional(false) {}
+
+QuicMaxStreamsFrame::QuicMaxStreamsFrame(QuicControlFrameId control_frame_id,
+                                         QuicStreamCount stream_count,
+                                         bool unidirectional)
+    : QuicInlinedFrame(MAX_STREAMS_FRAME),
+      control_frame_id(control_frame_id),
+      stream_count(stream_count),
+      unidirectional(unidirectional) {}
+
+std::ostream& operator<<(std::ostream& os, const QuicMaxStreamsFrame& frame) {
+  os << "{ control_frame_id: " << frame.control_frame_id
+     << ", stream_count: " << frame.stream_count
+     << ((frame.unidirectional) ? ", unidirectional }\n"
+                                : ", bidirectional }\n");
+  return os;
+}
+
+}  // namespace quic
diff --git a/quic/core/frames/quic_max_streams_frame.h b/quic/core/frames/quic_max_streams_frame.h
new file mode 100644
index 0000000..f8c78f9
--- /dev/null
+++ b/quic/core/frames/quic_max_streams_frame.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_FRAMES_QUIC_MAX_STREAMS_FRAME_H_
+#define QUICHE_QUIC_CORE_FRAMES_QUIC_MAX_STREAMS_FRAME_H_
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/quic/core/frames/quic_inlined_frame.h"
+#include "net/third_party/quiche/src/quic/core/quic_constants.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+// IETF format MAX_STREAMS frame.
+// This frame is used by the sender to inform the peer of the number of
+// streams that the peer may open and that the sender will accept.
+struct QUIC_EXPORT_PRIVATE QuicMaxStreamsFrame
+    : public QuicInlinedFrame<QuicMaxStreamsFrame> {
+  QuicMaxStreamsFrame();
+  QuicMaxStreamsFrame(QuicControlFrameId control_frame_id,
+                      QuicStreamCount stream_count,
+                      bool unidirectional);
+
+  friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+      std::ostream& os,
+      const QuicMaxStreamsFrame& frame);
+
+  // A unique identifier of this control frame. 0 when this frame is received,
+  // and non-zero when sent.
+  QuicControlFrameId control_frame_id;
+
+  // The number of streams that may be opened.
+  QuicStreamCount stream_count;
+  // Whether uni- or bi-directional streams
+  bool unidirectional;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_FRAMES_QUIC_MAX_STREAMS_FRAME_H_
diff --git a/quic/core/frames/quic_streams_blocked_frame.cc b/quic/core/frames/quic_streams_blocked_frame.cc
new file mode 100644
index 0000000..f0579c5
--- /dev/null
+++ b/quic/core/frames/quic_streams_blocked_frame.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/frames/quic_streams_blocked_frame.h"
+
+namespace quic {
+
+QuicStreamsBlockedFrame::QuicStreamsBlockedFrame()
+    : QuicInlinedFrame(STREAMS_BLOCKED_FRAME),
+      control_frame_id(kInvalidControlFrameId),
+      unidirectional(false) {}
+
+QuicStreamsBlockedFrame::QuicStreamsBlockedFrame(
+    QuicControlFrameId control_frame_id,
+    QuicStreamCount stream_count,
+    bool unidirectional)
+    : QuicInlinedFrame(STREAMS_BLOCKED_FRAME),
+      control_frame_id(control_frame_id),
+      stream_count(stream_count),
+      unidirectional(unidirectional) {}
+
+std::ostream& operator<<(std::ostream& os,
+                         const QuicStreamsBlockedFrame& frame) {
+  os << "{ control_frame_id: " << frame.control_frame_id
+     << ", stream count: " << frame.stream_count
+     << ((frame.unidirectional) ? ", unidirectional }\n"
+                                : ", bidirectional }\n");
+
+  return os;
+}
+
+}  // namespace quic
diff --git a/quic/core/frames/quic_streams_blocked_frame.h b/quic/core/frames/quic_streams_blocked_frame.h
new file mode 100644
index 0000000..ff7c7f4
--- /dev/null
+++ b/quic/core/frames/quic_streams_blocked_frame.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_FRAMES_QUIC_STREAMS_BLOCKED_FRAME_H_
+#define QUICHE_QUIC_CORE_FRAMES_QUIC_STREAMS_BLOCKED_FRAME_H_
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/quic/core/frames/quic_inlined_frame.h"
+#include "net/third_party/quiche/src/quic/core/quic_constants.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+// IETF format STREAMS_BLOCKED frame.
+// The sender uses this to inform the peer that the sender wished to
+// open a new stream, exceeding the limit on the number of streams.
+struct QUIC_EXPORT_PRIVATE QuicStreamsBlockedFrame
+    : public QuicInlinedFrame<QuicStreamsBlockedFrame> {
+  QuicStreamsBlockedFrame();
+  QuicStreamsBlockedFrame(QuicControlFrameId control_frame_id,
+                          QuicStreamCount stream_count,
+                          bool unidirectional);
+
+  friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+      std::ostream& os,
+      const QuicStreamsBlockedFrame& frame);
+
+  // A unique identifier of this control frame. 0 when this frame is received,
+  // and non-zero when sent.
+  QuicControlFrameId control_frame_id;
+
+  // The number of streams that the sender wishes to exceed
+  QuicStreamCount stream_count;
+
+  // Whether uni- or bi-directional streams
+  bool unidirectional;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_FRAMES_QUIC_STREAMS_BLOCKED_FRAME_H_
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 7a49700..603cd48 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -1527,9 +1527,15 @@
   server_config_.SetMaxIncomingDynamicStreamsToSend(
       kServerMaxIncomingDynamicStreams);
   ASSERT_TRUE(Initialize());
+  if (GetParam().negotiated_version.transport_version == QUIC_VERSION_99) {
+    // Do not run this test for version 99/IETF QUIC. Note that the test needs
+    // to be here, after calling Initialize(), because all tests end up calling
+    // EndToEndTest::TearDown(), which asserts that Initialize has been called
+    // and then proceeds to tear things down -- which fails if they are not
+    // properly set up.
+    return;
+  }
   EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
-  QuicConnection* client_connection =
-      client_->client()->client_session()->connection();
 
   // Make the client misbehave after negotiation.
   const int kServerMaxStreams = kMaxStreamsMinimumIncrement + 1;
@@ -1549,16 +1555,10 @@
     client_->SendMessage(headers, "", /*fin=*/false);
   }
   client_->WaitForResponse();
-  if (client_connection->transport_version() != QUIC_VERSION_99) {
-    EXPECT_TRUE(client_->connected());
-    EXPECT_EQ(QUIC_REFUSED_STREAM, client_->stream_error());
-    EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
-  } else {
-    // Version 99 disconnects the connection if we exceed the stream limit.
-    EXPECT_FALSE(client_->connected());
-    EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
-    EXPECT_EQ(QUIC_INVALID_STREAM_ID, client_->connection_error());
-  }
+
+  EXPECT_TRUE(client_->connected());
+  EXPECT_EQ(QUIC_REFUSED_STREAM, client_->stream_error());
+  EXPECT_EQ(QUIC_NO_ERROR, client_->connection_error());
 }
 
 TEST_P(EndToEndTest, SetIndependentMaxIncomingDynamicStreamsLimits) {
@@ -1574,10 +1574,19 @@
 
   // The client has received the server's limit and vice versa.
   QuicSpdyClientSession* client_session = client_->client()->client_session();
+  // The value returned by max_allowed... includes the Crypto and Header
+  // stream (created as a part of initialization). The config. values,
+  // above, are treated as "number of requests/responses" - that is, they do
+  // not include the static Crypto and Header streams. Reduce the value
+  // returned by max_allowed... by 2 to remove the static streams from the
+  // count.
   size_t client_max_open_outgoing_bidirectional_streams =
       client_session->connection()->transport_version() == QUIC_VERSION_99
           ? QuicSessionPeer::v99_streamid_manager(client_session)
-                ->max_allowed_outgoing_bidirectional_streams()
+                    ->max_allowed_outgoing_bidirectional_streams() -
+                QuicSessionPeer::v99_bidirectional_stream_id_manager(
+                    client_session)
+                    ->outgoing_static_stream_count()
           : QuicSessionPeer::GetStreamIdManager(client_session)
                 ->max_open_outgoing_streams();
   size_t client_max_open_outgoing_unidirectional_streams =
@@ -3911,6 +3920,37 @@
   EXPECT_EQ(QUIC_PACKET_WRITE_ERROR, client_->connection_error());
 }
 
+// Test that the stream id manager closes the connection if a stream
+// in excess of the allowed maximum.
+TEST_P(EndToEndTest, TooBigStreamIdClosesConnection) {
+  // Has to be before version test, see EndToEndTest::TearDown()
+  ASSERT_TRUE(Initialize());
+  if (negotiated_version_.transport_version != QUIC_VERSION_99) {
+    // Only runs for IETF QUIC.
+    return;
+  }
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+
+  std::string body(kMaxOutgoingPacketSize, 'a');
+  SpdyHeaderBlock headers;
+  headers[":method"] = "POST";
+  headers[":path"] = "/foo";
+  headers[":scheme"] = "https";
+  headers[":authority"] = server_hostname_;
+
+  // Force the client to write with a stream ID that exceeds the limit.
+  QuicSpdySession* session = client_->client()->client_session();
+  QuicStreamIdManager* stream_id_manager =
+      QuicSessionPeer::v99_bidirectional_stream_id_manager(session);
+  QuicStreamCount max_number_of_streams =
+      stream_id_manager->outgoing_max_streams();
+  QuicSessionPeer::SetNextOutgoingBidirectionalStreamId(
+      session, GetNthClientInitiatedBidirectionalId(max_number_of_streams + 1));
+  client_->SendCustomSynchronousRequest(headers, body);
+  EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+  EXPECT_EQ(QUIC_INVALID_STREAM_ID, client_->connection_error());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index 9a99ca7..7f4d8dc 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -114,13 +114,13 @@
         connection_->transport_version(), 0);
   }
 
-  // The function ensures that A) the max stream id frames get properly deleted
+  // The function ensures that A) the MAX_STREAMS frames get properly deleted
   // (since the test uses a 'did we leak memory' check ... if we just lose the
   // frame, the test fails) and B) returns true (instead of the default, false)
   // which ensures that the rest of the system thinks that the frame actually
   // was transmitted.
-  bool ClearMaxStreamIdControlFrame(const QuicFrame& frame) {
-    if (frame.type == MAX_STREAM_ID_FRAME) {
+  bool ClearMaxStreamsControlFrame(const QuicFrame& frame) {
+    if (frame.type == MAX_STREAMS_FRAME) {
       DeleteFrame(&const_cast<QuicFrame&>(frame));
       return true;
     }
@@ -128,8 +128,8 @@
   }
 
  public:
-  bool ClearStreamIdBlockedControlFrame(const QuicFrame& frame) {
-    if (frame.type == STREAM_ID_BLOCKED_FRAME) {
+  bool ClearStreamsBlockedControlFrame(const QuicFrame& frame) {
+    if (frame.type == STREAMS_BLOCKED_FRAME) {
       DeleteFrame(&const_cast<QuicFrame&>(frame));
       return true;
     }
@@ -146,7 +146,7 @@
       EXPECT_CALL(*connection_, SendControlFrame(_))
           .Times(testing::AnyNumber())
           .WillRepeatedly(Invoke(
-              this, &QuicSpdyClientSessionTest::ClearMaxStreamIdControlFrame));
+              this, &QuicSpdyClientSessionTest::ClearMaxStreamsControlFrame));
     }
     session_->CryptoConnect();
     QuicCryptoClientStream* stream = static_cast<QuicCryptoClientStream*>(
@@ -244,12 +244,24 @@
   EXPECT_FALSE(session_->CreateOutgoingBidirectionalStream());
 
   // Close the stream, but without having received a FIN or a RST_STREAM
-  // or MAX_STREAM_ID (V99) and check that a new one can not be created.
+  // or MAX_STREAMS (V99) and check that a new one can not be created.
   session_->CloseStream(stream->id());
   EXPECT_EQ(1u, session_->GetNumOpenOutgoingStreams());
 
   stream = session_->CreateOutgoingBidirectionalStream();
   EXPECT_FALSE(stream);
+
+  if (GetParam().transport_version == QUIC_VERSION_99) {
+    // Ensure that we have/have had 3 open streams, crypto, header, and the
+    // 1 test stream. Primary purpose of this is to fail when crypto
+    // no longer uses a normal stream. Some above constants will then need
+    // to be changed.
+    EXPECT_EQ(QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
+                      ->outgoing_static_stream_count() +
+                  1,
+              QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
+                  ->outgoing_stream_count());
+  }
 }
 
 TEST_P(QuicSpdyClientSessionTest, MaxNumStreamsWithRst) {
@@ -276,17 +288,34 @@
   // Check that a new one can be created.
   EXPECT_EQ(0u, session_->GetNumOpenOutgoingStreams());
   if (GetParam().transport_version == QUIC_VERSION_99) {
-    // In V99 the stream limit increases only if we get a MAX_STREAM_ID
+    // In V99 the stream limit increases only if we get a MAX_STREAMS
     // frame; pretend we got one.
 
-    // Note that this is to be the second stream created, but GetNth... starts
-    // numbering at 0 (the first stream is 0, second is 1...)
-    QuicMaxStreamIdFrame frame(0, GetNthClientInitiatedBidirectionalStreamId(
-                                      connection_->transport_version(), 1));
-    session_->OnMaxStreamIdFrame(frame);
+    // Note that this is to be the second stream created, hence
+    // the stream count is 4 (the two streams created as a part of
+    // the test plus the crypto and header stream, internally created).
+    // TODO(nharper): Change 4 to 3 & update comment accordingly when the crypto
+    // stuff is no longer in a regular stream.
+    // TODO(fkastenholz): do above if nharper doesn't :-)
+    QuicMaxStreamsFrame frame(
+        0,
+        QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
+                ->outgoing_static_stream_count() +
+            2,
+        /*unidirectional=*/false);
+    session_->OnMaxStreamsFrame(frame);
   }
   stream = session_->CreateOutgoingBidirectionalStream();
   EXPECT_NE(nullptr, stream);
+  if (GetParam().transport_version == QUIC_VERSION_99) {
+    // Ensure that we have/have had four open streams, crypto, header, and the
+    // two test streams. Primary purpose of this is to fail when crypto
+    // no longer uses a normal stream. Some above constants will then need
+    // to be changed.
+    EXPECT_EQ(4u,
+              QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
+                  ->outgoing_stream_count());
+  }
 }
 
 TEST_P(QuicSpdyClientSessionTest, ResetAndTrailers) {
@@ -308,7 +337,7 @@
 
   if (GetParam().transport_version == QUIC_VERSION_99) {
     // For v99, trying to open a stream and failing due to lack
-    // of stream ids will result in a STREAM_ID_BLOCKED. Make
+    // of stream ids will result in a STREAMS_BLOCKED. Make
     // sure we get one. Also clear out the frame because if it's
     // left sitting, the later SendRstStream will not actually
     // transmit the RST_STREAM because the connection will be in write-blocked
@@ -316,8 +345,7 @@
     // RST_STREAM, below, will not be satisfied.
     EXPECT_CALL(*connection_, SendControlFrame(_))
         .WillOnce(Invoke(
-            this,
-            &QuicSpdyClientSessionTest::ClearStreamIdBlockedControlFrame));
+            this, &QuicSpdyClientSessionTest::ClearStreamsBlockedControlFrame));
   }
 
   EXPECT_EQ(nullptr, session_->CreateOutgoingBidirectionalStream());
@@ -347,14 +375,32 @@
   // be able to create a new outgoing stream.
   EXPECT_EQ(0u, session_->GetNumOpenOutgoingStreams());
   if (GetParam().transport_version == QUIC_VERSION_99) {
-    // Note that this is to be the second stream created, but GetNth... starts
-    // numbering at 0 (the first stream is 0, second is 1...)
-    QuicMaxStreamIdFrame frame(0, GetNthClientInitiatedBidirectionalStreamId(
-                                      connection_->transport_version(), 1));
-    session_->OnMaxStreamIdFrame(frame);
+    // Note that this is to be the second stream created, hence
+    // the stream count is 4 (the two streams created as a part of
+    // the test plus the crypto and header stream, internally created).
+    // TODO(nharper): Change 4 to 3 & update comment accordingly when the crypto
+    // stuff is no longer in a regular stream.
+    // TODO(fkastenholz): do above if nharper doesn't :-)
+    QuicMaxStreamsFrame frame(
+        0,
+        QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
+                ->outgoing_static_stream_count() +
+            2,
+        /*unidirectional=*/false);
+
+    session_->OnMaxStreamsFrame(frame);
   }
   stream = session_->CreateOutgoingBidirectionalStream();
   EXPECT_NE(nullptr, stream);
+  if (GetParam().transport_version == QUIC_VERSION_99) {
+    // Ensure that we have/have had four open streams, crypto, header, and the
+    // two test streams. Primary purpose of this is to fail when crypto
+    // no longer uses a normal stream. Some above constants will then need
+    // to be changed.
+    EXPECT_EQ(4u,
+              QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
+                  ->outgoing_stream_count());
+  }
 }
 
 TEST_P(QuicSpdyClientSessionTest, ReceivedMalformedTrailersAfterSendingRst) {
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 5726573..83ea72f 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -277,8 +277,8 @@
 
 class QuicSpdySessionTestBase : public QuicTestWithParam<ParsedQuicVersion> {
  public:
-  bool ClearMaxStreamIdControlFrame(const QuicFrame& frame) {
-    if (frame.type == MAX_STREAM_ID_FRAME) {
+  bool ClearMaxStreamsControlFrame(const QuicFrame& frame) {
+    if (frame.type == MAX_STREAMS_FRAME) {
       DeleteFrame(&const_cast<QuicFrame&>(frame));
       return true;
     }
@@ -380,6 +380,24 @@
     return QuicUtils::StreamIdDelta(connection_->transport_version());
   }
 
+  QuicStreamId StreamCountToId(QuicStreamCount stream_count,
+                               Perspective perspective,
+                               bool bidirectional) {
+    // Calculate and build up stream ID rather than use
+    // GetFirst... because the test that relies on this method
+    // needs to do the stream count where #1 is 0/1/2/3, and not
+    // take into account that stream 0 is special.
+    QuicStreamId id =
+        ((stream_count - 1) * QuicUtils::StreamIdDelta(QUIC_VERSION_99));
+    if (!bidirectional) {
+      id |= 0x2;
+    }
+    if (perspective == Perspective::IS_SERVER) {
+      id |= 0x1;
+    }
+    return id;
+  }
+
   MockQuicConnectionHelper helper_;
   MockAlarmFactory alarm_factory_;
   StrictMock<MockQuicConnection>* connection_;
@@ -496,28 +514,35 @@
     // stream ID, the next ID should fail. Since the actual limit
     // is not the number of open streams, we allocate the max and the max+2.
     // Get the max allowed stream ID, this should succeed.
-    EXPECT_NE(nullptr,
-              session_.GetOrCreateDynamicStream(
-                  QuicSessionPeer::v99_streamid_manager(&session_)
-                      ->actual_max_allowed_incoming_bidirectional_stream_id()));
-    EXPECT_NE(
-        nullptr,
-        session_.GetOrCreateDynamicStream(
-            QuicSessionPeer::v99_streamid_manager(&session_)
-                ->actual_max_allowed_incoming_unidirectional_stream_id()));
-    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
-    // Get the (max allowed stream ID)++, this should fail.
-    EXPECT_EQ(nullptr,
-              session_.GetOrCreateDynamicStream(
-                  QuicSessionPeer::v99_streamid_manager(&session_)
-                      ->actual_max_allowed_incoming_bidirectional_stream_id() +
-                  IdDelta()));
-    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
-    EXPECT_EQ(nullptr,
-              session_.GetOrCreateDynamicStream(
-                  QuicSessionPeer::v99_streamid_manager(&session_)
-                      ->actual_max_allowed_incoming_unidirectional_stream_id() +
-                  IdDelta()));
+    QuicStreamId stream_id = StreamCountToId(
+        QuicSessionPeer::v99_streamid_manager(&session_)
+            ->actual_max_allowed_incoming_bidirectional_streams(),
+        Perspective::IS_CLIENT,  // Client initates stream, allocs stream id.
+        /*bidirectional=*/true);
+    EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id));
+    stream_id = StreamCountToId(
+        QuicSessionPeer::v99_streamid_manager(&session_)
+            ->actual_max_allowed_incoming_unidirectional_streams(),
+        Perspective::IS_CLIENT,
+        /*bidirectional=*/false);
+    EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id));
+    EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(2);
+    // Get the (max allowed stream ID)++. These should all fail.
+    stream_id = StreamCountToId(
+        QuicSessionPeer::v99_streamid_manager(&session_)
+                ->actual_max_allowed_incoming_bidirectional_streams() +
+            1,
+        Perspective::IS_CLIENT,
+        /*bidirectional=*/true);
+    EXPECT_EQ(nullptr, session_.GetOrCreateDynamicStream(stream_id));
+
+    stream_id = StreamCountToId(
+        QuicSessionPeer::v99_streamid_manager(&session_)
+                ->actual_max_allowed_incoming_unidirectional_streams() +
+            1,
+        Perspective::IS_CLIENT,
+        /*bidirectional=*/false);
+    EXPECT_EQ(nullptr, session_.GetOrCreateDynamicStream(stream_id));
   } else {
     QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
     session_.GetOrCreateDynamicStream(stream_id);
@@ -556,9 +581,10 @@
   session_.GetOrCreateDynamicStream(stream_id);
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
   // Stream count is 200, GetNth... starts counting at 0, so the 200'th stream
-  // is 199.
+  // is 199. BUT actually we need to do 198 because the crypto stream (Stream
+  // ID 0) has not been registered, but GetNth... assumes that it has.
   EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(
-                         GetNthClientInitiatedBidirectionalId(199)));
+                         GetNthClientInitiatedBidirectionalId(198)));
 }
 
 TEST_P(QuicSpdySessionTestServer,
@@ -679,7 +705,7 @@
   if (IsVersion99()) {
     EXPECT_CALL(*connection_, SendControlFrame(_))
         .WillRepeatedly(Invoke(
-            this, &QuicSpdySessionTestServer::ClearMaxStreamIdControlFrame));
+            this, &QuicSpdySessionTestServer::ClearMaxStreamsControlFrame));
   }
   // Encryption needs to be established before data can be sent.
   CryptoHandshakeMessage msg;
@@ -1469,6 +1495,16 @@
   // than version 99. In version 99 the connection gets closed.
   const QuicStreamId kMaxStreams = 5;
   QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  // GetNth assumes that both the crypto and header streams have been
+  // open, but the stream id manager, using GetFirstBidirectional... only
+  // assumes that the crypto stream is open. This means that GetNth...(0)
+  // Will return stream ID == 8 (with id ==0 for crypto and id==4 for headers).
+  // It also means that GetNth(kMax..=5) returns 28 (streams 0/1/2/3/4 are ids
+  // 8, 12, 16, 20, 24, respectively, so stream#5 is stream id 28).
+  // However, the stream ID manager does not assume stream 4 is for headers.
+  // The ID manager would assume that stream#5 is streamid 24.
+  // In order to make this all work out properly, kFinalStreamId will
+  // be set to GetNth...(kMaxStreams-1)... but only for V99
   const QuicStreamId kFirstStreamId = GetNthClientInitiatedBidirectionalId(0);
   const QuicStreamId kFinalStreamId =
       GetNthClientInitiatedBidirectionalId(kMaxStreams);
@@ -1505,8 +1541,10 @@
         .Times(1);
   } else {
     // On version 99 opening such a stream results in a connection close.
-    EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID,
-                                              "Stream id 28 above 24", _));
+    EXPECT_CALL(
+        *connection_,
+        CloseConnection(QUIC_INVALID_STREAM_ID,
+                        "Stream id 28 would exceed stream count limit 7", _));
   }
   // Create one more data streams to exceed limit of open stream.
   QuicStreamFrame data1(kFinalStreamId, false, 0, QuicStringPiece("HT"));
@@ -1518,10 +1556,10 @@
   // it) does not count against the open quota (because it is closed from the
   // protocol point of view).
   if (IsVersion99()) {
-    // Version 99 will result in a MAX_STREAM_ID frame as streams are consumed
+    // 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
-    // running low on available stream ids and then send a MAX_STREAM_ID frame,
+    // running low on available stream ids and then send a MAX_STREAMS frame,
     // caught by this EXPECT_CALL.
     EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
   } else {
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index eeebc20..ad56a06 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -1336,13 +1336,13 @@
   return connected_;
 }
 
-bool QuicConnection::OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) {
-  return visitor_->OnMaxStreamIdFrame(frame);
+bool QuicConnection::OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) {
+  return visitor_->OnMaxStreamsFrame(frame);
 }
 
-bool QuicConnection::OnStreamIdBlockedFrame(
-    const QuicStreamIdBlockedFrame& frame) {
-  return visitor_->OnStreamIdBlockedFrame(frame);
+bool QuicConnection::OnStreamsBlockedFrame(
+    const QuicStreamsBlockedFrame& frame) {
+  return visitor_->OnStreamsBlockedFrame(frame);
 }
 
 bool QuicConnection::OnGoAwayFrame(const QuicGoAwayFrame& frame) {
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index c921d1b..b37b60c 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -116,12 +116,11 @@
   // Called when |message| has been received.
   virtual void OnMessageReceived(QuicStringPiece message) = 0;
 
-  // Called when a MAX_STREAM_ID frame has been received from the peer.
-  virtual bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) = 0;
+  // Called when a MAX_STREAMS frame has been received from the peer.
+  virtual bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) = 0;
 
-  // Called when a STREAM_ID_BLOCKED frame has been received from the peer.
-  virtual bool OnStreamIdBlockedFrame(
-      const QuicStreamIdBlockedFrame& frame) = 0;
+  // Called when a STREAMS_BLOCKED frame has been received from the peer.
+  virtual bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) = 0;
 
   // Called when the connection is closed either locally by the framer, or
   // remotely by the peer.
@@ -500,8 +499,8 @@
   bool OnPathChallengeFrame(const QuicPathChallengeFrame& frame) override;
   bool OnPathResponseFrame(const QuicPathResponseFrame& frame) override;
   bool OnGoAwayFrame(const QuicGoAwayFrame& frame) override;
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override;
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override;
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override;
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override;
   bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override;
   bool OnBlockedFrame(const QuicBlockedFrame& frame) override;
   bool OnNewConnectionIdFrame(const QuicNewConnectionIdFrame& frame) override;
diff --git a/quic/core/quic_constants.h b/quic/core/quic_constants.h
index 4181cb7..67cd860 100644
--- a/quic/core/quic_constants.h
+++ b/quic/core/quic_constants.h
@@ -211,6 +211,9 @@
 // TODO(fkastenholz): Should update this to 64 bits for IETF Quic.
 const QuicStreamId kMaxQuicStreamId = 0xffffffff;
 
+// The maximum value that can be stored in a 32-bit QuicStreamCount.
+const QuicStreamCount kMaxQuicStreamCount = 0xffffffff;
+
 // Number of bytes reserved for packet header type.
 const size_t kPacketHeaderTypeSize = 1;
 
diff --git a/quic/core/quic_control_frame_manager.cc b/quic/core/quic_control_frame_manager.cc
index ed178f4..511ef2b 100644
--- a/quic/core/quic_control_frame_manager.cc
+++ b/quic/core/quic_control_frame_manager.cc
@@ -68,18 +68,20 @@
       QuicFrame(new QuicBlockedFrame(++last_control_frame_id_, id)));
 }
 
-void QuicControlFrameManager::WriteOrBufferStreamIdBlocked(QuicStreamId id) {
-  QUIC_DVLOG(1) << "Writing STREAM_ID_BLOCKED Frame";
-  QUIC_CODE_COUNT(stream_id_blocked_transmits);
-  WriteOrBufferQuicFrame(
-      QuicFrame(QuicStreamIdBlockedFrame(++last_control_frame_id_, id)));
+void QuicControlFrameManager::WriteOrBufferStreamsBlocked(QuicStreamCount count,
+                                                          bool unidirectional) {
+  QUIC_DVLOG(1) << "Writing STREAMS_BLOCKED Frame";
+  QUIC_CODE_COUNT(quic_streams_blocked_transmits);
+  WriteOrBufferQuicFrame(QuicFrame(QuicStreamsBlockedFrame(
+      ++last_control_frame_id_, count, unidirectional)));
 }
 
-void QuicControlFrameManager::WriteOrBufferMaxStreamId(QuicStreamId id) {
-  QUIC_DVLOG(1) << "Writing MAX_STREAM_ID Frame";
-  QUIC_CODE_COUNT(max_stream_id_transmits);
-  WriteOrBufferQuicFrame(
-      QuicFrame(QuicMaxStreamIdFrame(++last_control_frame_id_, id)));
+void QuicControlFrameManager::WriteOrBufferMaxStreams(QuicStreamCount count,
+                                                      bool unidirectional) {
+  QUIC_DVLOG(1) << "Writing MAX_STREAMS Frame";
+  QUIC_CODE_COUNT(quic_max_streams_transmits);
+  WriteOrBufferQuicFrame(QuicFrame(
+      QuicMaxStreamsFrame(++last_control_frame_id_, count, unidirectional)));
 }
 
 void QuicControlFrameManager::WriteOrBufferStopSending(uint16_t code,
diff --git a/quic/core/quic_control_frame_manager.h b/quic/core/quic_control_frame_manager.h
index a5213c4..a4c2678 100644
--- a/quic/core/quic_control_frame_manager.h
+++ b/quic/core/quic_control_frame_manager.h
@@ -54,13 +54,13 @@
   // immediately.
   void WriteOrBufferBlocked(QuicStreamId id);
 
-  // Tries to send a STREAM_ID_BLOCKED Frame. Buffers the frame if it cannot be
+  // Tries to send a STREAMS_BLOCKED Frame. Buffers the frame if it cannot be
   // sent immediately.
-  void WriteOrBufferStreamIdBlocked(QuicStreamId id);
+  void WriteOrBufferStreamsBlocked(QuicStreamCount count, bool unidirectional);
 
-  // Tries to send a MAX_STREAM_ID Frame. Buffers the frame if it cannot be sent
+  // Tries to send a MAX_STREAMS Frame. Buffers the frame if it cannot be sent
   // immediately.
-  void WriteOrBufferMaxStreamId(QuicStreamId id);
+  void WriteOrBufferMaxStreams(QuicStreamCount count, bool unidirectional);
 
   // Tries to send an IETF-QUIC STOP_SENDING frame. The frame is buffered if it
   // can not be sent immediately.
diff --git a/quic/core/quic_data_reader.cc b/quic/core/quic_data_reader.cc
index 0488fd3..b13b061 100644
--- a/quic/core/quic_data_reader.cc
+++ b/quic/core/quic_data_reader.cc
@@ -295,7 +295,7 @@
   return false;
 }
 
-bool QuicDataReader::ReadVarIntStreamId(QuicStreamId* result) {
+bool QuicDataReader::ReadVarIntU32(uint32_t* result) {
   uint64_t temp_uint64;
   // TODO(fkastenholz): We should disambiguate read-errors from
   // value errors.
@@ -305,7 +305,7 @@
   if (temp_uint64 > kMaxQuicStreamId) {
     return false;
   }
-  *result = static_cast<QuicStreamId>(temp_uint64);
+  *result = static_cast<uint32_t>(temp_uint64);
   return true;
 }
 
diff --git a/quic/core/quic_data_reader.h b/quic/core/quic_data_reader.h
index 3426e48..9e88a7e 100644
--- a/quic/core/quic_data_reader.h
+++ b/quic/core/quic_data_reader.h
@@ -142,11 +142,11 @@
   // and that the integers in the range 0 ... (2^62)-1.
   bool ReadVarInt62(uint64_t* result);
 
-  // Convenience method that reads a StreamId.
-  // Atempts to read a Stream ID into |result| using ReadVarInt62 and
+  // Convenience method that reads a uint32_t.
+  // Attempts to read a varint into a uint32_t. using ReadVarInt62 and
   // returns false if there is a read error or if the value is
   // greater than (2^32)-1.
-  bool ReadVarIntStreamId(QuicStreamId* result);
+  bool ReadVarIntU32(uint32_t* result);
 
   std::string DebugString() const;
 
diff --git a/quic/core/quic_data_writer_test.cc b/quic/core/quic_data_writer_test.cc
index 031620d..73bb156 100644
--- a/quic/core/quic_data_writer_test.cc
+++ b/quic/core/quic_data_writer_test.cc
@@ -1026,7 +1026,7 @@
 
   QuicDataReader reader(buffer, sizeof(buffer), Endianness::NETWORK_BYTE_ORDER);
   QuicStreamId received_stream_id;
-  bool read_result = reader.ReadVarIntStreamId(&received_stream_id);
+  bool read_result = reader.ReadVarIntU32(&received_stream_id);
   EXPECT_EQ(expected_decode_result, read_result);
   if (read_result) {
     EXPECT_EQ(value_in, received_stream_id);
@@ -1114,6 +1114,34 @@
   EXPECT_FALSE(ok);
 }
 
+// Test that ReadVarIntU32 works properly. Tests a valid stream count
+// (a 32 bit number) and an invalid one (a >32 bit number)
+TEST_P(QuicDataWriterTest, ValidU32) {
+  char buffer[1024];
+  memset(buffer, 0, sizeof(buffer));
+  QuicDataWriter writer(sizeof(buffer), static_cast<char*>(buffer),
+                        Endianness::NETWORK_BYTE_ORDER);
+  QuicDataReader reader(buffer, sizeof(buffer));
+  const QuicStreamCount write_stream_count = 0xffeeddcc;
+  EXPECT_TRUE(writer.WriteVarInt62(write_stream_count));
+  QuicStreamCount read_stream_count;
+  EXPECT_TRUE(reader.ReadVarIntU32(&read_stream_count));
+  EXPECT_EQ(write_stream_count, read_stream_count);
+}
+
+TEST_P(QuicDataWriterTest, InvalidU32) {
+  char buffer[1024];
+  memset(buffer, 0, sizeof(buffer));
+  QuicDataWriter writer(sizeof(buffer), static_cast<char*>(buffer),
+                        Endianness::NETWORK_BYTE_ORDER);
+  QuicDataReader reader(buffer, sizeof(buffer));
+  EXPECT_TRUE(writer.WriteVarInt62(UINT64_C(0x1ffeeddcc)));
+  QuicStreamCount read_stream_count = 123456;
+  EXPECT_FALSE(reader.ReadVarIntU32(&read_stream_count));
+  // If the value is bad, read_stream_count ought not change.
+  EXPECT_EQ(123456u, read_stream_count);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc
index 2e81740..a254585 100644
--- a/quic/core/quic_dispatcher.cc
+++ b/quic/core/quic_dispatcher.cc
@@ -941,12 +941,12 @@
   return false;
 }
 
-bool QuicDispatcher::OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) {
+bool QuicDispatcher::OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) {
   return true;
 }
 
-bool QuicDispatcher::OnStreamIdBlockedFrame(
-    const QuicStreamIdBlockedFrame& frame) {
+bool QuicDispatcher::OnStreamsBlockedFrame(
+    const QuicStreamsBlockedFrame& frame) {
   return true;
 }
 
diff --git a/quic/core/quic_dispatcher.h b/quic/core/quic_dispatcher.h
index af268f9..4503fc0 100644
--- a/quic/core/quic_dispatcher.h
+++ b/quic/core/quic_dispatcher.h
@@ -169,8 +169,8 @@
   bool OnPathChallengeFrame(const QuicPathChallengeFrame& frame) override;
   bool OnPathResponseFrame(const QuicPathResponseFrame& frame) override;
   bool OnGoAwayFrame(const QuicGoAwayFrame& frame) override;
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override;
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override;
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override;
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override;
   bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override;
   bool OnBlockedFrame(const QuicBlockedFrame& frame) override;
   bool OnNewConnectionIdFrame(const QuicNewConnectionIdFrame& frame) override;
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index e2a14ad..86c0896 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -138,8 +138,8 @@
     RETURN_STRING_LITERAL(QUIC_INVALID_MAX_DATA_FRAME_DATA);
     RETURN_STRING_LITERAL(QUIC_INVALID_MAX_STREAM_DATA_FRAME_DATA);
     RETURN_STRING_LITERAL(QUIC_INVALID_STREAM_BLOCKED_DATA);
-    RETURN_STRING_LITERAL(QUIC_MAX_STREAM_ID_DATA);
-    RETURN_STRING_LITERAL(QUIC_STREAM_ID_BLOCKED_DATA);
+    RETURN_STRING_LITERAL(QUIC_MAX_STREAMS_DATA);
+    RETURN_STRING_LITERAL(QUIC_STREAMS_BLOCKED_DATA);
     RETURN_STRING_LITERAL(QUIC_INVALID_NEW_CONNECTION_ID_DATA);
     RETURN_STRING_LITERAL(QUIC_INVALID_RETIRE_CONNECTION_ID_DATA);
     RETURN_STRING_LITERAL(QUIC_INVALID_STOP_SENDING_FRAME_DATA);
@@ -151,8 +151,8 @@
     RETURN_STRING_LITERAL(QUIC_INVALID_NEW_TOKEN);
     RETURN_STRING_LITERAL(QUIC_DATA_RECEIVED_ON_WRITE_UNIDIRECTIONAL_STREAM);
     RETURN_STRING_LITERAL(QUIC_TRY_TO_WRITE_DATA_ON_READ_UNIDIRECTIONAL_STREAM);
-    RETURN_STRING_LITERAL(QUIC_STREAM_ID_BLOCKED_ERROR);
-    RETURN_STRING_LITERAL(QUIC_MAX_STREAM_ID_ERROR);
+    RETURN_STRING_LITERAL(QUIC_STREAMS_BLOCKED_ERROR);
+    RETURN_STRING_LITERAL(QUIC_MAX_STREAMS_ERROR);
     RETURN_STRING_LITERAL(QUIC_HTTP_DECODER_ERROR);
     RETURN_STRING_LITERAL(QUIC_STALE_CONNECTION_CANCELLED);
     RETURN_STRING_LITERAL(QUIC_IETF_GQUIC_ERROR_MISSING);
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index b06cc7f..c8259f7 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -290,10 +290,10 @@
   QUIC_INVALID_MAX_DATA_FRAME_DATA = 102,
   // Received a MAX STREAM DATA frame with errors.
   QUIC_INVALID_MAX_STREAM_DATA_FRAME_DATA = 103,
-  // Received a MAX_STREAM_ID frame with bad data
-  QUIC_MAX_STREAM_ID_DATA = 104,
-  // Received a STREAM_ID_BLOCKED frame with bad data
-  QUIC_STREAM_ID_BLOCKED_DATA = 105,
+  // Received a MAX_STREAMS frame with bad data
+  QUIC_MAX_STREAMS_DATA = 104,
+  // Received a STREAMS_BLOCKED frame with bad data
+  QUIC_STREAMS_BLOCKED_DATA = 105,
   // Error deframing a STREAM BLOCKED frame.
   QUIC_INVALID_STREAM_BLOCKED_DATA = 106,
   // NEW CONNECTION ID frame data is malformed.
@@ -315,13 +315,11 @@
 
   // RETIRE CONNECTION ID frame data is malformed.
   QUIC_INVALID_RETIRE_CONNECTION_ID_DATA = 117,
-  //
-  // Error in a received STREAM ID BLOCKED frame. -- the stream ID is not
-  // consistent with the state of the endpoint.
-  QUIC_STREAM_ID_BLOCKED_ERROR = 118,
-  // Error in a received MAX STREAM ID frame -- the stream ID is not
-  // consistent with the state of the endpoint.
-  QUIC_MAX_STREAM_ID_ERROR = 119,
+
+  // Error in a received STREAMS BLOCKED frame.
+  QUIC_STREAMS_BLOCKED_ERROR = 118,
+  // Error in a received MAX STREAMS frame
+  QUIC_MAX_STREAMS_ERROR = 119,
   // Error in Http decoder
   QUIC_HTTP_DECODER_ERROR = 120,
   // Connection from stale host needs to be cancelled.
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index fcb85da..20dc57c 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -404,63 +404,6 @@
   return full_packet_number > 0 || version == QUIC_VERSION_99;
 }
 
-// Convert a stream ID to a count of streams, for IETF QUIC/Version 99 only.
-// There is no need to take into account whether the ID is for uni- or
-// bi-directional streams, or whether it's server- or client- initiated.  It
-// always returns a valid count.
-QuicStreamId StreamIdToCount(QuicTransportVersion version,
-                             QuicStreamId stream_id) {
-  DCHECK_EQ(QUIC_VERSION_99, version);
-  if ((stream_id & 0x3) == 0) {
-    return (stream_id / QuicUtils::StreamIdDelta(version));
-  }
-  return (stream_id / QuicUtils::StreamIdDelta(version)) + 1;
-}
-
-// Returns the maximum value that a stream count may have, taking into account
-// the fact that bidirectional, client initiated, streams have one fewer stream
-// available than the others. This is because the old crypto streams, with ID ==
-// 0 are not included in the count.
-// The version is not included in the call, nor does the method take the version
-// into account, because this is called only from code used for IETF QUIC.
-// TODO(fkastenholz): Remove this method and replace calls to it with direct
-// references to kMaxQuicStreamIdCount when streamid 0 becomes a normal stream
-// id.
-QuicStreamId GetMaxStreamCount(bool unidirectional, Perspective perspective) {
-  if (!unidirectional && perspective == Perspective::IS_CLIENT) {
-    return kMaxQuicStreamId >> 2;
-  }
-  return (kMaxQuicStreamId >> 2) + 1;
-}
-
-// Convert a stream count to the maximum stream ID for that count.
-// Needs to know whether the resulting stream ID  should be uni-directional,
-// bi-directional, server-initiated, or client-initiated.
-// Returns true if it works, false if not. The only error condition is that
-// the stream_count is too big and it would generate a stream id that is larger
-// than the implementation's maximum stream id value.
-bool StreamCountToId(QuicStreamId stream_count,
-                     bool unidirectional,
-                     Perspective perspective,
-                     QuicTransportVersion version,
-                     QuicStreamId* generated_stream_id) {
-  DCHECK_EQ(QUIC_VERSION_99, version);
-  // TODO(fkastenholz): when the MAX_STREAMS and STREAMS_BLOCKED frames
-  // are connected all the way up to the stream_id_manager, handle count==0
-  // properly (interpret it as "can open 0 streams") and the count being too
-  // large (close the connection).
-  if ((stream_count == 0) ||
-      (stream_count > GetMaxStreamCount(unidirectional, perspective))) {
-    return false;
-  }
-  *generated_stream_id =
-      ((unidirectional)
-           ? QuicUtils::GetFirstUnidirectionalStreamId(version, perspective)
-           : QuicUtils::GetFirstBidirectionalStreamId(version, perspective)) +
-      ((stream_count - 1) * QuicUtils::StreamIdDelta(version));
-  return true;
-}
-
 bool AppendIetfConnectionIdsNew(bool version_flag,
                                 QuicConnectionId destination_connection_id,
                                 QuicConnectionId source_connection_id,
@@ -664,31 +607,26 @@
 
 // static
 size_t QuicFramer::GetMaxStreamsFrameSize(QuicTransportVersion version,
-                                          const QuicMaxStreamIdFrame& frame) {
+                                          const QuicMaxStreamsFrame& frame) {
   if (version != QUIC_VERSION_99) {
     QUIC_BUG << "In version " << version
-             << " - not 99 - and tried to serialize MaxStreamId Frame.";
+             << " - not 99 - and tried to serialize MaxStreams Frame.";
   }
-
-  // Convert from the stream id on which the connection is blocked to a count
-  QuicStreamId stream_count = StreamIdToCount(version, frame.max_stream_id);
-
-  return kQuicFrameTypeSize + QuicDataWriter::GetVarInt62Len(stream_count);
+  return kQuicFrameTypeSize +
+         QuicDataWriter::GetVarInt62Len(frame.stream_count);
 }
 
 // static
 size_t QuicFramer::GetStreamsBlockedFrameSize(
     QuicTransportVersion version,
-    const QuicStreamIdBlockedFrame& frame) {
+    const QuicStreamsBlockedFrame& frame) {
   if (version != QUIC_VERSION_99) {
     QUIC_BUG << "In version " << version
-             << " - not 99 - and tried to serialize StreamIdBlocked Frame.";
+             << " - not 99 - and tried to serialize StreamsBlocked Frame.";
   }
 
-  // Convert from the stream id on which the connection is blocked to a count
-  QuicStreamId stream_count = StreamIdToCount(version, frame.stream_id);
-
-  return kQuicFrameTypeSize + QuicDataWriter::GetVarInt62Len(stream_count);
+  return kQuicFrameTypeSize +
+         QuicDataWriter::GetVarInt62Len(frame.stream_count);
 }
 
 // static
@@ -755,10 +693,10 @@
       return GetRetireConnectionIdFrameSize(*frame.retire_connection_id_frame);
     case NEW_TOKEN_FRAME:
       return GetNewTokenFrameSize(*frame.new_token_frame);
-    case MAX_STREAM_ID_FRAME:
-      return GetMaxStreamsFrameSize(version, frame.max_stream_id_frame);
-    case STREAM_ID_BLOCKED_FRAME:
-      return GetStreamsBlockedFrameSize(version, frame.stream_id_blocked_frame);
+    case MAX_STREAMS_FRAME:
+      return GetMaxStreamsFrameSize(version, frame.max_streams_frame);
+    case STREAMS_BLOCKED_FRAME:
+      return GetStreamsBlockedFrameSize(version, frame.streams_blocked_frame);
     case PATH_RESPONSE_FRAME:
       return GetPathResponseFrameSize(*frame.path_response_frame);
     case PATH_CHALLENGE_FRAME:
@@ -1063,13 +1001,13 @@
         set_detailed_error(
             "Attempt to append NEW_TOKEN_ID frame and not in version 99.");
         return RaiseError(QUIC_INTERNAL_ERROR);
-      case MAX_STREAM_ID_FRAME:
+      case MAX_STREAMS_FRAME:
         set_detailed_error(
-            "Attempt to append MAX_STREAM_ID frame and not in version 99.");
+            "Attempt to append MAX_STREAMS frame and not in version 99.");
         return RaiseError(QUIC_INTERNAL_ERROR);
-      case STREAM_ID_BLOCKED_FRAME:
+      case STREAMS_BLOCKED_FRAME:
         set_detailed_error(
-            "Attempt to append STREAM_ID_BLOCKED frame and not in version 99.");
+            "Attempt to append STREAMS_BLOCKED frame and not in version 99.");
         return RaiseError(QUIC_INTERNAL_ERROR);
       case PATH_RESPONSE_FRAME:
         set_detailed_error(
@@ -1195,14 +1133,14 @@
           return 0;
         }
         break;
-      case MAX_STREAM_ID_FRAME:
-        if (!AppendMaxStreamsFrame(frame.max_stream_id_frame, writer)) {
+      case MAX_STREAMS_FRAME:
+        if (!AppendMaxStreamsFrame(frame.max_streams_frame, writer)) {
           QUIC_BUG << "AppendMaxStreamsFrame failed" << detailed_error();
           return 0;
         }
         break;
-      case STREAM_ID_BLOCKED_FRAME:
-        if (!AppendStreamsBlockedFrame(frame.stream_id_blocked_frame, writer)) {
+      case STREAMS_BLOCKED_FRAME:
+        if (!AppendStreamsBlockedFrame(frame.streams_blocked_frame, writer)) {
           QUIC_BUG << "AppendStreamsBlockedFrame failed" << detailed_error();
           return 0;
         }
@@ -2973,12 +2911,12 @@
         }
         case IETF_MAX_STREAMS_BIDIRECTIONAL:
         case IETF_MAX_STREAMS_UNIDIRECTIONAL: {
-          QuicMaxStreamIdFrame frame;
+          QuicMaxStreamsFrame frame;
           if (!ProcessMaxStreamsFrame(reader, &frame, frame_type)) {
-            return RaiseError(QUIC_MAX_STREAM_ID_DATA);
+            return RaiseError(QUIC_MAX_STREAMS_DATA);
           }
-          QUIC_CODE_COUNT_N(max_stream_id_received, 1, 2);
-          if (!visitor_->OnMaxStreamIdFrame(frame)) {
+          QUIC_CODE_COUNT_N(quic_max_streams_received, 1, 2);
+          if (!visitor_->OnMaxStreamsFrame(frame)) {
             QUIC_DVLOG(1) << "Visitor asked to stop further processing.";
             // Returning true since there was no parsing error.
             return true;
@@ -3021,12 +2959,12 @@
         }
         case IETF_STREAMS_BLOCKED_UNIDIRECTIONAL:
         case IETF_STREAMS_BLOCKED_BIDIRECTIONAL: {
-          QuicStreamIdBlockedFrame frame;
+          QuicStreamsBlockedFrame frame;
           if (!ProcessStreamsBlockedFrame(reader, &frame, frame_type)) {
-            return RaiseError(QUIC_STREAM_ID_BLOCKED_DATA);
+            return RaiseError(QUIC_STREAMS_BLOCKED_DATA);
           }
-          QUIC_CODE_COUNT_N(stream_id_blocked_received, 1, 2);
-          if (!visitor_->OnStreamIdBlockedFrame(frame)) {
+          QUIC_CODE_COUNT_N(quic_streams_blocked_received, 1, 2);
+          if (!visitor_->OnStreamsBlockedFrame(frame)) {
             QUIC_DVLOG(1) << "Visitor asked to stop further processing.";
             // Returning true since there was no parsing error.
             return true;
@@ -3245,7 +3183,7 @@
                                         uint8_t frame_type,
                                         QuicStreamFrame* frame) {
   // Read stream id from the frame. It's always present.
-  if (!reader->ReadVarIntStreamId(&frame->stream_id)) {
+  if (!reader->ReadVarIntU32(&frame->stream_id)) {
     set_detailed_error("Unable to read stream_id.");
     return false;
   }
@@ -4306,13 +4244,13 @@
       set_detailed_error(
           "Attempt to append NEW_TOKEN frame and not in version 99.");
       return RaiseError(QUIC_INTERNAL_ERROR);
-    case MAX_STREAM_ID_FRAME:
+    case MAX_STREAMS_FRAME:
       set_detailed_error(
-          "Attempt to append MAX_STREAM_ID frame and not in version 99.");
+          "Attempt to append MAX_STREAMS frame and not in version 99.");
       return RaiseError(QUIC_INTERNAL_ERROR);
-    case STREAM_ID_BLOCKED_FRAME:
+    case STREAMS_BLOCKED_FRAME:
       set_detailed_error(
-          "Attempt to append STREAM_ID_BLOCKED frame and not in version 99.");
+          "Attempt to append STREAMS_BLOCKED frame and not in version 99.");
       return RaiseError(QUIC_INTERNAL_ERROR);
     case PATH_RESPONSE_FRAME:
       set_detailed_error(
@@ -4411,20 +4349,18 @@
     case NEW_TOKEN_FRAME:
       type_byte = IETF_NEW_TOKEN;
       break;
-    case MAX_STREAM_ID_FRAME:
-      if (QuicUtils::IsBidirectionalStreamId(
-              frame.max_stream_id_frame.max_stream_id)) {
-        type_byte = IETF_MAX_STREAMS_BIDIRECTIONAL;
-      } else {
+    case MAX_STREAMS_FRAME:
+      if (frame.max_streams_frame.unidirectional) {
         type_byte = IETF_MAX_STREAMS_UNIDIRECTIONAL;
+      } else {
+        type_byte = IETF_MAX_STREAMS_BIDIRECTIONAL;
       }
       break;
-    case STREAM_ID_BLOCKED_FRAME:
-      if (QuicUtils::IsBidirectionalStreamId(
-              frame.max_stream_id_frame.max_stream_id)) {
-        type_byte = IETF_STREAMS_BLOCKED_BIDIRECTIONAL;
-      } else {
+    case STREAMS_BLOCKED_FRAME:
+      if (frame.streams_blocked_frame.unidirectional) {
         type_byte = IETF_STREAMS_BLOCKED_UNIDIRECTIONAL;
+      } else {
+        type_byte = IETF_STREAMS_BLOCKED_BIDIRECTIONAL;
       }
       break;
     case PATH_RESPONSE_FRAME:
@@ -5382,7 +5318,7 @@
   // Get Stream ID from frame. ReadVarIntStreamID returns false
   // if either A) there is a read error or B) the resulting value of
   // the Stream ID is larger than the maximum allowed value.
-  if (!reader->ReadVarIntStreamId(&frame->stream_id)) {
+  if (!reader->ReadVarIntU32(&frame->stream_id)) {
     set_detailed_error("Unable to read rst stream stream id.");
     return false;
   }
@@ -5402,7 +5338,7 @@
 bool QuicFramer::ProcessStopSendingFrame(
     QuicDataReader* reader,
     QuicStopSendingFrame* stop_sending_frame) {
-  if (!reader->ReadVarIntStreamId(&stop_sending_frame->stream_id)) {
+  if (!reader->ReadVarIntU32(&stop_sending_frame->stream_id)) {
     set_detailed_error("Unable to read stop sending stream id.");
     return false;
   }
@@ -5464,7 +5400,7 @@
 
 bool QuicFramer::ProcessMaxStreamDataFrame(QuicDataReader* reader,
                                            QuicWindowUpdateFrame* frame) {
-  if (!reader->ReadVarIntStreamId(&frame->stream_id)) {
+  if (!reader->ReadVarIntU32(&frame->stream_id)) {
     set_detailed_error("Can not read MAX_STREAM_DATA stream id");
     return false;
   }
@@ -5475,13 +5411,9 @@
   return true;
 }
 
-bool QuicFramer::AppendMaxStreamsFrame(const QuicMaxStreamIdFrame& frame,
+bool QuicFramer::AppendMaxStreamsFrame(const QuicMaxStreamsFrame& frame,
                                        QuicDataWriter* writer) {
-  // Convert from the stream id on which the connection is blocked to a count
-  QuicStreamId stream_count =
-      StreamIdToCount(version_.transport_version, frame.max_stream_id);
-
-  if (!writer->WriteVarInt62(stream_count)) {
+  if (!writer->WriteVarInt62(frame.stream_count)) {
     set_detailed_error("Can not write MAX_STREAMS stream count");
     return false;
   }
@@ -5489,33 +5421,14 @@
 }
 
 bool QuicFramer::ProcessMaxStreamsFrame(QuicDataReader* reader,
-                                        QuicMaxStreamIdFrame* frame,
+                                        QuicMaxStreamsFrame* frame,
                                         uint64_t frame_type) {
-  QuicStreamId received_stream_count;
-  if (!reader->ReadVarIntStreamId(&received_stream_count)) {
+  if (!reader->ReadVarIntU32(&frame->stream_count)) {
     set_detailed_error("Can not read MAX_STREAMS stream count.");
     return false;
   }
-  // TODO(fkastenholz): handle properly when the STREAMS_BLOCKED
-  // frame is implemented and passed up to the stream ID manager.
-  if (received_stream_count == 0) {
-    set_detailed_error("MAX_STREAMS stream count of 0 not supported.");
-    return false;
-  }
-  // Note that this code assumes that the only possible error that
-  // StreamCountToId can detect is that the stream count is too big or is 0.
-  // Too big is prevented by passing in the minimum of the received count
-  // and the maximum supported count, ensuring that the stream ID is
-  // pegged at the maximum allowed ID.
-  // count==0 is handled above, so that detailed_error_ may be set
-  // properly.
-  return StreamCountToId(
-      std::min(
-          received_stream_count,
-          GetMaxStreamCount((frame_type == IETF_MAX_STREAMS_UNIDIRECTIONAL),
-                            perspective_)),
-      /*unidirectional=*/(frame_type == IETF_MAX_STREAMS_UNIDIRECTIONAL),
-      perspective_, version_.transport_version, &frame->max_stream_id);
+  frame->unidirectional = (frame_type == IETF_MAX_STREAMS_UNIDIRECTIONAL);
+  return true;
 }
 
 bool QuicFramer::AppendIetfBlockedFrame(const QuicBlockedFrame& frame,
@@ -5553,7 +5466,7 @@
 
 bool QuicFramer::ProcessStreamBlockedFrame(QuicDataReader* reader,
                                            QuicBlockedFrame* frame) {
-  if (!reader->ReadVarIntStreamId(&frame->stream_id)) {
+  if (!reader->ReadVarIntU32(&frame->stream_id)) {
     set_detailed_error("Can not read stream blocked stream id.");
     return false;
   }
@@ -5564,14 +5477,9 @@
   return true;
 }
 
-bool QuicFramer::AppendStreamsBlockedFrame(
-    const QuicStreamIdBlockedFrame& frame,
-    QuicDataWriter* writer) {
-  // Convert from the stream id on which the connection is blocked to a count
-  QuicStreamId stream_count =
-      StreamIdToCount(version_.transport_version, frame.stream_id);
-
-  if (!writer->WriteVarInt62(stream_count)) {
+bool QuicFramer::AppendStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame,
+                                           QuicDataWriter* writer) {
+  if (!writer->WriteVarInt62(frame.stream_count)) {
     set_detailed_error("Can not write STREAMS_BLOCKED stream count");
     return false;
   }
@@ -5579,43 +5487,29 @@
 }
 
 bool QuicFramer::ProcessStreamsBlockedFrame(QuicDataReader* reader,
-                                            QuicStreamIdBlockedFrame* frame,
+                                            QuicStreamsBlockedFrame* frame,
                                             uint64_t frame_type) {
-  QuicStreamId received_stream_count;
-  if (!reader->ReadVarIntStreamId(&received_stream_count)) {
-    set_detailed_error("Can not read STREAMS_BLOCKED stream id.");
+  if (!reader->ReadVarIntU32(&frame->stream_count)) {
+    set_detailed_error("Can not read STREAMS_BLOCKED stream count.");
     return false;
   }
+  frame->unidirectional = (frame_type == IETF_STREAMS_BLOCKED_UNIDIRECTIONAL);
+
   // TODO(fkastenholz): handle properly when the STREAMS_BLOCKED
   // frame is implemented and passed up to the stream ID manager.
-  if (received_stream_count == 0) {
-    set_detailed_error("STREAMS_BLOCKED stream count 0 not supported.");
-    return false;
-  }
-  // TODO(fkastenholz): handle properly when the STREAMS_BLOCKED
-  // frame is implemented and passed up to the stream ID manager.
-  if (received_stream_count >
-      GetMaxStreamCount((frame_type == IETF_MAX_STREAMS_UNIDIRECTIONAL),
-                        ((perspective_ == Perspective::IS_CLIENT)
-                             ? Perspective::IS_SERVER
-                             : Perspective::IS_CLIENT))) {
+  if (frame->stream_count >
+      QuicUtils::GetMaxStreamCount(
+          (frame_type == IETF_STREAMS_BLOCKED_UNIDIRECTIONAL),
+          ((perspective_ == Perspective::IS_CLIENT)
+               ? Perspective::IS_SERVER
+               : Perspective::IS_CLIENT))) {
     // If stream count is such that the resulting stream ID would exceed our
     // implementation limit, generate an error.
     set_detailed_error(
         "STREAMS_BLOCKED stream count exceeds implementation limit.");
     return false;
   }
-  // Convert the stream count to an ID that can be used.
-  // The STREAMS_BLOCKED frame is a request for more streams
-  // that the peer will initiate. If this node is a client, it
-  // means that the peer is a server, and wants server-initiated
-  // stream IDs.
-  return StreamCountToId(
-      received_stream_count,
-      /*unidirectional=*/(frame_type == IETF_STREAMS_BLOCKED_UNIDIRECTIONAL),
-      (perspective_ == Perspective::IS_CLIENT) ? Perspective::IS_SERVER
-                                               : Perspective::IS_CLIENT,
-      version_.transport_version, &frame->stream_id);
+  return true;
 }
 
 bool QuicFramer::AppendNewConnectionIdFrame(
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index 90ef998..b20ac92 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -201,12 +201,11 @@
   virtual void OnAuthenticatedIetfStatelessResetPacket(
       const QuicIetfStatelessResetPacket& packet) = 0;
 
-  // Called when an IETF MaxStreamId frame has been parsed.
-  virtual bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) = 0;
+  // Called when an IETF MaxStreams frame has been parsed.
+  virtual bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) = 0;
 
-  // Called when an IETF StreamIdBlocked frame has been parsed.
-  virtual bool OnStreamIdBlockedFrame(
-      const QuicStreamIdBlockedFrame& frame) = 0;
+  // Called when an IETF StreamsBlocked frame has been parsed.
+  virtual bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) = 0;
 };
 
 // Class for parsing and constructing QUIC packets.  It has a
@@ -309,11 +308,11 @@
                                          const QuicWindowUpdateFrame& frame);
   // Size in bytes of all MaxStreams frame fields.
   static size_t GetMaxStreamsFrameSize(QuicTransportVersion version,
-                                       const QuicMaxStreamIdFrame& frame);
+                                       const QuicMaxStreamsFrame& frame);
   // Size in bytes of all StreamsBlocked frame fields.
   static size_t GetStreamsBlockedFrameSize(
       QuicTransportVersion version,
-      const QuicStreamIdBlockedFrame& frame);
+      const QuicStreamsBlockedFrame& frame);
   // Size in bytes of all Blocked frame fields.
   static size_t GetBlockedFrameSize(QuicTransportVersion version,
                                     const QuicBlockedFrame& frame);
@@ -809,10 +808,10 @@
   bool ProcessMaxStreamDataFrame(QuicDataReader* reader,
                                  QuicWindowUpdateFrame* frame);
 
-  bool AppendMaxStreamsFrame(const QuicMaxStreamIdFrame& frame,
+  bool AppendMaxStreamsFrame(const QuicMaxStreamsFrame& frame,
                              QuicDataWriter* writer);
   bool ProcessMaxStreamsFrame(QuicDataReader* reader,
-                              QuicMaxStreamIdFrame* frame,
+                              QuicMaxStreamsFrame* frame,
                               uint64_t frame_type);
 
   bool AppendIetfBlockedFrame(const QuicBlockedFrame& frame,
@@ -824,10 +823,10 @@
   bool ProcessStreamBlockedFrame(QuicDataReader* reader,
                                  QuicBlockedFrame* frame);
 
-  bool AppendStreamsBlockedFrame(const QuicStreamIdBlockedFrame& frame,
+  bool AppendStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame,
                                  QuicDataWriter* writer);
   bool ProcessStreamsBlockedFrame(QuicDataReader* reader,
-                                  QuicStreamIdBlockedFrame* frame,
+                                  QuicStreamsBlockedFrame* frame,
                                   uint64_t frame_type);
 
   bool AppendNewConnectionIdFrame(const QuicNewConnectionIdFrame& frame,
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 6892ea2..1ce9067 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -335,13 +335,13 @@
     return true;
   }
 
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override {
-    max_stream_id_frame_ = frame;
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override {
+    max_streams_frame_ = frame;
     return true;
   }
 
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override {
-    stream_id_blocked_frame_ = frame;
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override {
+    streams_blocked_frame_ = frame;
     return true;
   }
 
@@ -410,8 +410,8 @@
   QuicPathResponseFrame path_response_frame_;
   QuicWindowUpdateFrame window_update_frame_;
   QuicBlockedFrame blocked_frame_;
-  QuicStreamIdBlockedFrame stream_id_blocked_frame_;
-  QuicMaxStreamIdFrame max_stream_id_frame_;
+  QuicStreamsBlockedFrame streams_blocked_frame_;
+  QuicMaxStreamsFrame max_streams_frame_;
   QuicNewConnectionIdFrame new_connection_id_;
   QuicRetireConnectionIdFrame retire_connection_id_;
   QuicNewTokenFrame new_token_;
@@ -10106,7 +10106,7 @@
                                       QUIC_ARRAYSIZE(packet99));
 }
 
-TEST_P(QuicFramerTest, ServerBiDiMaxStreamsFrame) {
+TEST_P(QuicFramerTest, BiDiMaxStreamsFrame) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
     return;
@@ -10143,18 +10143,12 @@
       *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a server receiving a MAX_STREAMS frame. The
-  // stream ID that it generates should be a server-initiated
-  // stream ID. The expected Stream ID is
-  //                 ((0x3-1) * 4) | 0x1 = 0x9
-  //                  count-to-id      server inited, bidi
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                           /*bidirectional=*/true, 3),
-            visitor_.max_stream_id_frame_.max_stream_id);
-  CheckFramingBoundaries(packet99, QUIC_MAX_STREAM_ID_DATA);
+  EXPECT_EQ(3u, visitor_.max_streams_frame_.stream_count);
+  EXPECT_FALSE(visitor_.max_streams_frame_.unidirectional);
+  CheckFramingBoundaries(packet99, QUIC_MAX_STREAMS_DATA);
 }
 
-TEST_P(QuicFramerTest, ClientBiDiMaxStreamsFrame) {
+TEST_P(QuicFramerTest, UniDiMaxStreamsFrame) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
     return;
@@ -10170,9 +10164,9 @@
       // packet number
       {"",
        {0x12, 0x34, 0x9A, 0xBC}},
-      // frame type (IETF_MAX_STREAMS_BIDIRECTIONAL)
+      // frame type (IETF_MAX_STREAMS_UNIDIRECTIONAL)
       {"",
-       {0x12}},
+       {0x13}},
       // max. streams
       {"Can not read MAX_STREAMS stream count.",
        {kVarInt62OneByte + 0x03}},
@@ -10190,19 +10184,9 @@
       *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a client receiving a MAX_STREAMS frame. The
-  // stream ID that it generates should be a client-initiated
-  // stream ID. The expected Stream ID is
-  //                ((0x3-1) * 4)       = 0xc
-  // It is not 8 because a client-initiated, bidi stream ID's
-  // low bits are 00 - which means that the old crypto stream
-  // falls into this category, and the first stream is streamid=4,
-  // not streamid=0.
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                           /*bidirectional=*/true, 3),
-            visitor_.max_stream_id_frame_.max_stream_id);
-
-  CheckFramingBoundaries(packet99, QUIC_MAX_STREAM_ID_DATA);
+  EXPECT_EQ(3u, visitor_.max_streams_frame_.stream_count);
+  EXPECT_TRUE(visitor_.max_streams_frame_.unidirectional);
+  CheckFramingBoundaries(packet99, QUIC_MAX_STREAMS_DATA);
 }
 
 TEST_P(QuicFramerTest, ServerUniDiMaxStreamsFrame) {
@@ -10242,16 +10226,9 @@
       *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a server receiving a MAX_STREAMS frame. The
-  // stream ID that it generates should be a server-initiated
-  // stream ID. The expected Stream ID is
-  //      ((0x3-1) * 4) | 0x1 | 0x2 = 0xb
-  //        count-to-id      server inited, unidi
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                           /*bidirectional=*/false, 3),
-            visitor_.max_stream_id_frame_.max_stream_id);
-
-  CheckFramingBoundaries(packet99, QUIC_MAX_STREAM_ID_DATA);
+  EXPECT_EQ(3u, visitor_.max_streams_frame_.stream_count);
+  EXPECT_TRUE(visitor_.max_streams_frame_.unidirectional);
+  CheckFramingBoundaries(packet99, QUIC_MAX_STREAMS_DATA);
 }
 
 TEST_P(QuicFramerTest, ClientUniDiMaxStreamsFrame) {
@@ -10290,16 +10267,9 @@
       *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a client receiving a MAX_STREAMS frame. The
-  // stream ID that it generates should be a client-initiated
-  // stream ID. The expected Stream ID is
-  //               ((0x3-1) * 4) | 0x02= 0xa
-  //                count-to-id      client/unidi
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                           /*bidirectional=*/false, 3),
-            visitor_.max_stream_id_frame_.max_stream_id);
-
-  CheckFramingBoundaries(packet99, QUIC_MAX_STREAM_ID_DATA);
+  EXPECT_EQ(3u, visitor_.max_streams_frame_.stream_count);
+  EXPECT_TRUE(visitor_.max_streams_frame_.unidirectional);
+  CheckFramingBoundaries(packet99, QUIC_MAX_STREAMS_DATA);
 }
 
 // The following four tests ensure that the framer can deserialize a stream
@@ -10308,7 +10278,7 @@
 // the stream limit is pegged to the maximum supported value. There are four
 // tests, for the four combinations of uni- and bi-directional, server- and
 // client- initiated.
-TEST_P(QuicFramerTest, ServerBiDiMaxStreamsFrameTooBig) {
+TEST_P(QuicFramerTest, BiDiMaxStreamsFrameTooBig) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
     return;
@@ -10342,14 +10312,8 @@
       encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a server receiving a MAX_STREAMS frame. The
-  // stream ID that it generates should be a server-initiated
-  // stream ID. The expected Stream ID is
-  //            0xfffffffc | 0x01  --> 0xfffffffd
-  //              maxid     server inited, bidi
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                           /*bidirectional=*/true, 0x40000000),
-            visitor_.max_stream_id_frame_.max_stream_id);
+  EXPECT_EQ(0x40000000, visitor_.max_streams_frame_.stream_count);
+  EXPECT_FALSE(visitor_.max_streams_frame_.unidirectional);
 }
 
 TEST_P(QuicFramerTest, ClientBiDiMaxStreamsFrameTooBig) {
@@ -10387,19 +10351,8 @@
       encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a client receiving a MAX_STREAMS frame. The
-  // stream ID that it generates should be a client-initiated
-  // stream ID. The expected Stream ID is
-  //            0xfffffffc         --> 0xfffffffc
-  //            max id       bidi/client-inited
-  // TODO(fkastenholz): Change -2 to -1 when stream id 0 is no longer
-  // special.
-  // Subtract 1 because client/bidi stream ids start counting at
-  // 4, not 0. If we didn;t subtract 1, the resulting math would wrap to stream
-  // id 0, not 0xfffffffc.
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                           /*bidirectional=*/true, (0x40000000 - 1)),
-            visitor_.max_stream_id_frame_.max_stream_id);
+  EXPECT_EQ(0x40000000, visitor_.max_streams_frame_.stream_count);
+  EXPECT_FALSE(visitor_.max_streams_frame_.unidirectional);
 }
 
 TEST_P(QuicFramerTest, ServerUniDiMaxStreamsFrameTooBig) {
@@ -10437,14 +10390,8 @@
       encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a server receiving a MAX_STREAMS frame. The
-  // stream ID that it generates should be a server-initiated
-  // stream ID. The expected Stream ID is
-  //      0xfffffffc | 0x1 | 0x2 = 0xffffffff
-  //        maxid      server inited, unidi
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                           /*bidirectional=*/false, 0x40000000),
-            visitor_.max_stream_id_frame_.max_stream_id);
+  EXPECT_EQ(0x40000000, visitor_.max_streams_frame_.stream_count);
+  EXPECT_TRUE(visitor_.max_streams_frame_.unidirectional);
 }
 
 TEST_P(QuicFramerTest, ClientUniDiMaxStreamsFrameTooBig) {
@@ -10482,19 +10429,11 @@
       encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a client receiving a MAX_STREAMS frame. The
-  // stream ID that it generates should be a client-initiated
-  // stream ID. The expected Stream ID is
-  //               0xfffffffc | 0x02= 0xfffffffe
-  //                maxid       client/unidi
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                           /*bidirectional=*/false, 0x40000000),
-            visitor_.max_stream_id_frame_.max_stream_id);
+  EXPECT_EQ(0x40000000, visitor_.max_streams_frame_.stream_count);
+  EXPECT_TRUE(visitor_.max_streams_frame_.unidirectional);
 }
 
-// Check that a stream count of 0 is rejected.
-// Directionality and intiation are not important for
-// this test.
+// Specifically test that count==0 is accepted.
 TEST_P(QuicFramerTest, MaxStreamsFrameZeroCount) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
@@ -10519,10 +10458,7 @@
 
   QuicEncryptedPacket encrypted(AsChars(packet99), QUIC_ARRAYSIZE(packet99),
                                 false);
-  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
-  EXPECT_EQ(QUIC_MAX_STREAM_ID_DATA, framer_.error());
-  EXPECT_EQ(framer_.detailed_error(),
-            "MAX_STREAMS stream count of 0 not supported.");
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
 }
 
 TEST_P(QuicFramerTest, ServerBiDiStreamsBlockedFrame) {
@@ -10543,11 +10479,54 @@
       // packet number
       {"",
        {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_MAX_STREAMS_UNIDIRECTIONAL frame)
+      {"",
+       {0x13}},
+      // stream count
+      {"Can not read MAX_STREAMS stream count.",
+       {kVarInt62OneByte + 0x00}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.max_streams_frame_.stream_count);
+  EXPECT_TRUE(visitor_.max_streams_frame_.unidirectional);
+
+  CheckFramingBoundaries(packet99, QUIC_MAX_STREAMS_DATA);
+}
+
+TEST_P(QuicFramerTest, BiDiStreamsBlockedFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+  SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
       // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL frame)
       {"",
        {0x16}},
       // stream id
-      {"Can not read STREAMS_BLOCKED stream id.",
+      {"Can not read STREAMS_BLOCKED stream count.",
        {kVarInt62OneByte + 0x03}},
   };
   // clang-format on
@@ -10562,71 +10541,13 @@
       *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a server receiving a STREAMS_BLOCKED frame. The
-  // stream ID that it generates should be a client-initiated
-  // stream ID. The expected Stream ID is
-  //                ((0x3-1) * 4)        = 0xc
-  //                 count-to-id      client inited, bidi
-  // It is not 8 because a client-initiated, bidi stream ID's
-  // low bits are 00 - which means that the old crypto stream
-  // falls into this category, and the first stream is streamid=4,
-  // not streamid=0.
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                           /*bidirectional=*/true, 3),
-            visitor_.stream_id_blocked_frame_.stream_id);
+  EXPECT_EQ(3u, visitor_.streams_blocked_frame_.stream_count);
+  EXPECT_FALSE(visitor_.streams_blocked_frame_.unidirectional);
 
-  CheckFramingBoundaries(packet99, QUIC_STREAM_ID_BLOCKED_DATA);
+  CheckFramingBoundaries(packet99, QUIC_STREAMS_BLOCKED_DATA);
 }
 
-TEST_P(QuicFramerTest, ClientBiDiStreamsBlockedFrame) {
-  // This test only for version 99.
-  if (framer_.transport_version() != QUIC_VERSION_99) {
-    return;
-  }
-  SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
-
-  // clang-format off
-  PacketFragments packet99 = {
-      // type (short header, 4 byte packet number)
-      {"",
-       {0x43}},
-      // Test runs in client mode, no connection id
-      // packet number
-      {"",
-       {0x12, 0x34, 0x9A, 0xBC}},
-      // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL frame)
-      {"",
-       {0x16}},
-      // stream id
-      {"Can not read STREAMS_BLOCKED stream id.",
-       {kVarInt62OneByte + 0x03}},
-  };
-  // clang-format on
-
-  std::unique_ptr<QuicEncryptedPacket> encrypted(
-      AssemblePacketFromFragments(packet99));
-  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
-  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
-
-  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
-  ASSERT_TRUE(visitor_.header_.get());
-  EXPECT_TRUE(CheckDecryption(
-      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
-      PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
-
-  // This test is a client receiving a STREAMS_BLOCKED frame. The
-  // stream ID that it generates should be a server-initiated
-  // stream ID. The expected Stream ID is
-  //                ((0x3-1) * 4) | 0x01 = 0x9
-  //                 count-to-id      server inited, bidi
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                           /*bidirectional=*/true, 3),
-            visitor_.stream_id_blocked_frame_.stream_id);
-
-  CheckFramingBoundaries(packet99, QUIC_STREAM_ID_BLOCKED_DATA);
-}
-
-TEST_P(QuicFramerTest, ServerUniDiStreamsBlockedFrame) {
+TEST_P(QuicFramerTest, UniDiStreamsBlockedFrame) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
     return;
@@ -10648,7 +10569,7 @@
       {"",
        {0x17}},
       // stream id
-      {"Can not read STREAMS_BLOCKED stream id.",
+      {"Can not read STREAMS_BLOCKED stream count.",
        {kVarInt62OneByte + 0x03}},
   };
   // clang-format on
@@ -10663,16 +10584,9 @@
       *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a server receiving a STREAMS_BLOCKED frame. The
-  // stream ID that it generates should be a client-initiated
-  // stream ID. The expected Stream ID is
-  //                ((0x3-1) * 4)  | 0x2  = 0xa
-  //                 count-to-id      client inited, unidi
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                           /*bidirectional=*/false, 3),
-            visitor_.stream_id_blocked_frame_.stream_id);
-
-  CheckFramingBoundaries(packet99, QUIC_STREAM_ID_BLOCKED_DATA);
+  EXPECT_EQ(3u, visitor_.streams_blocked_frame_.stream_count);
+  EXPECT_TRUE(visitor_.streams_blocked_frame_.unidirectional);
+  CheckFramingBoundaries(packet99, QUIC_STREAMS_BLOCKED_DATA);
 }
 
 TEST_P(QuicFramerTest, ClientUniDiStreamsBlockedFrame) {
@@ -10695,7 +10609,7 @@
       {"",
        {0x17}},
       // stream id
-      {"Can not read STREAMS_BLOCKED stream id.",
+      {"Can not read STREAMS_BLOCKED stream count.",
        {kVarInt62OneByte + 0x03}},
   };
   // clang-format on
@@ -10711,16 +10625,9 @@
       *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
       PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
 
-  // This test is a client receiving a STREAMS_BLOCKED frame. The
-  // stream ID that it generates should be a server-initiated
-  // stream ID. The expected Stream ID is
-  //                ((0x3-1) * 4) | 0x01 | 0x2 = 0xb
-  //                 count-to-id      server inited, bidi
-  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                           /*bidirectional=*/false, 3),
-            visitor_.stream_id_blocked_frame_.stream_id);
-
-  CheckFramingBoundaries(packet99, QUIC_STREAM_ID_BLOCKED_DATA);
+  EXPECT_EQ(3u, visitor_.streams_blocked_frame_.stream_count);
+  EXPECT_TRUE(visitor_.streams_blocked_frame_.unidirectional);
+  CheckFramingBoundaries(packet99, QUIC_STREAMS_BLOCKED_DATA);
 }
 
 // Check that when we get a STREAMS_BLOCKED frame that specifies too large
@@ -10742,7 +10649,7 @@
     // packet number
     0x12, 0x34, 0x9A, 0xBC,
     // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL)
-    0x17,
+    0x16,
 
     // max. streams. Max stream ID allowed is 0xffffffff
     // This encodes a count of 0x40000000, leading to stream
@@ -10756,45 +10663,57 @@
   QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
   EXPECT_FALSE(framer_.ProcessPacket(encrypted));
 
-  EXPECT_EQ(QUIC_STREAM_ID_BLOCKED_DATA, framer_.error());
+  EXPECT_EQ(QUIC_STREAMS_BLOCKED_DATA, framer_.error());
   EXPECT_EQ(framer_.detailed_error(),
             "STREAMS_BLOCKED stream count exceeds implementation limit.");
 }
 
-// Test that count==0 is rejected.
+// Specifically test that count==0 is accepted.
 TEST_P(QuicFramerTest, StreamsBlockedFrameZeroCount) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
     return;
   }
+
   SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
 
   // clang-format off
-  unsigned char packet99[] = {
-    // type (short header, 4 byte packet number)
-    0x43,
-    // Test runs in client mode, no connection id
-    // packet number
-    0x12, 0x34, 0x9A, 0xBC,
-    // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL)
-    0x17,
-
-    // max. streams = 0
-    kVarInt62OneByte + 0x00
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_STREAMS_BLOCKED_UNIDIRECTIONAL frame)
+      {"",
+       {0x17}},
+      // stream id
+      {"Can not read STREAMS_BLOCKED stream count.",
+       {kVarInt62OneByte + 0x00}},
   };
   // clang-format on
 
-  QuicEncryptedPacket encrypted(AsChars(packet99), QUIC_ARRAYSIZE(packet99),
-                                false);
-  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
-  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
 
-  EXPECT_EQ(QUIC_STREAM_ID_BLOCKED_DATA, framer_.error());
-  EXPECT_EQ(framer_.detailed_error(),
-            "STREAMS_BLOCKED stream count 0 not supported.");
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.streams_blocked_frame_.stream_count);
+  EXPECT_TRUE(visitor_.streams_blocked_frame_.unidirectional);
+
+  CheckFramingBoundaries(packet99, QUIC_STREAMS_BLOCKED_DATA);
 }
 
-TEST_P(QuicFramerTest, BuildServerBiDiStreamsBlockedPacket) {
+TEST_P(QuicFramerTest, BuildBiDiStreamsBlockedPacket) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
     return;
@@ -10806,13 +10725,9 @@
   header.version_flag = false;
   header.packet_number = kPacketNumber;
 
-  QuicStreamIdBlockedFrame frame;
-  // A server building a STREAMS_BLOCKED frame generates
-  // a server-initiated stream ID. This test is bidirectional.
-  // The low two bits of the stream ID are 01
-  // Expected value is 0x8u | 0x1u;
-  frame.stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                                   /*bidirectional=*/true, 3);
+  QuicStreamsBlockedFrame frame;
+  frame.stream_count = 3;
+  frame.unidirectional = false;
 
   QuicFrames frames = {QuicFrame(frame)};
 
@@ -10840,54 +10755,7 @@
                                       QUIC_ARRAYSIZE(packet99));
 }
 
-TEST_P(QuicFramerTest, BuildClientBiDiStreamsBlockedPacket) {
-  // This test only for version 99.
-  if (framer_.transport_version() != QUIC_VERSION_99) {
-    return;
-  }
-
-  // This test runs in client mode.
-  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
-
-  QuicPacketHeader header;
-  header.destination_connection_id = FramerTestConnectionId();
-  header.reset_flag = false;
-  header.version_flag = false;
-  header.packet_number = kPacketNumber;
-
-  QuicStreamIdBlockedFrame frame;
-  // A client building a STREAMS_BLOCKED frame generates
-  // a client-initiated stream ID. This test is bidirectional.
-  // The low two bits of the stream ID are 00. Expected value is 0x8
-  frame.stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                                   /*bidirectional=*/true, 3);
-  QuicFrames frames = {QuicFrame(frame)};
-
-  // clang-format off
-  unsigned char packet99[] = {
-    // type (short header, 4 byte packet number)
-    0x43,
-    // connection_id
-    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
-    // packet number
-    0x12, 0x34, 0x56, 0x78,
-
-    // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL frame)
-    0x16,
-    // Stream count
-    kVarInt62OneByte + 0x03
-  };
-  // clang-format on
-
-  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
-  ASSERT_TRUE(data != nullptr);
-
-  test::CompareCharArraysWithHexError("constructed packet", data->data(),
-                                      data->length(), AsChars(packet99),
-                                      QUIC_ARRAYSIZE(packet99));
-}
-
-TEST_P(QuicFramerTest, BuildServerUniStreamsBlockedPacket) {
+TEST_P(QuicFramerTest, BuildUniStreamsBlockedPacket) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
     return;
@@ -10899,12 +10767,10 @@
   header.version_flag = false;
   header.packet_number = kPacketNumber;
 
-  QuicStreamIdBlockedFrame frame;
-  // A server building a STREAMS_BLOCKED frame generates
-  // a server-initiated stream ID. This test is bidirectional.
-  // The low two bits of the stream ID are 11. Expected value is 0xb
-  frame.stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                                   /*bidirectional=*/false, 3);
+  QuicStreamsBlockedFrame frame;
+  frame.stream_count = 3;
+  frame.unidirectional = true;
+
   QuicFrames frames = {QuicFrame(frame)};
 
   // clang-format off
@@ -10931,54 +10797,7 @@
                                       QUIC_ARRAYSIZE(packet99));
 }
 
-TEST_P(QuicFramerTest, BuildClientUniDiStreamsBlockedPacket) {
-  // This test only for version 99.
-  if (framer_.transport_version() != QUIC_VERSION_99) {
-    return;
-  }
-
-  // This test runs in client mode.
-  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
-
-  QuicPacketHeader header;
-  header.destination_connection_id = FramerTestConnectionId();
-  header.reset_flag = false;
-  header.version_flag = false;
-  header.packet_number = kPacketNumber;
-
-  QuicStreamIdBlockedFrame frame;
-  // A client building a STREAMS_BLOCKED frame generates
-  // a client-initiated stream ID. This test is bidirectional.
-  // The low two bits of the stream ID are 10. Expected value is 0xa
-  frame.stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                                   /*bidirectional=*/false, 3);
-  QuicFrames frames = {QuicFrame(frame)};
-
-  // clang-format off
-  unsigned char packet99[] = {
-    // type (short header, 4 byte packet number)
-    0x43,
-    // connection_id
-    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
-    // packet number
-    0x12, 0x34, 0x56, 0x78,
-
-    // frame type (IETF_STREAMS_BLOCKED_UNIDIRECTIONAL frame)
-    0x17,
-    // Stream count
-    kVarInt62OneByte + 0x03
-  };
-  // clang-format on
-
-  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
-  ASSERT_TRUE(data != nullptr);
-
-  test::CompareCharArraysWithHexError("constructed packet", data->data(),
-                                      data->length(), AsChars(packet99),
-                                      QUIC_ARRAYSIZE(packet99));
-}
-
-TEST_P(QuicFramerTest, BuildServerBiDiMaxStreamsPacket) {
+TEST_P(QuicFramerTest, BuildBiDiMaxStreamsPacket) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
     return;
@@ -10990,14 +10809,10 @@
   header.version_flag = false;
   header.packet_number = kPacketNumber;
 
-  QuicMaxStreamIdFrame frame;
-  // A server building a MAX_STREAMS frame generates
-  // a client-initiated stream ID. This test is bidirectional.
-  // The low two bits of the stream ID are 00. Expected value is 0xc
-  // because streamid==0 is special and the first client/bidi
-  // stream is 4, not 0.
-  frame.max_stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                                       /*bidirectional=*/true, 3);
+  QuicMaxStreamsFrame frame;
+  frame.stream_count = 3;
+  frame.unidirectional = false;
+
   QuicFrames frames = {QuicFrame(frame)};
 
   // clang-format off
@@ -11024,7 +10839,7 @@
                                       QUIC_ARRAYSIZE(packet99));
 }
 
-TEST_P(QuicFramerTest, BuildClientBiDiMaxStreamsPacket) {
+TEST_P(QuicFramerTest, BuildUniDiMaxStreamsPacket) {
   // This test only for version 99.
   if (framer_.transport_version() != QUIC_VERSION_99) {
     return;
@@ -11039,103 +10854,10 @@
   header.version_flag = false;
   header.packet_number = kPacketNumber;
 
-  QuicMaxStreamIdFrame frame;
-  // A client building a MAX_STREAMS frame generates
-  // a server-initiated stream ID. This test is bidirectional.
-  // The low two bits of the stream ID are 01. Expected value is 0x9
-  frame.max_stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                                       /*bidirectional=*/true, 3);
-  QuicFrames frames = {QuicFrame(frame)};
+  QuicMaxStreamsFrame frame;
+  frame.stream_count = 3;
+  frame.unidirectional = true;
 
-  // clang-format off
-  unsigned char packet99[] = {
-    // type (short header, 4 byte packet number)
-    0x43,
-    // connection_id
-    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
-    // packet number
-    0x12, 0x34, 0x56, 0x78,
-
-    // frame type (IETF_MAX_STREAMS_BIDIRECTIONAL frame)
-    0x12,
-    // Stream count
-    kVarInt62OneByte + 0x03
-  };
-  // clang-format on
-
-  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
-  ASSERT_TRUE(data != nullptr);
-
-  test::CompareCharArraysWithHexError("constructed packet", data->data(),
-                                      data->length(), AsChars(packet99),
-                                      QUIC_ARRAYSIZE(packet99));
-}
-
-TEST_P(QuicFramerTest, BuildServerUniMaxStreamsPacket) {
-  // This test only for version 99.
-  if (framer_.transport_version() != QUIC_VERSION_99) {
-    return;
-  }
-
-  QuicPacketHeader header;
-  header.destination_connection_id = FramerTestConnectionId();
-  header.reset_flag = false;
-  header.version_flag = false;
-  header.packet_number = kPacketNumber;
-
-  QuicMaxStreamIdFrame frame;
-  // A server building a MAX_STREAMS frame generates
-  // a client-initiated stream ID. This test is bidirectional.
-  // The low two bits of the stream ID are 10. Expected value is 0xa
-  frame.max_stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
-                                       /*bidirectional=*/false, 3);
-  QuicFrames frames = {QuicFrame(frame)};
-
-  // clang-format off
-  unsigned char packet99[] = {
-    // type (short header, 4 byte packet number)
-    0x43,
-    // connection_id
-    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
-    // packet number
-    0x12, 0x34, 0x56, 0x78,
-
-    // frame type (IETF_MAX_STREAMS_UNIDIRECTIONAL frame)
-    0x13,
-    // Stream count
-    kVarInt62OneByte + 0x03
-  };
-  // clang-format on
-
-  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
-  ASSERT_TRUE(data != nullptr);
-
-  test::CompareCharArraysWithHexError("constructed packet", data->data(),
-                                      data->length(), AsChars(packet99),
-                                      QUIC_ARRAYSIZE(packet99));
-}
-
-TEST_P(QuicFramerTest, BuildClientUniDiMaxStreamsPacket) {
-  // This test only for version 99.
-  if (framer_.transport_version() != QUIC_VERSION_99) {
-    return;
-  }
-
-  // This test runs in client mode.
-  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
-
-  QuicPacketHeader header;
-  header.destination_connection_id = FramerTestConnectionId();
-  header.reset_flag = false;
-  header.version_flag = false;
-  header.packet_number = kPacketNumber;
-
-  QuicMaxStreamIdFrame frame;
-  // A client building a MAX_STREAMS frame generates
-  // a server-initiated stream ID. This test is bidirectional.
-  // The low two bits of the stream ID are 11. Expected value is 0xb
-  frame.max_stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
-                                       /*bidirectional=*/false, 3);
   QuicFrames frames = {QuicFrame(frame)};
 
   // clang-format off
@@ -11762,17 +11484,17 @@
             QuicFramer::GetRetransmittableControlFrameSize(
                 framer_.transport_version(), QuicFrame(&new_connection_id)));
 
-  QuicMaxStreamIdFrame max_stream_id(6, 3);
+  QuicMaxStreamsFrame max_streams(6, 3, /*unidirectional=*/false);
   EXPECT_EQ(QuicFramer::GetMaxStreamsFrameSize(framer_.transport_version(),
-                                               max_stream_id),
+                                               max_streams),
             QuicFramer::GetRetransmittableControlFrameSize(
-                framer_.transport_version(), QuicFrame(max_stream_id)));
+                framer_.transport_version(), QuicFrame(max_streams)));
 
-  QuicStreamIdBlockedFrame stream_id_blocked(7, 3);
+  QuicStreamsBlockedFrame streams_blocked(7, 3, /*unidirectional=*/false);
   EXPECT_EQ(QuicFramer::GetStreamsBlockedFrameSize(framer_.transport_version(),
-                                                   stream_id_blocked),
+                                                   streams_blocked),
             QuicFramer::GetRetransmittableControlFrameSize(
-                framer_.transport_version(), QuicFrame(stream_id_blocked)));
+                framer_.transport_version(), QuicFrame(streams_blocked)));
 
   QuicPathFrameBuffer buffer = {
       {0x80, 0x91, 0xa2, 0xb3, 0xc4, 0xd5, 0xe5, 0xf7}};
diff --git a/quic/core/quic_ietf_framer_test.cc b/quic/core/quic_ietf_framer_test.cc
index b0c0125..fa3aca4 100644
--- a/quic/core/quic_ietf_framer_test.cc
+++ b/quic/core/quic_ietf_framer_test.cc
@@ -192,11 +192,11 @@
   void OnAuthenticatedIetfStatelessResetPacket(
       const QuicIetfStatelessResetPacket& packet) override {}
 
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override {
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override {
     return true;
   }
 
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override {
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override {
     return true;
   }
 };
@@ -455,28 +455,16 @@
     EXPECT_EQ(receive_frame.byte_offset, transmit_frame.byte_offset);
   }
 
-  void TryMaxStreamsFrame(QuicStreamId stream_id,
+  void TryMaxStreamsFrame(QuicStreamCount stream_count,
                           bool unidirectional,
                           bool stream_id_server_initiated) {
-    if (!unidirectional && !stream_id_server_initiated && stream_id == 0) {
-      // For bidirectional, client initiated, streams, 0 is not allowed,
-      // it's used for the crypto stream and is not included in the counting.
-      return;
-    }
-
     char packet_buffer[kNormalPacketBufferSize];
     memset(packet_buffer, 0, sizeof(packet_buffer));
 
     Perspective old_perspective = framer_.perspective();
-    // Set up the writer and transmit QuicMaxStreamIdFrame
+    // Set up the writer and transmit QuicMaxStreamsFrame
     QuicDataWriter writer(sizeof(packet_buffer), packet_buffer,
                           NETWORK_BYTE_ORDER);
-    if (stream_id_server_initiated) {
-      stream_id |= 0x01;
-    }
-    if (unidirectional) {
-      stream_id |= 0x02;
-    }
 
     // Set the perspective of the sender. If the stream id is supposed to
     // be server-initiated, then the sender of the MAX_STREAMS should be
@@ -485,7 +473,7 @@
     QuicFramerPeer::SetPerspective(&framer_, (stream_id_server_initiated)
                                                  ? Perspective::IS_CLIENT
                                                  : Perspective::IS_SERVER);
-    QuicMaxStreamIdFrame transmit_frame(0, stream_id);
+    QuicMaxStreamsFrame transmit_frame(0, stream_count, unidirectional);
 
     // Add the frame.
     EXPECT_TRUE(QuicFramerPeer::AppendMaxStreamsFrame(&framer_, transmit_frame,
@@ -502,7 +490,7 @@
 
     // Set up reader and empty receive QuicPaddingFrame.
     QuicDataReader reader(packet_buffer, writer.length(), NETWORK_BYTE_ORDER);
-    QuicMaxStreamIdFrame receive_frame;
+    QuicMaxStreamsFrame receive_frame;
 
     // Deframe it
     EXPECT_TRUE(QuicFramerPeer::ProcessMaxStreamsFrame(
@@ -512,42 +500,30 @@
         << " Error: " << framer_.detailed_error();
 
     // Now check that received and sent data are equivalent
-    EXPECT_EQ(stream_id, receive_frame.max_stream_id);
-    EXPECT_EQ(transmit_frame.max_stream_id, receive_frame.max_stream_id);
+    EXPECT_EQ(stream_count, receive_frame.stream_count);
+    EXPECT_EQ(transmit_frame.stream_count, receive_frame.stream_count);
     QuicFramerPeer::SetPerspective(&framer_, old_perspective);
   }
 
-  void TryStreamsBlockedFrame(QuicStreamId stream_id,
+  void TryStreamsBlockedFrame(QuicStreamCount stream_count,
                               bool unidirectional,
                               bool stream_id_server_initiated) {
-    if (!unidirectional && !stream_id_server_initiated && stream_id == 0) {
-      // For bidirectional, client initiated, streams, 0 is not allowed,
-      // it's used for the crypto stream and is not included in the counting.
-      return;
-    }
-
     char packet_buffer[kNormalPacketBufferSize];
     memset(packet_buffer, 0, sizeof(packet_buffer));
 
     Perspective old_perspective = framer_.perspective();
-    // Set up the writer and transmit QuicMaxStreamIdFrame
+    // Set up the writer and transmit QuicStreamsBlockedFrame
     QuicDataWriter writer(sizeof(packet_buffer), packet_buffer,
                           NETWORK_BYTE_ORDER);
-    if (stream_id_server_initiated) {
-      stream_id |= 0x01;
-    }
-    if (unidirectional) {
-      stream_id |= 0x02;
-    }
 
     // Set the perspective of the sender. If the stream id is supposed to
-    // be server-initiated, then the sender of the MAX_STREAMS should be
+    // be server-initiated, then the sender of the STREAMS_BLOCKED should be
     // a client, and vice versa. Do this prior to constructing the frame or
     // generating the packet, so that any internal dependencies are satisfied.
     QuicFramerPeer::SetPerspective(&framer_, (stream_id_server_initiated)
                                                  ? Perspective::IS_SERVER
                                                  : Perspective::IS_CLIENT);
-    QuicStreamIdBlockedFrame transmit_frame(0, stream_id);
+    QuicStreamsBlockedFrame transmit_frame(0, stream_count, unidirectional);
 
     // Add the frame.
     EXPECT_TRUE(QuicFramerPeer::AppendStreamsBlockedFrame(
@@ -564,7 +540,7 @@
 
     // Set up reader and empty receive QuicPaddingFrame.
     QuicDataReader reader(packet_buffer, writer.length(), NETWORK_BYTE_ORDER);
-    QuicStreamIdBlockedFrame receive_frame;
+    QuicStreamsBlockedFrame receive_frame;
 
     // Deframe it
     EXPECT_TRUE(QuicFramerPeer::ProcessStreamsBlockedFrame(
@@ -573,8 +549,8 @@
                          : IETF_STREAMS_BLOCKED_BIDIRECTIONAL));
 
     // Now check that received and sent data are equivalent
-    EXPECT_EQ(stream_id, receive_frame.stream_id);
-    EXPECT_EQ(transmit_frame.stream_id, receive_frame.stream_id);
+    EXPECT_EQ(stream_count, receive_frame.stream_count);
+    EXPECT_EQ(transmit_frame.stream_count, receive_frame.stream_count);
     QuicFramerPeer::SetPerspective(&framer_, old_perspective);
   }
 
@@ -1264,19 +1240,18 @@
 }
 
 TEST_F(QuicIetfFramerTest, MaxStreamsFrame) {
-  QuicIetfStreamId stream_ids[] = {kStreamId4, kStreamId2, kStreamId1,
-                                   kStreamId0};
+  QuicStreamCount stream_counts[] = {0x3fffffff, 0x3fff, 0x3f, 0x1};
 
-  for (QuicIetfStreamId stream_id : stream_ids) {
+  for (QuicStreamCount stream_count : stream_counts) {
     // Cover all four combinations of uni/bi-directional and
     // server-/client- initiation.
-    TryMaxStreamsFrame(stream_id, /*unidirectional=*/true,
+    TryMaxStreamsFrame(stream_count, /*unidirectional=*/true,
                        /*stream_id_server_initiated=*/true);
-    TryMaxStreamsFrame(stream_id, /*unidirectional=*/true,
+    TryMaxStreamsFrame(stream_count, /*unidirectional=*/true,
                        /*stream_id_server_initiated=*/false);
-    TryMaxStreamsFrame(stream_id, /*unidirectional=*/false,
+    TryMaxStreamsFrame(stream_count, /*unidirectional=*/false,
                        /*stream_id_server_initiated=*/true);
-    TryMaxStreamsFrame(stream_id, /*unidirectional=*/false,
+    TryMaxStreamsFrame(stream_count, /*unidirectional=*/false,
                        /*stream_id_server_initiated=*/false);
   }
 }
@@ -1362,20 +1337,19 @@
 }
 
 TEST_F(QuicIetfFramerTest, StreamsBlockedFrame) {
-  QuicIetfStreamId stream_ids[] = {kStreamId4, kStreamId2, kStreamId1,
-                                   kStreamId0};
+  QuicStreamCount stream_counts[] = {0x3fffffff, 0x3fff, 0x3f, 0x1};
 
-  for (QuicIetfStreamId stream_id : stream_ids) {
-    TryStreamsBlockedFrame(stream_id,
+  for (QuicStreamCount stream_count : stream_counts) {
+    TryStreamsBlockedFrame(stream_count,
                            /*unidirectional=*/false,
                            /*stream_id_server_initiated=*/false);
-    TryStreamsBlockedFrame(stream_id,
+    TryStreamsBlockedFrame(stream_count,
                            /*unidirectional=*/false,
                            /*stream_id_server_initiated=*/true);
-    TryStreamsBlockedFrame(stream_id,
+    TryStreamsBlockedFrame(stream_count,
                            /*unidirectional=*/true,
                            /*stream_id_server_initiated=*/false);
-    TryStreamsBlockedFrame(stream_id,
+    TryStreamsBlockedFrame(stream_count,
                            /*unidirectional=*/true,
                            /*stream_id_server_initiated=*/true);
   }
@@ -1401,7 +1375,7 @@
 
   memset(packet_buffer, 0, sizeof(packet_buffer));
 
-  // Set up the writer and transmit QuicStreamIdBlockedFrame
+  // Set up the writer and transmit a QuicNewConnectionIdFrame
   QuicDataWriter writer(sizeof(packet_buffer), packet_buffer,
                         NETWORK_BYTE_ORDER);
 
@@ -1449,7 +1423,7 @@
 
   memset(packet_buffer, 0, sizeof(packet_buffer));
 
-  // Set up the writer and transmit QuicStreamIdBlockedFrame
+  // Set up the writer and transmit QuicRetireConnectionIdFrame
   QuicDataWriter writer(sizeof(packet_buffer), packet_buffer,
                         NETWORK_BYTE_ORDER);
 
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 91d323b..d780961 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -244,14 +244,14 @@
 
   // Get the QuicStream for this stream. Ignore the STOP_SENDING
   // if the QuicStream pointer is NULL
-  // QUESTION: IS THIS THE RIGHT THING TO DO? (that is, this would happen IFF
-  // there was an entry in the map, but the pointer is null. sounds more like a
-  // deep programming error rather than a simple protocol problem).
+  // QUESTION(fkastenholz): IS THIS THE RIGHT THING TO DO? (that is, this would
+  // happen IFF there was an entry in the map, but the pointer is null. sounds
+  // more like a deep programming error rather than a simple protocol problem).
   QuicStream* stream = it->second.get();
   if (stream == nullptr) {
-    QUIC_DVLOG(1) << ENDPOINT
-                  << "Received STOP_SENDING for NULL QuicStream, stream_id: "
-                  << stream_id << ". Ignoring.";
+    QUIC_BUG << ENDPOINT
+             << "Received STOP_SENDING for NULL QuicStream, stream_id: "
+             << stream_id << ". Ignoring.";
     return true;
   }
 
@@ -732,12 +732,15 @@
   control_frame_manager_.WriteOrBufferWindowUpdate(id, byte_offset);
 }
 
-void QuicSession::SendMaxStreamId(QuicStreamId max_allowed_incoming_id) {
-  control_frame_manager_.WriteOrBufferMaxStreamId(max_allowed_incoming_id);
+void QuicSession::SendMaxStreams(QuicStreamCount stream_count,
+                                 bool unidirectional) {
+  control_frame_manager_.WriteOrBufferMaxStreams(stream_count, unidirectional);
 }
 
-void QuicSession::SendStreamIdBlocked(QuicStreamId max_allowed_outgoing_id) {
-  control_frame_manager_.WriteOrBufferStreamIdBlocked(max_allowed_outgoing_id);
+void QuicSession::SendStreamsBlocked(QuicStreamCount stream_count,
+                                     bool unidirectional) {
+  control_frame_manager_.WriteOrBufferStreamsBlocked(stream_count,
+                                                     unidirectional);
 }
 
 void QuicSession::CloseStream(QuicStreamId stream_id) {
@@ -913,7 +916,14 @@
   }
   QUIC_DVLOG(1) << "Setting max_open_outgoing_streams_ to " << max_streams;
   if (connection_->transport_version() == QUIC_VERSION_99) {
-    v99_streamid_manager_.SetMaxOpenOutgoingStreams(max_streams);
+    // TODO: When transport negotiation knows about bi- and uni- directional
+    // streams, this should be modified to indicate which one to the manager.
+    // Currently, BOTH are set to the same value.
+    // TODO(fkastenholz): AdjustMax is cognizant of the number of static streams
+    // and sets the maximum to be max_streams + number_of_statics. This should
+    // eventually be removed from IETF QUIC. -- Replace the call with
+    // ConfigureMaxOpen...
+    v99_streamid_manager_.AdjustMaxOpenOutgoingStreams(max_streams);
   } else {
     stream_id_manager_.set_max_open_outgoing_streams(max_streams);
   }
@@ -1765,13 +1775,12 @@
   return stream_id_manager_.next_outgoing_stream_id();
 }
 
-bool QuicSession::OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) {
-  return v99_streamid_manager_.OnMaxStreamIdFrame(frame);
+bool QuicSession::OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) {
+  return v99_streamid_manager_.OnMaxStreamsFrame(frame);
 }
 
-bool QuicSession::OnStreamIdBlockedFrame(
-    const QuicStreamIdBlockedFrame& frame) {
-  return v99_streamid_manager_.OnStreamIdBlockedFrame(frame);
+bool QuicSession::OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) {
+  return v99_streamid_manager_.OnStreamsBlockedFrame(frame);
 }
 
 size_t QuicSession::max_open_incoming_bidirectional_streams() const {
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index d620502..54637d4 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -125,8 +125,8 @@
   void OnPathDegrading() override;
   bool AllowSelfAddressChange() const override;
   void OnForwardProgressConfirmed() override;
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override;
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override;
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override;
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override;
   bool OnStopSendingFrame(const QuicStopSendingFrame& frame) override;
 
   // QuicStreamFrameDataProducer
@@ -211,11 +211,11 @@
   // Sends a WINDOW_UPDATE frame.
   virtual void SendWindowUpdate(QuicStreamId id, QuicStreamOffset byte_offset);
 
-  // Send a MAX_STREAM_ID frame.
-  void SendMaxStreamId(QuicStreamId max_allowed_incoming_id);
+  // Send a MAX_STREAMS frame.
+  void SendMaxStreams(QuicStreamCount stream_count, bool unidirectional);
 
-  // Send a STREAM_ID_BLOCKED frame.
-  void SendStreamIdBlocked(QuicStreamId max_allowed_outgoing_id);
+  // Send a STREAMS_BLOCKED frame.
+  void SendStreamsBlocked(QuicStreamCount stream_count, bool unidirectional);
 
   // Create and transmit a STOP_SENDING frame
   virtual void SendStopSending(uint16_t code, QuicStreamId stream_id);
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index a517d47..63235ba 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -393,6 +393,24 @@
            QuicUtils::StreamIdDelta(connection_->transport_version()) * n;
   }
 
+  QuicStreamId StreamCountToId(QuicStreamCount stream_count,
+                               Perspective perspective,
+                               bool bidirectional) {
+    // Calculate and build up stream ID rather than use
+    // GetFirst... because tests that rely on this method
+    // needs to do the stream count where #1 is 0/1/2/3, and not
+    // take into account that stream 0 is special.
+    QuicStreamId id =
+        ((stream_count - 1) * QuicUtils::StreamIdDelta(QUIC_VERSION_99));
+    if (!bidirectional) {
+      id |= 0x2;
+    }
+    if (perspective == Perspective::IS_SERVER) {
+      id |= 0x1;
+    }
+    return id;
+  }
+
   MockQuicConnectionHelper helper_;
   MockAlarmFactory alarm_factory_;
   NiceMock<MockQuicSessionVisitor> session_visitor_;
@@ -1514,8 +1532,10 @@
   }
 
   if (transport_version() == QUIC_VERSION_99) {
-    EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID,
-                                              "Stream id 24 above 20", _));
+    EXPECT_CALL(
+        *connection_,
+        CloseConnection(QUIC_INVALID_STREAM_ID,
+                        "Stream id 24 would exceed stream count limit 6", _));
   } else {
     EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
     EXPECT_CALL(*connection_,
@@ -1601,7 +1621,7 @@
   // it) does not count against the open quota (because it is closed from the
   // protocol point of view).
   if (transport_version() == QUIC_VERSION_99) {
-    // On v99, we will expect to see a MAX_STREAM_ID go out when there are not
+    // On v99, we will expect to see a MAX_STREAMS go out when there are not
     // enough streams to create the next one.
     EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
   } else {
@@ -2179,19 +2199,24 @@
     // Applicable only to V99
     return;
   }
-  QuicStreamId bidirectional_stream_id =
+  QuicStreamId bidirectional_stream_id = StreamCountToId(
       QuicSessionPeer::v99_streamid_manager(&session_)
-          ->advertised_max_allowed_incoming_bidirectional_stream_id() -
-      kV99StreamIdIncrement;
+              ->advertised_max_allowed_incoming_bidirectional_streams() -
+          1,
+      Perspective::IS_CLIENT,
+      /*bidirectional=*/true);
+
   QuicStreamFrame bidirectional_stream_frame(bidirectional_stream_id, false, 0,
                                              "Random String");
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
   session_.OnStreamFrame(bidirectional_stream_frame);
 
-  QuicStreamId unidirectional_stream_id =
+  QuicStreamId unidirectional_stream_id = StreamCountToId(
       QuicSessionPeer::v99_streamid_manager(&session_)
-          ->advertised_max_allowed_incoming_unidirectional_stream_id() -
-      kV99StreamIdIncrement;
+              ->advertised_max_allowed_incoming_unidirectional_streams() -
+          1,
+      Perspective::IS_CLIENT,
+      /*bidirectional=*/false);
   QuicStreamFrame unidirectional_stream_frame(unidirectional_stream_id, false,
                                               0, "Random String");
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
@@ -2204,17 +2229,19 @@
     // Applicable only to V99
     return;
   }
-  QuicStreamId bidirectional_stream_id =
+  QuicStreamId bidirectional_stream_id = StreamCountToId(
       QuicSessionPeer::v99_streamid_manager(&session_)
-          ->advertised_max_allowed_incoming_bidirectional_stream_id();
+          ->advertised_max_allowed_incoming_bidirectional_streams(),
+      Perspective::IS_CLIENT, /*bidirectional=*/true);
   QuicStreamFrame bidirectional_stream_frame(bidirectional_stream_id, false, 0,
                                              "Random String");
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
   session_.OnStreamFrame(bidirectional_stream_frame);
 
-  QuicStreamId unidirectional_stream_id =
+  QuicStreamId unidirectional_stream_id = StreamCountToId(
       QuicSessionPeer::v99_streamid_manager(&session_)
-          ->advertised_max_allowed_incoming_unidirectional_stream_id();
+          ->advertised_max_allowed_incoming_unidirectional_streams(),
+      Perspective::IS_CLIENT, /*bidirectional=*/false);
   QuicStreamFrame unidirectional_stream_frame(unidirectional_stream_id, false,
                                               0, "Random String");
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
@@ -2227,24 +2254,30 @@
     // Applicable only to V99
     return;
   }
-  QuicStreamId bidirectional_stream_id =
+  QuicStreamId bidirectional_stream_id = StreamCountToId(
       QuicSessionPeer::v99_streamid_manager(&session_)
-          ->advertised_max_allowed_incoming_bidirectional_stream_id() +
-      kV99StreamIdIncrement;
+              ->advertised_max_allowed_incoming_bidirectional_streams() +
+          1,
+      Perspective::IS_CLIENT, /*bidirectional=*/true);
   QuicStreamFrame bidirectional_stream_frame(bidirectional_stream_id, false, 0,
                                              "Random String");
-  EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID,
-                                            "Stream id 404 above 400", _));
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_INVALID_STREAM_ID,
+                      "Stream id 404 would exceed stream count limit 101", _));
   session_.OnStreamFrame(bidirectional_stream_frame);
 
-  QuicStreamId unidirectional_stream_id =
+  QuicStreamId unidirectional_stream_id = StreamCountToId(
       QuicSessionPeer::v99_streamid_manager(&session_)
-          ->advertised_max_allowed_incoming_unidirectional_stream_id() +
-      kV99StreamIdIncrement;
+              ->advertised_max_allowed_incoming_unidirectional_streams() +
+          1,
+      Perspective::IS_CLIENT, /*bidirectional=*/false);
   QuicStreamFrame unidirectional_stream_frame(unidirectional_stream_id, false,
                                               0, "Random String");
-  EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID,
-                                            "Stream id 402 above 398", _));
+  EXPECT_CALL(
+      *connection_,
+      CloseConnection(QUIC_INVALID_STREAM_ID,
+                      "Stream id 402 would exceed stream count limit 100", _));
   session_.OnStreamFrame(unidirectional_stream_frame);
 }
 
diff --git a/quic/core/quic_stream_id_manager.cc b/quic/core/quic_stream_id_manager.cc
index fb6e14b..f68401a 100644
--- a/quic/core/quic_stream_id_manager.cc
+++ b/quic/core/quic_stream_id_manager.cc
@@ -22,312 +22,358 @@
 
 QuicStreamIdManager::QuicStreamIdManager(
     QuicSession* session,
-    QuicStreamId next_outgoing_stream_id,
-    QuicStreamId largest_peer_created_stream_id,
-    QuicStreamId first_incoming_dynamic_stream_id,
-    size_t max_allowed_outgoing_streams,
-    size_t max_allowed_incoming_streams)
+    bool unidirectional,
+    QuicStreamCount max_allowed_outgoing_streams,
+    QuicStreamCount max_allowed_incoming_streams)
     : session_(session),
-      next_outgoing_stream_id_(next_outgoing_stream_id),
-      largest_peer_created_stream_id_(largest_peer_created_stream_id),
-      max_allowed_outgoing_stream_id_(0),
-      actual_max_allowed_incoming_stream_id_(0),
-      advertised_max_allowed_incoming_stream_id_(0),
-      max_stream_id_window_(max_allowed_incoming_streams /
-                            kMaxStreamIdWindowDivisor),
-      max_allowed_incoming_streams_(max_allowed_incoming_streams),
-      first_incoming_dynamic_stream_id_(first_incoming_dynamic_stream_id),
-      first_outgoing_dynamic_stream_id_(next_outgoing_stream_id) {
-  available_incoming_streams_ = max_allowed_incoming_streams_;
-  SetMaxOpenOutgoingStreams(max_allowed_outgoing_streams);
-  SetMaxOpenIncomingStreams(max_allowed_incoming_streams);
+      unidirectional_(unidirectional),
+      outgoing_max_streams_(max_allowed_outgoing_streams),
+      next_outgoing_stream_id_(GetFirstOutgoingStreamId()),
+      outgoing_stream_count_(0),
+      outgoing_static_stream_count_(0),
+      using_default_max_streams_(true),
+      incoming_actual_max_streams_(max_allowed_incoming_streams),
+      // Advertised max starts at actual because it's communicated in the
+      // handshake.
+      incoming_advertised_max_streams_(max_allowed_incoming_streams),
+      incoming_initial_max_open_streams_(max_allowed_incoming_streams),
+      incoming_static_stream_count_(0),
+      incoming_stream_count_(0),
+      largest_peer_created_stream_id_(
+          QuicUtils::GetInvalidStreamId(transport_version())),
+      max_streams_window_(0) {
+  CalculateIncomingMaxStreamsWindow();
 }
 
 QuicStreamIdManager::~QuicStreamIdManager() {
-  QUIC_LOG_IF(WARNING,
-              session_->num_locally_closed_incoming_streams_highest_offset() >
-                  max_allowed_incoming_streams_)
-      << "Surprisingly high number of locally closed peer initiated streams"
-         "still waiting for final byte offset: "
-      << session_->num_locally_closed_incoming_streams_highest_offset();
-  QUIC_LOG_IF(WARNING,
-              session_->GetNumLocallyClosedOutgoingStreamsHighestOffset() >
-                  max_allowed_outgoing_streams_)
-      << "Surprisingly high number of locally closed self initiated streams"
-         "still waiting for final byte offset: "
-      << session_->GetNumLocallyClosedOutgoingStreamsHighestOffset();
 }
 
-bool QuicStreamIdManager::OnMaxStreamIdFrame(
-    const QuicMaxStreamIdFrame& frame) {
-  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(frame.max_stream_id),
-            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
-  // Need to determine whether the stream id matches our client/server
-  // perspective or not. If not, it's an error. If so, update appropriate
-  // maxima.
-  QUIC_CODE_COUNT_N(max_stream_id_received, 2, 2);
-  // TODO(fkastenholz): this test needs to be broader to handle uni- and bi-
-  // directional stream ids when that functionality is supported.
-  if (IsIncomingStream(frame.max_stream_id)) {
-    // TODO(fkastenholz): This, and following, closeConnection may
-    // need modification when proper support for IETF CONNECTION
-    // CLOSE is done.
-    QUIC_CODE_COUNT(max_stream_id_bad_direction);
-    session_->connection()->CloseConnection(
-        QUIC_MAX_STREAM_ID_ERROR,
-        "Received max stream ID with wrong initiator bit setting",
-        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+bool QuicStreamIdManager::OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) {
+  // 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 (!ConfigureMaxOpenOutgoingStreams(frame.stream_count)) {
     return false;
   }
-
-  // If a MAX_STREAM_ID advertises a stream ID that is smaller than previously
-  // advertised, it is to be ignored.
-  if (frame.max_stream_id < max_allowed_outgoing_stream_id_) {
-    QUIC_CODE_COUNT(max_stream_id_ignored);
-    return true;
+  // 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();
   }
-  max_allowed_outgoing_stream_id_ = frame.max_stream_id;
-
-  // Outgoing stream limit has increased, tell the applications
-  session_->OnCanCreateNewOutgoingStream();
-
   return true;
 }
 
-bool QuicStreamIdManager::OnStreamIdBlockedFrame(
-    const QuicStreamIdBlockedFrame& frame) {
-  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(frame.stream_id),
-            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
-  QUIC_CODE_COUNT_N(stream_id_blocked_received, 2, 2);
-  QuicStreamId id = frame.stream_id;
-  if (!IsIncomingStream(frame.stream_id)) {
-    // Client/server mismatch, close the connection
-    // TODO(fkastenholz): revise when proper IETF Connection Close support is
-    // done.
-    QUIC_CODE_COUNT(stream_id_blocked_bad_direction);
-    session_->connection()->CloseConnection(
-        QUIC_STREAM_ID_BLOCKED_ERROR,
-        "Invalid stream ID directionality specified",
-        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
-    return false;
-  }
+// The peer sends a streams blocked frame when it can not open any more
+// streams because it has runs into the limit.
+bool QuicStreamIdManager::OnStreamsBlockedFrame(
+    const QuicStreamsBlockedFrame& frame) {
+  // Ensure that the frame has the correct directionality.
+  DCHECK_EQ(frame.unidirectional, unidirectional_);
+  QUIC_CODE_COUNT_N(quic_streams_blocked_received, 2, 2);
 
-  if (id > advertised_max_allowed_incoming_stream_id_) {
+  if (frame.stream_count > incoming_advertised_max_streams_) {
     // Peer thinks it can send more streams that we've told it.
     // This is a protocol error.
     // TODO(fkastenholz): revise when proper IETF Connection Close support is
     // done.
-    QUIC_CODE_COUNT(stream_id_blocked_id_too_big);
+    QUIC_CODE_COUNT(quic_streams_blocked_too_big);
     session_->connection()->CloseConnection(
-        QUIC_STREAM_ID_BLOCKED_ERROR, "Invalid stream ID specified",
+        QUIC_STREAMS_BLOCKED_ERROR, "Invalid stream count specified",
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
     return false;
   }
-  if (id < actual_max_allowed_incoming_stream_id_) {
-    // Peer thinks it's blocked on an ID that is less than our current
-    // max. Inform the peer of the correct stream ID.
-    SendMaxStreamIdFrame();
-    return true;
+  if (frame.stream_count < incoming_actual_max_streams_) {
+    // Peer thinks it's blocked on a stream count that is less than our current
+    // max. Inform the peer of the correct stream count. Sending a MAX_STREAMS
+    // frame in this case is not controlled by the window.
+    SendMaxStreamsFrame();
   }
-  // The peer's notion of the maximum ID is correct,
-  // there is nothing to do.
-  QUIC_CODE_COUNT(stream_id_blocked_id_correct);
+  QUIC_CODE_COUNT(quic_streams_blocked_id_correct);
   return true;
 }
 
-// TODO(fkastenholz): Many changes will be needed here:
-//  -- Use IETF QUIC server/client-initiation sense
-//  -- Support both BIDI and UNI streams.
-//  -- can not change the max number of streams after config negotiation has
-//     been done.
-void QuicStreamIdManager::SetMaxOpenOutgoingStreams(size_t max_streams) {
-  max_allowed_outgoing_streams_ = max_streams;
-  max_allowed_outgoing_stream_id_ =
-      next_outgoing_stream_id_ + (max_streams - 1) * kV99StreamIdIncrement;
-}
-
-// TODO(fkastenholz): Many changes will be needed here:
-//  -- can not change the max number of streams after config negotiation has
-//     been done.
-//  -- Currently uses the Google Client/server-initiation sense, needs to
-//     be IETF.
-//  -- Support both BIDI and UNI streams.
-//  -- Convert calculation of the maximum ID from Google-QUIC semantics to IETF
-//     QUIC semantics.
-void QuicStreamIdManager::SetMaxOpenIncomingStreams(size_t max_streams) {
-  max_allowed_incoming_streams_ = max_streams;
-  // The peer should always believe that it has the negotiated
-  // number of stream ids available for use.
-  available_incoming_streams_ = max_allowed_incoming_streams_;
-
-  // the window is a fraction of the peer's notion of its stream-id space.
-  max_stream_id_window_ =
-      available_incoming_streams_ / kMaxStreamIdWindowDivisor;
-  if (max_stream_id_window_ == 0) {
-    max_stream_id_window_ = 1;
+// Used when configuration has been done and we have an initial
+// maximum stream count from the peer.
+bool QuicStreamIdManager::ConfigureMaxOpenOutgoingStreams(
+    size_t max_open_streams) {
+  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
+    // outgoing_max_streams_ is currently an estimate. The MAX_STREAMS frame or
+    // transport negotiation is authoritative and can reduce
+    // outgoing_max_streams_ -- so long as outgoing_max_streams_ is not set to
+    // be less than the number of existing outgoing streams. If that happens,
+    // close the connection.
+    if (max_open_streams < outgoing_stream_count_) {
+      session_->connection()->CloseConnection(
+          QUIC_MAX_STREAMS_ERROR,
+          "Stream limit less than existing stream count",
+          ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+      return false;
+    }
+    using_default_max_streams_ = false;
+  } else if (max_open_streams <= outgoing_max_streams_) {
+    // Is not the 1st MAX_STREAMS or negotiation.
+    // Only update the stream count if it would increase the limit.
+    // If it decreases the limit, or doesn't change it, then do not update.
+    // Note that this handles the case of receiving a count of 0 in the frame
+    return true;
   }
 
-  actual_max_allowed_incoming_stream_id_ =
-      first_incoming_dynamic_stream_id_ +
-      (max_allowed_incoming_streams_ - 1) * kV99StreamIdIncrement;
-  // To start, we can assume advertised and actual are the same.
-  advertised_max_allowed_incoming_stream_id_ =
-      actual_max_allowed_incoming_stream_id_;
+  // This implementation only supports 32 bit Stream IDs, so limit max streams
+  // if it would exceed the max 32 bits can express.
+  outgoing_max_streams_ = std::min(
+      static_cast<QuicStreamCount>(max_open_streams),
+      QuicUtils::GetMaxStreamCount(unidirectional_, session_->perspective()));
+
+  return true;
 }
 
-void QuicStreamIdManager::MaybeSendMaxStreamIdFrame() {
-  if (available_incoming_streams_ > max_stream_id_window_) {
+void QuicStreamIdManager::SetMaxOpenOutgoingStreams(size_t max_open_streams) {
+  QUIC_BUG_IF(!using_default_max_streams_);
+  AdjustMaxOpenOutgoingStreams(max_open_streams);
+}
+
+// Adjust the outgoing stream limit - max_open_streams is the limit, not
+// including static streams. If the new stream limit wraps, will peg
+// the limit at the implementation max.
+// TODO(fkastenholz): AdjustMax is cognizant of the number of static streams and
+// sets the maximum to be max_streams + number_of_statics. This should
+// eventually be removed from IETF QUIC.
+void QuicStreamIdManager::AdjustMaxOpenOutgoingStreams(
+    size_t max_open_streams) {
+  if ((outgoing_static_stream_count_ + max_open_streams) < max_open_streams) {
+    // New limit causes us to wrap, set limit to be the implementation maximum.
+    ConfigureMaxOpenOutgoingStreams(
+        QuicUtils::GetMaxStreamCount(unidirectional_, perspective()));
+    return;
+  }
+  // Does not wrap, set limit to what is requested.
+  ConfigureMaxOpenOutgoingStreams(outgoing_static_stream_count_ +
+                                  max_open_streams);
+}
+
+void QuicStreamIdManager::SetMaxOpenIncomingStreams(size_t max_open_streams) {
+  QuicStreamCount implementation_max =
+      QuicUtils::GetMaxStreamCount(unidirectional_, perspective());
+  QuicStreamCount new_max =
+      std::min(implementation_max,
+               static_cast<QuicStreamCount>(max_open_streams +
+                                            incoming_static_stream_count_));
+  if (new_max < max_open_streams) {
+    // wrapped around ...
+    new_max = implementation_max;
+  }
+  if (new_max < incoming_stream_count_) {
+    session_->connection()->CloseConnection(
+        QUIC_MAX_STREAMS_ERROR, "Stream limit less than existing stream count",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  incoming_actual_max_streams_ = new_max;
+  incoming_advertised_max_streams_ = new_max;
+  incoming_initial_max_open_streams_ =
+      std::min(max_open_streams, static_cast<size_t>(implementation_max));
+  CalculateIncomingMaxStreamsWindow();
+}
+
+void QuicStreamIdManager::MaybeSendMaxStreamsFrame() {
+  if ((incoming_advertised_max_streams_ - incoming_stream_count_) >
+      max_streams_window_) {
     // window too large, no advertisement
     return;
   }
-  // Calculate the number of streams that the peer will believe
-  // it has. The "/kV99StreamIdIncrement" converts from stream-id-
-  // values to number-of-stream-ids.
-  available_incoming_streams_ += (actual_max_allowed_incoming_stream_id_ -
-                                  advertised_max_allowed_incoming_stream_id_) /
-                                 kV99StreamIdIncrement;
-  SendMaxStreamIdFrame();
+  SendMaxStreamsFrame();
 }
 
-void QuicStreamIdManager::SendMaxStreamIdFrame() {
-  advertised_max_allowed_incoming_stream_id_ =
-      actual_max_allowed_incoming_stream_id_;
-  // And Advertise it.
-  session_->SendMaxStreamId(advertised_max_allowed_incoming_stream_id_);
+void QuicStreamIdManager::SendMaxStreamsFrame() {
+  incoming_advertised_max_streams_ = incoming_actual_max_streams_;
+  session_->SendMaxStreams(incoming_advertised_max_streams_, unidirectional_);
 }
 
 void QuicStreamIdManager::OnStreamClosed(QuicStreamId stream_id) {
-  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(stream_id),
-            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
+  DCHECK_NE(QuicUtils::IsBidirectionalStreamId(stream_id), unidirectional_);
   if (!IsIncomingStream(stream_id)) {
-    // Nothing to do for outbound streams with respect to the
-    // stream ID space management.
+    // Nothing to do for outgoing streams.
     return;
   }
-  // If the stream is inbound, we can increase the stream ID limit and maybe
-  // advertise the new limit to the peer.
-  if (actual_max_allowed_incoming_stream_id_ >=
-      (kMaxQuicStreamId - kV99StreamIdIncrement)) {
+  // If the stream is inbound, we can increase the actual stream limit and maybe
+  // advertise the new limit to the peer.  Have to check to make sure that we do
+  // not exceed the maximum.
+  if (incoming_actual_max_streams_ ==
+      QuicUtils::GetMaxStreamCount(unidirectional_, perspective())) {
     // Reached the maximum stream id value that the implementation
     // supports. Nothing can be done here.
     return;
   }
-  actual_max_allowed_incoming_stream_id_ += kV99StreamIdIncrement;
-  MaybeSendMaxStreamIdFrame();
+  // One stream closed ... another can be opened.
+  incoming_actual_max_streams_++;
+  MaybeSendMaxStreamsFrame();
 }
 
 QuicStreamId QuicStreamIdManager::GetNextOutgoingStreamId() {
-  QUIC_BUG_IF(next_outgoing_stream_id_ > max_allowed_outgoing_stream_id_)
-      << "Attempt allocate a new outgoing stream ID would exceed the limit";
+  // TODO(fkastenholz): Should we close the connection?
+  QUIC_BUG_IF(outgoing_stream_count_ >= outgoing_max_streams_)
+      << "Attempt to allocate a new outgoing stream that would exceed the "
+         "limit";
   QuicStreamId id = next_outgoing_stream_id_;
-  next_outgoing_stream_id_ += kV99StreamIdIncrement;
+  next_outgoing_stream_id_ += QuicUtils::StreamIdDelta(transport_version());
+  outgoing_stream_count_++;
   return id;
 }
 
 bool QuicStreamIdManager::CanOpenNextOutgoingStream() {
-  DCHECK_EQ(QUIC_VERSION_99, session_->connection()->transport_version());
-  if (next_outgoing_stream_id_ > max_allowed_outgoing_stream_id_) {
-    // Next stream ID would exceed the limit, need to inform the peer.
-    session_->SendStreamIdBlocked(max_allowed_outgoing_stream_id_);
-    QUIC_CODE_COUNT(reached_outgoing_stream_id_limit);
-    return false;
+  DCHECK_EQ(QUIC_VERSION_99, transport_version());
+  if (outgoing_stream_count_ < outgoing_max_streams_) {
+    return true;
   }
-  return true;
+  // Next stream ID would exceed the limit, need to inform the peer.
+  session_->SendStreamsBlocked(outgoing_max_streams_, unidirectional_);
+  QUIC_CODE_COUNT(quic_reached_outgoing_stream_id_limit);
+  return false;
 }
 
-void QuicStreamIdManager::RegisterStaticStream(QuicStreamId stream_id) {
-  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(stream_id),
-            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
-  QuicStreamId first_dynamic_stream_id = stream_id + kV99StreamIdIncrement;
-
-  if (IsIncomingStream(first_dynamic_stream_id)) {
+bool QuicStreamIdManager::RegisterStaticStream(QuicStreamId stream_id) {
+  DCHECK_NE(QuicUtils::IsBidirectionalStreamId(stream_id), unidirectional_);
+  if (IsIncomingStream(stream_id)) {
     // This code is predicated on static stream ids being allocated densely, in
     // order, and starting with the first stream allowed. QUIC_BUG if this is
     // not so.
-    QUIC_BUG_IF(stream_id > first_incoming_dynamic_stream_id_)
-        << "Error in incoming static stream allocation, expected to allocate "
-        << first_incoming_dynamic_stream_id_ << " got " << stream_id;
-
     // This is a stream id for a stream that is started by the peer, deal with
     // the incoming stream ids. Increase the floor and adjust everything
     // accordingly.
-    if (stream_id == first_incoming_dynamic_stream_id_) {
-      actual_max_allowed_incoming_stream_id_ += kV99StreamIdIncrement;
-      first_incoming_dynamic_stream_id_ = first_dynamic_stream_id;
+
+    QUIC_BUG_IF(incoming_actual_max_streams_ >
+                QuicUtils::GetMaxStreamCount(unidirectional_, perspective()));
+
+    // If we have reached the limit on stream creation, do not create
+    // the static stream; return false.
+    if (incoming_stream_count_ >=
+        QuicUtils::GetMaxStreamCount(unidirectional_, perspective())) {
+      return false;
     }
-    return;
+
+    if (incoming_actual_max_streams_ <
+        QuicUtils::GetMaxStreamCount(unidirectional_, perspective())) {
+      incoming_actual_max_streams_++;
+    }
+    if (incoming_advertised_max_streams_ <
+        QuicUtils::GetMaxStreamCount(unidirectional_, perspective())) {
+      incoming_advertised_max_streams_++;
+    }
+    incoming_stream_count_++;
+    incoming_static_stream_count_++;
+    return true;
   }
 
-  // This code is predicated on static stream ids being allocated densely, in
-  // order, and starting with the first stream allowed. QUIC_BUG if this is
-  // not so.
-  QUIC_BUG_IF(stream_id > first_outgoing_dynamic_stream_id_)
-      << "Error in outgoing static stream allocation, expected to allocate "
-      << first_outgoing_dynamic_stream_id_ << " got " << stream_id;
-  // This is a stream id for a stream that is started by this node; deal with
-  // the outgoing stream ids. Increase the floor and adjust everything
-  // accordingly.
-  if (stream_id == first_outgoing_dynamic_stream_id_) {
-    max_allowed_outgoing_stream_id_ += kV99StreamIdIncrement;
-    first_outgoing_dynamic_stream_id_ = first_dynamic_stream_id;
+  QUIC_BUG_IF(!using_default_max_streams_)
+      << "Attempted to allocate static stream (id " << stream_id
+      << ") after receiving a MAX_STREAMS frame";
+
+  // If we have reached the limit on stream creation, do not create
+  // the static stream; return false.
+  if (outgoing_max_streams_ >=
+      QuicUtils::GetMaxStreamCount(unidirectional_, perspective())) {
+    return false;
   }
+
+  // This is a stream id for a stream that is started by this node
+  if (perspective() == Perspective::IS_CLIENT &&
+      stream_id == QuicUtils::GetCryptoStreamId(transport_version())) {
+    // TODO(fkastenholz): When crypto is moved into the CRYPTO_STREAM
+    // and streamID 0 is no longer special, this needs to be removed.
+    // Stream-id-0 seems not be allocated via get-next-stream-id,
+    // which would increment outgoing_stream_count_, so increment
+    // the count here to account for it.
+    // Do not need to update next_outgoing_stream_id_ because it is
+    // initiated to 4 (that is, it skips the crypto stream ID).
+    if (outgoing_stream_count_ >=
+        QuicUtils::GetMaxStreamCount(unidirectional_, perspective())) {
+      // Already at the implementation limit, return false...
+      return false;
+    }
+    outgoing_stream_count_++;
+  }
+
+  // Increase the outgoing_max_streams_ limit to reflect the semantic that
+  // outgoing_max_streams_ was inialized to a "maximum request/response" count
+  // and only becomes a maximum stream count when we receive the first
+  // MAX_STREAMS.
+  outgoing_max_streams_++;
+  outgoing_static_stream_count_++;
+  return true;
 }
 
+// Stream_id is the id of a new incoming stream. Check if it can be
+// created (doesn't violate limits, etc).
 bool QuicStreamIdManager::MaybeIncreaseLargestPeerStreamId(
     const QuicStreamId stream_id) {
-  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(stream_id),
-            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
+  DCHECK_NE(QuicUtils::IsBidirectionalStreamId(stream_id), unidirectional_);
+
   available_streams_.erase(stream_id);
 
   if (largest_peer_created_stream_id_ !=
-          QuicUtils::GetInvalidStreamId(
-              session_->connection()->transport_version()) &&
+          QuicUtils::GetInvalidStreamId(transport_version()) &&
       stream_id <= largest_peer_created_stream_id_) {
     return true;
   }
 
-  if (stream_id > actual_max_allowed_incoming_stream_id_) {
-    // Desired stream ID is larger than the limit, do not increase.
+  QuicStreamCount stream_count_increment;
+  if (largest_peer_created_stream_id_ !=
+      QuicUtils::GetInvalidStreamId(transport_version())) {
+    stream_count_increment = (stream_id - largest_peer_created_stream_id_) /
+                             QuicUtils::StreamIdDelta(transport_version());
+  } else {
+    // Largest_peer_created_stream_id is the invalid ID,
+    // which means that the peer has not created any stream IDs.
+    // The "+1" is because the first stream ID has not yet
+    // been used. For example, if the FirstIncoming ID is 1
+    // and stream_id is 1, then we want the increment to be 1.
+    stream_count_increment = ((stream_id - GetFirstIncomingStreamId()) /
+                              QuicUtils::StreamIdDelta(transport_version())) +
+                             1;
+  }
+
+  // If already at, or over, the limit, close the connection/etc.
+  if (((incoming_stream_count_ + stream_count_increment) >
+       incoming_advertised_max_streams_) ||
+      ((incoming_stream_count_ + stream_count_increment) <
+       incoming_stream_count_)) {
+    // This stream would exceed the limit. do not increase.
     QUIC_DLOG(INFO) << ENDPOINT
                     << "Failed to create a new incoming stream with id:"
-                    << stream_id << ".  Maximum allowed stream id is "
-                    << actual_max_allowed_incoming_stream_id_ << ".";
+                    << stream_id << ", reaching MAX_STREAMS limit: "
+                    << incoming_advertised_max_streams_ << ".";
     session_->connection()->CloseConnection(
         QUIC_INVALID_STREAM_ID,
-        QuicStrCat("Stream id ", stream_id, " above ",
-                   actual_max_allowed_incoming_stream_id_),
+        QuicStrCat("Stream id ", stream_id, " would exceed stream count limit ",
+                   incoming_advertised_max_streams_),
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
     return false;
   }
 
-  available_incoming_streams_--;
-
-  QuicStreamId id = largest_peer_created_stream_id_ + kV99StreamIdIncrement;
-  if (largest_peer_created_stream_id_ ==
-      QuicUtils::GetInvalidStreamId(
-          session_->connection()->transport_version())) {
-    // Adjust id based on perspective and whether stream_id is bidirectional or
-    // unidirectional.
-    if (QuicUtils::IsBidirectionalStreamId(stream_id)) {
-      // This should only happen on client side because server bidirectional
-      // stream ID manager's largest_peer_created_stream_id_ is initialized to
-      // the crypto stream ID.
-      DCHECK_EQ(Perspective::IS_CLIENT, session_->perspective());
-      id = 1;
-    } else {
-      id = session_->perspective() == Perspective::IS_SERVER ? 2 : 3;
-    }
+  QuicStreamId id = GetFirstIncomingStreamId();
+  if (largest_peer_created_stream_id_ !=
+      QuicUtils::GetInvalidStreamId(transport_version())) {
+    id = largest_peer_created_stream_id_ +
+         QuicUtils::StreamIdDelta(transport_version());
   }
-  for (; id < stream_id; id += kV99StreamIdIncrement) {
+
+  for (; id < stream_id; id += QuicUtils::StreamIdDelta(transport_version())) {
     available_streams_.insert(id);
   }
+  incoming_stream_count_ += stream_count_increment;
   largest_peer_created_stream_id_ = stream_id;
   return true;
 }
 
 bool QuicStreamIdManager::IsAvailableStream(QuicStreamId id) const {
-  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(id),
-            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
+  DCHECK_NE(QuicUtils::IsBidirectionalStreamId(id), unidirectional_);
   if (!IsIncomingStream(id)) {
     // Stream IDs under next_ougoing_stream_id_ are either open or previously
     // open but now closed.
@@ -335,17 +381,57 @@
   }
   // For peer created streams, we also need to consider available streams.
   return largest_peer_created_stream_id_ ==
-             QuicUtils::GetInvalidStreamId(
-                 session_->connection()->transport_version()) ||
+             QuicUtils::GetInvalidStreamId(transport_version()) ||
          id > largest_peer_created_stream_id_ ||
          QuicContainsKey(available_streams_, id);
 }
 
 bool QuicStreamIdManager::IsIncomingStream(QuicStreamId id) const {
-  DCHECK_EQ(QuicUtils::IsBidirectionalStreamId(id),
-            QuicUtils::IsBidirectionalStreamId(next_outgoing_stream_id_));
-  return id % kV99StreamIdIncrement !=
-         next_outgoing_stream_id_ % kV99StreamIdIncrement;
+  DCHECK_NE(QuicUtils::IsBidirectionalStreamId(id), unidirectional_);
+  // The 0x1 bit in the stream id indicates whether the stream id is
+  // server- or client- initiated. Next_OUTGOING_stream_id_ has that bit
+  // set based on whether this node is a server or client. Thus, if the stream
+  // id in question has the 0x1 bit set opposite of next_OUTGOING_stream_id_,
+  // then that stream id is incoming -- it is for streams initiated by the peer.
+  return (id & 0x1) != (next_outgoing_stream_id_ & 0x1);
+}
+
+QuicStreamId QuicStreamIdManager::GetFirstOutgoingStreamId() const {
+  return (unidirectional_) ? QuicUtils::GetFirstUnidirectionalStreamId(
+                                 transport_version(), perspective())
+                           : QuicUtils::GetFirstBidirectionalStreamId(
+                                 transport_version(), perspective());
+}
+
+QuicStreamId QuicStreamIdManager::GetFirstIncomingStreamId() const {
+  return (unidirectional_) ? QuicUtils::GetFirstUnidirectionalStreamId(
+                                 transport_version(), peer_perspective())
+                           : QuicUtils::GetFirstBidirectionalStreamId(
+                                 transport_version(), peer_perspective());
+}
+
+Perspective QuicStreamIdManager::perspective() const {
+  return session_->perspective();
+}
+
+Perspective QuicStreamIdManager::peer_perspective() const {
+  return (perspective() == Perspective::IS_SERVER) ? Perspective::IS_CLIENT
+                                                   : Perspective::IS_SERVER;
+}
+
+QuicTransportVersion QuicStreamIdManager::transport_version() const {
+  return session_->connection()->transport_version();
+}
+
+size_t QuicStreamIdManager::available_incoming_streams() {
+  return incoming_advertised_max_streams_ - incoming_stream_count_;
+}
+
+void QuicStreamIdManager::CalculateIncomingMaxStreamsWindow() {
+  max_streams_window_ = incoming_actual_max_streams_ / kMaxStreamsWindowDivisor;
+  if (max_streams_window_ == 0) {
+    max_streams_window_ = 1;
+  }
 }
 
 }  // namespace quic
diff --git a/quic/core/quic_stream_id_manager.h b/quic/core/quic_stream_id_manager.h
index 6fd75fb..42c7d1a 100644
--- a/quic/core/quic_stream_id_manager.h
+++ b/quic/core/quic_stream_id_manager.h
@@ -6,6 +6,7 @@
 
 #include "net/third_party/quiche/src/quic/core/frames/quic_frame.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
 
@@ -23,21 +24,17 @@
 const QuicStreamId kV99StreamIdIncrement = 4;
 
 // This constant controls the size of the window when deciding whether
-// to generate a MAX STREAM ID frame or not. See the discussion of the
+// to generate a MAX_STREAMS frame or not. See the discussion of the
 // window, below, for more details.
-const int kMaxStreamIdWindowDivisor = 2;
+const int kMaxStreamsWindowDivisor = 2;
 
 // This class manages the stream ids for Version 99/IETF QUIC.
-// TODO(fkastenholz): Expand to support bi- and uni-directional stream ids
-// TODO(fkastenholz): Roll in pre-version-99 management
 class QUIC_EXPORT_PRIVATE QuicStreamIdManager {
  public:
   QuicStreamIdManager(QuicSession* session,
-                      QuicStreamId next_outgoing_stream_id,
-                      QuicStreamId largest_peer_created_stream_id,
-                      QuicStreamId first_incoming_dynamic_stream_id,
-                      size_t max_allowed_outgoing_streams,
-                      size_t max_allowed_incoming_streams);
+                      bool unidirectional,
+                      QuicStreamCount max_allowed_outgoing_streams,
+                      QuicStreamCount max_allowed_incoming_streams);
 
   ~QuicStreamIdManager();
 
@@ -45,68 +42,85 @@
   // of the stream ID manager.
   std::string DebugString() const {
     return QuicStrCat(
-        " { max_allowed_outgoing_stream_id: ", max_allowed_outgoing_stream_id_,
-        ", actual_max_allowed_incoming_stream_id_: ",
-        actual_max_allowed_incoming_stream_id_,
-        ", advertised_max_allowed_incoming_stream_id_: ",
-        advertised_max_allowed_incoming_stream_id_,
-        ", max_stream_id_window_: ", max_stream_id_window_,
-        ", max_allowed_outgoing_streams_: ", max_allowed_outgoing_streams_,
-        ", max_allowed_incoming_streams_: ", max_allowed_incoming_streams_,
-        ", available_incoming_streams_: ", available_incoming_streams_,
-        ", first_incoming_dynamic_stream_id_: ",
-        first_incoming_dynamic_stream_id_,
-        ", first_outgoing_dynamic_stream_id_: ",
-        first_outgoing_dynamic_stream_id_, " }");
+        " { unidirectional_: ", unidirectional_,
+        ", perspective: ", perspective(),
+        ", outgoing_max_streams_: ", outgoing_max_streams_,
+        ", next_outgoing_stream_id_: ", next_outgoing_stream_id_,
+        ", outgoing_stream_count_: ", outgoing_stream_count_,
+        ", outgoing_static_stream_count_: ", outgoing_static_stream_count_,
+        ", using_default_max_streams_: ", using_default_max_streams_,
+        ", incoming_actual_max_streams_: ", incoming_actual_max_streams_,
+        ", incoming_advertised_max_streams_: ",
+        incoming_advertised_max_streams_,
+        ", incoming_static_stream_count_: ", incoming_static_stream_count_,
+        ", incoming_stream_count_: ", incoming_stream_count_,
+        ", available_streams_.size(): ", available_streams_.size(),
+        ", largest_peer_created_stream_id_: ", largest_peer_created_stream_id_,
+        ", max_streams_window_: ", max_streams_window_, " }");
   }
 
-  // Processes the MAX STREAM ID frame, invoked from
-  // QuicSession::OnMaxStreamIdFrame. It has the same semantics as the
+  // Processes the MAX_STREAMS frame, invoked from
+  // QuicSession::OnMaxStreamsFrame. It has the same semantics as the
   // QuicFramerVisitorInterface, returning true if the framer should continue
   // processing the packet, false if not.
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame);
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame);
 
-  // Processes the STREAM ID BLOCKED frame, invoked from
-  // QuicSession::OnStreamIdBlockedFrame. It has the same semantics as the
+  // Processes the STREAMS_BLOCKED frame, invoked from
+  // QuicSession::OnStreamsBlockedFrame. It has the same semantics as the
   // QuicFramerVisitorInterface, returning true if the framer should continue
   // processing the packet, false if not.
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame);
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame);
 
-  // Indicates whether the next outgoing stream ID can be allocated or not. The
-  // test is whether it will exceed the maximum-stream-id or not.
+  // Indicates whether the next outgoing stream ID can be allocated or not.
   bool CanOpenNextOutgoingStream();
 
-  // Generate and send a MAX_STREAM_ID frame.
-  void SendMaxStreamIdFrame();
+  // Generate and send a MAX_STREAMS frame.
+  void SendMaxStreamsFrame();
 
-  // Invoked to deal with releasing a stream ID.
+  // Invoked to deal with releasing a stream. Does nothing if the stream is
+  // outgoing. If the stream is incoming, the number of streams that the peer
+  // can open will be updated and a MAX_STREAMS frame, informing the peer of
+  // the additional streams, may be sent.
   void OnStreamClosed(QuicStreamId stream_id);
 
-  // Returns the next outgoing stream id. If it fails (due to running into the
-  // max_allowed_outgoing_stream_id limit) then it returns an invalid stream id.
+  // Returns the next outgoing stream id. Applications must call
+  // CanOpenNextOutgoingStream() first.  A QUIC_BUG is logged if this method
+  // allocates a stream ID past the peer specified limit.
   QuicStreamId GetNextOutgoingStreamId();
 
-  // Initialize the maximum allowed incoming stream id and number of streams.
-  void SetMaxOpenIncomingStreams(size_t max_streams);
+  // Set the outgoing stream limits to be |max_open_streams| plus the number
+  // of static streams that have been opened. For outgoing and incoming,
+  // respectively.
+  // SetMaxOpenOutgoingStreams will QUIC_BUG if it is called after
+  // a MAX_STREAMS frame has been received.
+  // TODO(fkastenholz): When static streams disappear, these should be removed.
+  void SetMaxOpenOutgoingStreams(size_t max_open_streams);
+  void SetMaxOpenIncomingStreams(size_t max_open_streams);
 
-  // Initialize the maximum allowed outgoing stream id, number of streams, and
-  // MAX_STREAM_ID advertisement window.
-  void SetMaxOpenOutgoingStreams(size_t max_streams);
+  // Adjust the outgoing stream limit - max_open_streams is the limit, not
+  // including static streams. Does not QUIC_BUG if it is called _after_
+  // receiving a MAX_STREAMS.
+  void AdjustMaxOpenOutgoingStreams(size_t max_open_streams);
+
+  // Sets the maximum number of outgoing streams to max_open_streams.
+  // Used when configuration has been done and we have an initial
+  // maximum stream count from the peer. Note that if the stream count is such
+  // that it would result in stream ID values that are greater than the
+  // implementation limit, it pegs the count at the implementation limit.
+  bool ConfigureMaxOpenOutgoingStreams(size_t max_open_streams);
 
   // Register a new stream as a static stream. This is used so that the
-  // advertised maximum stream ID can be calculated based on the start of the
+  // advertised MAX STREAMS can be calculated based on the start of the
   // dynamic stream space. This method will take any stream ID, one that either
   // this node or the peer will initiate.
-  void RegisterStaticStream(QuicStreamId stream_id);
+  // Returns false if this fails because the new static stream would cause the
+  // stream limit to be exceeded.
+  bool RegisterStaticStream(QuicStreamId stream_id);
 
-  // Check that an incoming stream id is valid -- is below the maximum allowed
-  // stream ID. Note that this method uses the actual maximum, not the most
-  // recently advertised maximum this helps preserve the Google-QUIC semantic
-  // that we actually care about the number of open streams, not the maximum
-  // stream ID.  Returns true if the stream ID is valid. If the stream ID fails
-  // the test, will close the connection (per the protocol specification) and
-  // return false. This method also maintains state with regard to the number of
-  // streams that the peer can open (used for generating MAX_STREAM_ID frames).
+  // Checks if the incoming stream ID exceeds the MAX_STREAMS limit.  If the
+  // limit is exceeded, closes the connection and returns false.  Uses the
+  // actual maximium, not the most recently advertised value, in order to
+  // enforce the Google-QUIC number of open streams behavior.
   // This method should be called exactly once for each incoming stream
   // creation.
   bool MaybeIncreaseLargestPeerStreamId(const QuicStreamId stream_id);
@@ -117,122 +131,145 @@
   // Return true if given stream is peer initiated.
   bool IsIncomingStream(QuicStreamId id) const;
 
-  size_t max_allowed_outgoing_streams() const {
-    return max_allowed_outgoing_streams_;
+  size_t outgoing_static_stream_count() const {
+    return outgoing_static_stream_count_;
   }
-  size_t max_allowed_incoming_streams() const {
-    return max_allowed_incoming_streams_;
+
+  size_t incoming_initial_max_open_streams() const {
+    return incoming_initial_max_open_streams_;
   }
-  QuicStreamId max_allowed_outgoing_stream_id() const {
-    return max_allowed_outgoing_stream_id_;
-  }
-  QuicStreamId advertised_max_allowed_incoming_stream_id() const {
-    return advertised_max_allowed_incoming_stream_id_;
-  }
-  QuicStreamId actual_max_allowed_incoming_stream_id() const {
-    return actual_max_allowed_incoming_stream_id_;
-  }
-  QuicStreamId max_stream_id_window() const { return max_stream_id_window_; }
+
+  QuicStreamCount max_streams_window() const { return max_streams_window_; }
 
   QuicStreamId next_outgoing_stream_id() const {
     return next_outgoing_stream_id_;
   }
 
-  QuicStreamId first_incoming_dynamic_stream_id() {
-    return first_incoming_dynamic_stream_id_;
-  }
-  QuicStreamId first_outgoing_dynamic_stream_id() {
-    return first_outgoing_dynamic_stream_id_;
-  }
-  size_t available_incoming_streams() { return available_incoming_streams_; }
-
-  void set_max_allowed_incoming_streams(size_t stream_count) {
-    max_allowed_incoming_streams_ = stream_count;
-  }
+  // Number of streams that the peer believes that it can still create.
+  size_t available_incoming_streams();
 
   void set_largest_peer_created_stream_id(
       QuicStreamId largest_peer_created_stream_id) {
     largest_peer_created_stream_id_ = largest_peer_created_stream_id;
   }
 
+  // These are the limits for outgoing and incoming streams,
+  // respectively. For incoming there are two limits, what has
+  // been advertised to the peer and what is actually available.
+  // The advertised incoming amount should never be more than the actual
+  // incoming one.
+  QuicStreamCount outgoing_max_streams() const { return outgoing_max_streams_; }
+  QuicStreamCount incoming_actual_max_streams() const {
+    return incoming_actual_max_streams_;
+  }
+  QuicStreamCount incoming_advertised_max_streams() const {
+    return incoming_advertised_max_streams_;
+  }
+  // Number of streams that have been opened (including those that have been
+  // opened and then closed. Must never exceed outgoing_max_streams
+  QuicStreamCount outgoing_stream_count() { return outgoing_stream_count_; }
+
+  // Perspective (CLIENT/SERVER) of this node and the peer, respectively.
+  Perspective perspective() const;
+  Perspective peer_perspective() const;
+
+  QuicTransportVersion transport_version() const;
+
  private:
   friend class test::QuicSessionPeer;
   friend class test::QuicStreamIdManagerPeer;
 
-  // Check whether the MAX_STREAM_ID window has opened up enough and, if so,
-  // generate and send a MAX_STREAM_ID frame.
-  void MaybeSendMaxStreamIdFrame();
+  // Check whether the MAX_STREAMS window has opened up enough and, if so,
+  // generate and send a MAX_STREAMS frame.
+  void MaybeSendMaxStreamsFrame();
+
+  // Get what should be the first incoming/outgoing stream ID that
+  // this stream id manager will manage, taking into account directionality and
+  // client/server perspective.
+  QuicStreamId GetFirstOutgoingStreamId() const;
+  QuicStreamId GetFirstIncomingStreamId() const;
+
+  void CalculateIncomingMaxStreamsWindow();
 
   // Back reference to the session containing this Stream ID Manager.
   // needed to access various session methods, such as perspective()
   QuicSession* session_;
 
+  // Whether this stream id manager is for unidrectional (true) or bidirectional
+  // (false) streams.
+  bool unidirectional_;
+
+  // This is the number of streams that this node can initiate.
+  // This limit applies to both static and dynamic streams - the total
+  // of the two can not exceed this count.
+  // This limit is:
+  //   - Initiated to a value specified in the constructor
+  //   - May be updated when the config is received.
+  //   - Is updated whenever a MAX STREAMS frame is received.
+  QuicStreamCount outgoing_max_streams_;
+
   // The ID to use for the next outgoing stream.
   QuicStreamId next_outgoing_stream_id_;
 
+  // The number of outgoing streams that have ever been opened, including those
+  // that have been closed. This number must never be larger than
+  // outgoing_max_streams_.
+  QuicStreamCount outgoing_stream_count_;
+
+  // Number of outgoing static streams created.
+  // TODO(fkastenholz): Remove when static streams no longer supported for IETF
+  // QUIC.
+  QuicStreamCount outgoing_static_stream_count_;
+
+  // Set to true while the default (from the constructor) outgoing stream limit
+  // is in use. It is set to false when either a MAX STREAMS frame is received
+  // or the transport negotiation completes and sets the stream limit (this is
+  // equivalent to a MAX_STREAMS frame).
+  // Necessary because outgoing_max_streams_ is a "best guess"
+  // until we receive an authoritative value from the peer.
+  // outgoing_max_streams_ is initialized in the constructor
+  // to some hard-coded value, which may or may not be consistent
+  // with what the peer wants. Furthermore, as we create outgoing
+  // static streams, the cap raises as static streams get inserted
+  // "beneath" the dynamic streams because, prior to receiving
+  // a MAX_STREAMS, the values setting the limit are interpreted
+  // as "number of request/responses" that can be created. Once
+  // a MAX_STREAMS is received, it becomes a hard limit.
+  bool using_default_max_streams_;
+
+  // FOR INCOMING STREAMS
+
+  // The maximum number of streams that can be opened by the peer.
+  QuicStreamCount incoming_actual_max_streams_;
+  QuicStreamCount incoming_advertised_max_streams_;
+
+  // Initial maximum on the number of open streams allowed.
+  QuicStreamCount incoming_initial_max_open_streams_;
+
+  // Number of outgoing static streams created.
+  // TODO(fkastenholz): Remove when static streams no longer supported for IETF
+  // QUIC.
+  QuicStreamCount incoming_static_stream_count_;
+
+  // This is the number of streams that have been created -- some are still
+  // open, the others have been closed. It is the number that is compared
+  // against MAX_STREAMS when deciding whether to accept a new stream or not.
+  QuicStreamCount incoming_stream_count_;
+
   // Set of stream ids that are less than the largest stream id that has been
   // received, but are nonetheless available to be created.
   QuicUnorderedSet<QuicStreamId> available_streams_;
 
   QuicStreamId largest_peer_created_stream_id_;
 
-  // The maximum stream ID value that we can use. This is initialized based on,
-  // first, the default number of open streams we can do, updated per the number
-  // of streams we receive in the transport parameters, and then finally is
-  // modified whenever a MAX_STREAM_ID frame is received from the peer.
-  QuicStreamId max_allowed_outgoing_stream_id_;
-
-  // Unlike for streams this node initiates, for incoming streams, there are two
-  // maxima; the actual maximum which is the limit the peer must obey and the
-  // maximum that was most recently advertised to the peer in a MAX_STREAM_ID
-  // frame.
-  //
-  // The advertised maximum is never larger than the actual maximum. The actual
-  // maximum increases whenever an incoming stream is closed. The advertised
-  // maximum increases (to the actual maximum) whenever a MAX_STREAM_ID is sent.
-  //
-  // The peer is granted some leeway, incoming streams are accepted as long as
-  // their stream id is not greater than the actual maximum.  The protocol
-  // specifies that the advertised maximum is the limit. This implmentation uses
-  // the actual maximum in order to support Google-QUIC semantics, where it's
-  // the number of open streams, not their ID number, that is the real limit.
-  QuicStreamId actual_max_allowed_incoming_stream_id_;
-  QuicStreamId advertised_max_allowed_incoming_stream_id_;
-
-  // max_stream_id_window_ is set to max_allowed_outgoing_streams_ / 2
-  // (half of the number of streams that are allowed).  The local node
-  // does not send a MAX_STREAM_ID frame to the peer until the local node
-  // believes that the peer can open fewer than |max_stream_id_window_|
-  // streams. When that is so, the local node sends a MAX_STREAM_ID every time
-  // an inbound stream is closed.
-  QuicStreamId max_stream_id_window_;
-
-  // Maximum number of outgoing and incoming streams that are allowed to be
-  // concurrently opened. Initialized as part of configuration.
-  size_t max_allowed_outgoing_streams_;
-  size_t max_allowed_incoming_streams_;
-
-  // Keep track of the first dynamic stream id (which is the largest static
-  // stream id plus one id).  For Google QUIC, static streams are not counted
-  // against the stream count limit. When the number of static streams
-  // increases, the maximum stream id has to increase by a corresponding amount.
-  // These are used as floors from which the relevant maximum is
-  // calculated. Keeping the "first dynamic" rather than the "last static" has
-  // some implementation advantages.
-  QuicStreamId first_incoming_dynamic_stream_id_;
-  QuicStreamId first_outgoing_dynamic_stream_id_;
-
-  // Number of streams that that this node believes that the
-  // peer can open. It is initialized to the same value as
-  // max_allowed_incoming_streams_. It is decremented every
-  // time a new incoming stream is detected.  A MAX_STREAM_ID
-  // is sent whenver a stream closes and this counter is less
-  // than the window. When that happens, it is incremented by
-  // the number of streams we make available (the actual max
-  // stream ID - the most recently advertised one)
-  size_t available_incoming_streams_;
+  // When incoming streams close the local node sends MAX_STREAMS frames. It
+  // does so only when the peer can open fewer than |max_stream_id_window_|
+  // streams. That is, when |incoming_actual_max_streams_| -
+  // |incoming_advertised_max_streams_| is less than the window.
+  // 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_;
 };
-
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_QUIC_STREAM_ID_MANAGER_H_
diff --git a/quic/core/quic_stream_id_manager_test.cc b/quic/core/quic_stream_id_manager_test.cc
index 3d0c06c..47b97cc 100644
--- a/quic/core/quic_stream_id_manager_test.cc
+++ b/quic/core/quic_stream_id_manager_test.cc
@@ -118,10 +118,10 @@
     connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
     session_ = QuicMakeUnique<TestQuicSession>(connection_);
     stream_id_manager_ =
-        GetParam() ? QuicSessionPeer::v99_bidirectional_stream_id_manager(
-                         session_.get())
-                   : QuicSessionPeer::v99_unidirectional_stream_id_manager(
-                         session_.get());
+        IsBidi() ? QuicSessionPeer::v99_bidirectional_stream_id_manager(
+                       session_.get())
+                 : QuicSessionPeer::v99_unidirectional_stream_id_manager(
+                       session_.get());
   }
 
   QuicTransportVersion transport_version() const {
@@ -154,6 +154,27 @@
            kV99StreamIdIncrement * n;
   }
 
+  QuicStreamId StreamCountToId(QuicStreamCount stream_count,
+                               Perspective perspective) {
+    // Calculate and build up stream ID rather than use
+    // GetFirst... because the tests that rely on this method
+    // needs to do the stream count where #1 is 0/1/2/3, and not
+    // take into account that stream 0 is special.
+    QuicStreamId id =
+        ((stream_count - 1) * QuicUtils::StreamIdDelta(QUIC_VERSION_99));
+    if (IsUnidi()) {
+      id |= 0x2;
+    }
+    if (perspective == Perspective::IS_SERVER) {
+      id |= 0x1;
+    }
+    return id;
+  }
+
+  // GetParam returns true if the test is for bidirectional streams
+  bool IsUnidi() { return GetParam() ? false : true; }
+  bool IsBidi() { return GetParam(); }
+
   MockQuicConnectionHelper helper_;
   MockAlarmFactory alarm_factory_;
   StrictMock<MockQuicConnection>* connection_;
@@ -178,128 +199,146 @@
 TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerClientInitialization) {
   // These fields are inited via the QuicSession constructor to default
   // values defined as a constant.
+  // If bidi, Crypto stream default created  at start up, it is one
+  // more stream to account for since initialization is "number of
+  // request/responses" & crypto is added in to that, not streams.
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection + (GetParam() ? 1 : 0),
+            stream_id_manager_->outgoing_max_streams());
+  // Test is predicated on having 1 static stream going if bidi, 0 if not...)
+  EXPECT_EQ((GetParam() ? 1u : 0),
+            stream_id_manager_->outgoing_static_stream_count());
+
   EXPECT_EQ(kDefaultMaxStreamsPerConnection,
-            stream_id_manager_->max_allowed_incoming_streams());
+            stream_id_manager_->incoming_actual_max_streams());
   EXPECT_EQ(kDefaultMaxStreamsPerConnection,
-            stream_id_manager_->max_allowed_outgoing_streams());
+            stream_id_manager_->incoming_advertised_max_streams());
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection,
+            stream_id_manager_->incoming_initial_max_open_streams());
 
   // The window for advertising updates to the MAX STREAM ID is half the number
   // of streams allowed.
-  EXPECT_EQ(kDefaultMaxStreamsPerConnection / kMaxStreamIdWindowDivisor,
-            stream_id_manager_->max_stream_id_window());
-
-  // This test runs as a client, so it initiates (that is to say, outgoing)
-  // even-numbered stream IDs. Also, our implementation starts allocating
-  // stream IDs at 0 (for clients) 1 (for servers) -- before taking statically
-  // allocated streams into account. The -1 in the calculation is
-  // because the value being tested is the maximum allowed stream ID, not the
-  // first unallowed stream id.
-  const QuicStreamId kExpectedMaxOutgoingStreamId =
-      (GetParam() ? session_->next_outgoing_bidirectional_stream_id()
-                  : session_->next_outgoing_unidirectional_stream_id()) +
-      ((kDefaultMaxStreamsPerConnection - 1) * kV99StreamIdIncrement);
-  EXPECT_EQ(kExpectedMaxOutgoingStreamId,
-            stream_id_manager_->max_allowed_outgoing_stream_id());
-
-  // Same for IDs of incoming streams...
-  const QuicStreamId kExpectedMaxIncomingStreamId =
-      stream_id_manager_->first_incoming_dynamic_stream_id() +
-      (kDefaultMaxStreamsPerConnection - 1) * kV99StreamIdIncrement;
-  EXPECT_EQ(kExpectedMaxIncomingStreamId,
-            stream_id_manager_->actual_max_allowed_incoming_stream_id());
-  EXPECT_EQ(kExpectedMaxIncomingStreamId,
-            stream_id_manager_->advertised_max_allowed_incoming_stream_id());
-}
-
-// This test checks that the initialization for the maximum allowed outgoing
-// stream id is correct.
-TEST_P(QuicStreamIdManagerTestClient, CheckMaxAllowedOutgoing) {
-  const size_t kNumOutgoingStreams = 124;
-  stream_id_manager_->SetMaxOpenOutgoingStreams(kNumOutgoingStreams);
-  EXPECT_EQ(kNumOutgoingStreams,
-            stream_id_manager_->max_allowed_outgoing_streams());
-
-  // Check that the maximum available stream is properly set.
-  size_t expected_max_outgoing_id =
-      (GetParam() ? session_->next_outgoing_bidirectional_stream_id()
-                  : session_->next_outgoing_unidirectional_stream_id()) +
-      ((kNumOutgoingStreams - 1) * kV99StreamIdIncrement);
-  EXPECT_EQ(expected_max_outgoing_id,
-            stream_id_manager_->max_allowed_outgoing_stream_id());
-}
-
-// This test checks that the initialization for the maximum allowed incoming
-// stream id is correct.
-TEST_P(QuicStreamIdManagerTestClient, CheckMaxAllowedIncoming) {
-  const size_t kStreamCount = 245;
-  stream_id_manager_->SetMaxOpenIncomingStreams(kStreamCount);
-  EXPECT_EQ(kStreamCount, stream_id_manager_->max_allowed_incoming_streams());
-  // Check that the window is 1/2 (integer math) of the stream count.
-  EXPECT_EQ(kStreamCount / 2, stream_id_manager_->max_stream_id_window());
-
-  // Actual- and advertised- maxima start out equal.
-  EXPECT_EQ(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
-            stream_id_manager_->advertised_max_allowed_incoming_stream_id());
-
-  // Check that the maximum stream ID is properly calculated.
-  EXPECT_EQ(stream_id_manager_->first_incoming_dynamic_stream_id() +
-                ((kStreamCount - 1) * kV99StreamIdIncrement),
-            stream_id_manager_->actual_max_allowed_incoming_stream_id());
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection / kMaxStreamsWindowDivisor,
+            stream_id_manager_->max_streams_window());
 }
 
 // This test checks that the stream advertisement window is set to 1
 // if the number of stream ids is 1. This is a special case in the code.
-TEST_P(QuicStreamIdManagerTestClient, CheckMaxStreamIdWindow1) {
+TEST_P(QuicStreamIdManagerTestClient, CheckMaxStreamsWindow1) {
   stream_id_manager_->SetMaxOpenIncomingStreams(1);
-  EXPECT_EQ(1u, stream_id_manager_->max_allowed_incoming_streams());
+  EXPECT_EQ(1u, stream_id_manager_->incoming_initial_max_open_streams());
+  EXPECT_EQ(1u, stream_id_manager_->incoming_actual_max_streams());
   // If streamid_count/2==0 (integer math) force it to 1.
-  EXPECT_EQ(1u, stream_id_manager_->max_stream_id_window());
+  EXPECT_EQ(1u, stream_id_manager_->max_streams_window());
 }
 
-// Check the case of the stream ID in a STREAM_ID_BLOCKED frame is less than the
-// stream ID most recently advertised in a MAX_STREAM_ID frame. This should
-// cause a MAX_STREAM_ID frame with the most recently advertised stream id to be
-// sent.
-TEST_P(QuicStreamIdManagerTestClient, ProcessStreamIdBlockedOk) {
+// Test that stream counts that would exceed the implementation maximum are
+// safely handled.
+// First, check that setting up to and including the implementation maximum
+// is OK.
+TEST_P(QuicStreamIdManagerTestClient, CheckMaxStreamsBadValuesToMaxOkOutgoing) {
+  QuicStreamCount implementation_max =
+      QuicUtils::GetMaxStreamCount(!GetParam(), /* GetParam==true for bidi */
+                                   Perspective::IS_CLIENT);
+  stream_id_manager_->AdjustMaxOpenOutgoingStreams(implementation_max - 1u);
+  // If bidi, Crypto stream default created  at start up, it is one
+  // more stream to account for since initialization is "number of
+  // request/responses" & crypto is added in to that, not streams.
+  EXPECT_EQ(implementation_max - 1u + (GetParam() ? 1 : 0),
+            stream_id_manager_->outgoing_max_streams());
+
+  stream_id_manager_->AdjustMaxOpenOutgoingStreams(implementation_max);
+  EXPECT_EQ(implementation_max, stream_id_manager_->outgoing_max_streams());
+}
+
+// Now check that setting to a value larger than the maximum fails.
+TEST_P(QuicStreamIdManagerTestClient,
+       CheckMaxStreamsBadValuesOverMaxFailsOutgoing) {
+  QuicStreamCount implementation_max =
+      QuicUtils::GetMaxStreamCount(!GetParam(), /* GetParam==true for bidi */
+                                   Perspective::IS_CLIENT);
+  // Ensure that the limit is less than the implementation maximum.
+  EXPECT_LT(stream_id_manager_->outgoing_max_streams(), implementation_max);
+
+  // Try to go over.
+  stream_id_manager_->AdjustMaxOpenOutgoingStreams(implementation_max + 1);
+  // Should be pegged at the max.
+  EXPECT_EQ(implementation_max, stream_id_manager_->outgoing_max_streams());
+}
+
+// Now do the same for the incoming streams
+TEST_P(QuicStreamIdManagerTestClient, CheckMaxStreamsBadValuesIncoming) {
+  QuicStreamCount implementation_max =
+      QuicUtils::GetMaxStreamCount(!GetParam(), /* GetParam==true for bidi */
+                                   Perspective::IS_CLIENT);
+  stream_id_manager_->SetMaxOpenIncomingStreams(implementation_max - 1u);
+  EXPECT_EQ(implementation_max - 1u,
+            stream_id_manager_->incoming_initial_max_open_streams());
+  EXPECT_EQ(implementation_max - 1u,
+            stream_id_manager_->incoming_actual_max_streams());
+  EXPECT_EQ((implementation_max - 1u) / 2,
+            stream_id_manager_->max_streams_window());
+
+  stream_id_manager_->SetMaxOpenIncomingStreams(implementation_max);
+  EXPECT_EQ(implementation_max,
+            stream_id_manager_->incoming_initial_max_open_streams());
+  EXPECT_EQ(implementation_max,
+            stream_id_manager_->incoming_actual_max_streams());
+  EXPECT_EQ(implementation_max / 2, stream_id_manager_->max_streams_window());
+
+  // Reset to 1 so that we can detect the change.
+  stream_id_manager_->SetMaxOpenIncomingStreams(1u);
+  EXPECT_EQ(1u, stream_id_manager_->incoming_initial_max_open_streams());
+  EXPECT_EQ(1u, stream_id_manager_->incoming_actual_max_streams());
+  EXPECT_EQ(1u, stream_id_manager_->max_streams_window());
+  // Now try to exceed the max, without wrapping.
+  stream_id_manager_->SetMaxOpenIncomingStreams(implementation_max + 1);
+  EXPECT_EQ(implementation_max,
+            stream_id_manager_->incoming_initial_max_open_streams());
+  EXPECT_EQ(implementation_max,
+            stream_id_manager_->incoming_actual_max_streams());
+  EXPECT_EQ(implementation_max / 2, stream_id_manager_->max_streams_window());
+}
+
+// Check the case of the stream count in a STREAMS_BLOCKED frame is less than
+// the count most recently advertised in a MAX_STREAMS frame. This should cause
+// a MAX_STREAMS frame with the most recently advertised count to be sent.
+TEST_P(QuicStreamIdManagerTestClient, ProcessStreamsBlockedOk) {
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .WillRepeatedly(Invoke(session_.get(), &TestQuicSession::SaveFrame));
-  QuicStreamId stream_id =
-      stream_id_manager_->advertised_max_allowed_incoming_stream_id() -
-      kV99StreamIdIncrement;
-  QuicStreamIdBlockedFrame frame(0, stream_id);
-  session_->OnStreamIdBlockedFrame(frame);
+  QuicStreamCount stream_count =
+      stream_id_manager_->incoming_initial_max_open_streams() - 1;
+  QuicStreamsBlockedFrame frame(0, stream_count, /*unidirectional=*/false);
+  session_->OnStreamsBlockedFrame(frame);
 
-  // We should see a MAX_STREAM_ID frame.
-  EXPECT_EQ(MAX_STREAM_ID_FRAME, session_->save_frame().type);
+  // We should see a MAX_STREAMS frame.
+  EXPECT_EQ(MAX_STREAMS_FRAME, session_->save_frame().type);
 
   // and it should advertise the current max-allowed value.
-  EXPECT_EQ(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
-            session_->save_frame().max_stream_id_frame.max_stream_id);
+  EXPECT_EQ(stream_id_manager_->incoming_initial_max_open_streams(),
+            session_->save_frame().max_streams_frame.stream_count);
 }
 
-// Check the case of the stream ID in a STREAM_ID_BLOCKED frame is equal to
-// stream ID most recently advertised in a MAX_STREAM_ID frame. No
-// MAX_STREAM_ID should be generated.
-TEST_P(QuicStreamIdManagerTestClient, ProcessStreamIdBlockedNoOp) {
+// Check the case of the stream count in a STREAMS_BLOCKED frame is equal to the
+// count most recently advertised in a MAX_STREAMS frame. No MAX_STREAMS
+// should be generated.
+TEST_P(QuicStreamIdManagerTestClient, ProcessStreamsBlockedNoOp) {
   EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
-  QuicStreamId stream_id =
-      stream_id_manager_->advertised_max_allowed_incoming_stream_id();
-  QuicStreamIdBlockedFrame frame(0, stream_id);
-  session_->OnStreamIdBlockedFrame(frame);
+  QuicStreamCount stream_count =
+      stream_id_manager_->incoming_initial_max_open_streams();
+  QuicStreamsBlockedFrame frame(0, stream_count, /*unidirectional=*/false);
+  session_->OnStreamsBlockedFrame(frame);
 }
 
-// Check the case of the stream ID in a STREAM_ID_BLOCKED frame is greater than
-// the stream ID most recently advertised in a MAX_STREAM_ID frame. Expect a
+// Check the case of the stream count in a STREAMS_BLOCKED frame is greater than
+// the count most recently advertised in a MAX_STREAMS frame. Expect a
 // connection close with an error.
-TEST_P(QuicStreamIdManagerTestClient, ProcessStreamIdBlockedTooBig) {
-  EXPECT_CALL(*connection_,
-              CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
+TEST_P(QuicStreamIdManagerTestClient, ProcessStreamsBlockedTooBig) {
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_STREAMS_BLOCKED_ERROR, _, _));
   EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
-  QuicStreamId stream_id =
-      stream_id_manager_->advertised_max_allowed_incoming_stream_id() +
-      kV99StreamIdIncrement;
-  QuicStreamIdBlockedFrame frame(0, stream_id);
-  session_->OnStreamIdBlockedFrame(frame);
+  QuicStreamCount stream_count =
+      stream_id_manager_->incoming_initial_max_open_streams() + 1;
+  QuicStreamsBlockedFrame frame(0, stream_count, /*unidirectional=*/false);
+  session_->OnStreamsBlockedFrame(frame);
 }
 
 // Same basic tests as above, but calls
@@ -310,8 +349,8 @@
 // First test make sure that streams with ids below the limit are accepted.
 TEST_P(QuicStreamIdManagerTestClient, IsIncomingStreamIdValidBelowLimit) {
   QuicStreamId stream_id =
-      stream_id_manager_->actual_max_allowed_incoming_stream_id() -
-      kV99StreamIdIncrement;
+      StreamCountToId(stream_id_manager_->incoming_actual_max_streams() - 1,
+                      Perspective::IS_CLIENT);
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
   EXPECT_TRUE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(stream_id));
 }
@@ -319,227 +358,219 @@
 // Accept a stream with an ID that equals the limit.
 TEST_P(QuicStreamIdManagerTestClient, IsIncomingStreamIdValidAtLimit) {
   QuicStreamId stream_id =
-      stream_id_manager_->actual_max_allowed_incoming_stream_id();
+      StreamCountToId(stream_id_manager_->incoming_actual_max_streams(),
+                      Perspective::IS_CLIENT);
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
   EXPECT_TRUE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(stream_id));
 }
 
 // Close the connection if the id exceeds the limit.
 TEST_P(QuicStreamIdManagerTestClient, IsIncomingStreamIdInValidAboveLimit) {
-  QuicStreamId stream_id =
-      stream_id_manager_->actual_max_allowed_incoming_stream_id() +
-      kV99StreamIdIncrement;
+  QuicStreamId stream_id = StreamCountToId(
+      stream_id_manager_->incoming_actual_max_streams() + 1,
+      Perspective::IS_SERVER);  // This node is a client, incoming
+                                // stream ids must be server-originated.
   std::string error_details =
-      GetParam() ? "Stream id 401 above 397" : "Stream id 403 above 399";
+      GetParam() ? "Stream id 401 would exceed stream count limit 100"
+                 : "Stream id 403 would exceed stream count limit 100";
   EXPECT_CALL(*connection_,
               CloseConnection(QUIC_INVALID_STREAM_ID, error_details, _));
   EXPECT_FALSE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(stream_id));
 }
 
-// Test that a client will reject a MAX_STREAM_ID that specifies a
-// server-initiated stream ID.
-TEST_P(QuicStreamIdManagerTestClient, RejectServerMaxStreamId) {
-  QuicStreamId id = stream_id_manager_->max_allowed_outgoing_stream_id();
-
-  // Ensure that the ID that will be in the MAX_STREAM_ID is larger than the
-  // current MAX.
-  id += (kV99StreamIdIncrement * 2);
-
-  // Make it an odd (server-initiated) ID.
-  id |= 0x1;
-  EXPECT_FALSE(QuicUtils::IsClientInitiatedStreamId(QUIC_VERSION_99, id));
-
-  // Make the frame and process it; should result in the connection being
-  // closed.
-  QuicMaxStreamIdFrame frame(0, id);
-  EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAM_ID_ERROR, _, _));
-  session_->OnMaxStreamIdFrame(frame);
-}
-
-// Test that a client will reject a STREAM_ID_BLOCKED that specifies a
-// client-initiated stream ID. STREAM_ID_BLOCKED from a server should specify an
-// odd (server-initiated_ ID). Generate one with an odd ID and check that the
-// connection is closed.
-TEST_P(QuicStreamIdManagerTestClient, RejectServerStreamIdBlocked) {
-  QuicStreamId id = stream_id_manager_->max_allowed_outgoing_stream_id();
-
-  // Ensure that the ID that will be in the MAX_STREAM_ID is larger than the
-  // current MAX.
-  id += (kV99StreamIdIncrement * 2);
-  // Make sure it's odd, like a client-initiated ID.
-  id &= ~0x01;
-  EXPECT_TRUE(QuicUtils::IsClientInitiatedStreamId(QUIC_VERSION_99, id));
-
-  // Generate and process the frame; connection should be closed.
-  QuicStreamIdBlockedFrame frame(0, id);
-  EXPECT_CALL(*connection_,
-              CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
-  session_->OnStreamIdBlockedFrame(frame);
-}
-
-// Test functionality for reception of a MAX STREAM ID frame. This code is
+// Test functionality for reception of a MAX_STREAMS frame. This code is
 // client/server-agnostic.
-TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerClientOnMaxStreamIdFrame) {
-  // Get the current maximum allowed outgoing stream ID.
-  QuicStreamId initial_stream_id =
-      stream_id_manager_->max_allowed_outgoing_stream_id();
-  QuicMaxStreamIdFrame frame;
+TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerClientOnMaxStreamsFrame) {
+  // Get the current maximum allowed outgoing stream count.
+  QuicStreamCount initial_stream_count =
+      // need to know the number of request/response streams.
+      // This is the total number of outgoing streams (which includes both
+      // req/resp and statics) minus just the statics...
+      stream_id_manager_->outgoing_max_streams() -
+      stream_id_manager_->outgoing_static_stream_count();
 
-  // If the stream ID in the frame is < the current maximum then
-  // the frame should be ignored.
-  frame.max_stream_id = initial_stream_id - kV99StreamIdIncrement;
-  EXPECT_TRUE(stream_id_manager_->OnMaxStreamIdFrame(frame));
-  EXPECT_EQ(initial_stream_id,
-            stream_id_manager_->max_allowed_outgoing_stream_id());
+  QuicMaxStreamsFrame frame;
 
-  // A stream ID greater than the current limit should increase the limit.
-  frame.max_stream_id = initial_stream_id + kV99StreamIdIncrement;
-  EXPECT_TRUE(stream_id_manager_->OnMaxStreamIdFrame(frame));
-  EXPECT_EQ(initial_stream_id + kV99StreamIdIncrement,
-            stream_id_manager_->max_allowed_outgoing_stream_id());
+  // Even though the stream count in the frame is < the initial maximum,
+  // it is should be ignored since the initial max was set via
+  // the constructor (an educated guess) and the MAX STREAMS frame
+  // is authoritative.
+  frame.stream_count = initial_stream_count - 1;
+
+  frame.unidirectional = IsUnidi();
+  EXPECT_TRUE(stream_id_manager_->OnMaxStreamsFrame(frame));
+  EXPECT_EQ(initial_stream_count - 1,
+            stream_id_manager_->outgoing_max_streams());
+
+  QuicStreamCount save_outgoing_max_streams =
+      stream_id_manager_->outgoing_max_streams();
+  // Now that there has been one MAX STREAMS frame, we should not
+  // accept a MAX_STREAMS that reduces the limit...
+  frame.stream_count = initial_stream_count - 2;
+  frame.unidirectional = IsUnidi();
+  EXPECT_TRUE(stream_id_manager_->OnMaxStreamsFrame(frame));
+  // should not change from previous setting.
+  EXPECT_EQ(save_outgoing_max_streams,
+            stream_id_manager_->outgoing_max_streams());
+
+  // A stream count greater than the current limit should increase the limit.
+  frame.stream_count = initial_stream_count + 1;
+  EXPECT_TRUE(stream_id_manager_->OnMaxStreamsFrame(frame));
+
+  EXPECT_EQ(initial_stream_count + 1,
+            stream_id_manager_->outgoing_max_streams());
 }
 
-// Test functionality for reception of a STREAM ID BLOCKED frame.
+// Test functionality for reception of a STREAMS_BLOCKED frame.
 // This code is client/server-agnostic.
-TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerOnStreamIdBlockedFrame) {
-  // Get the current maximum allowed incoming stream ID.
-  QuicStreamId advertised_stream_id =
-      stream_id_manager_->advertised_max_allowed_incoming_stream_id();
-  QuicStreamIdBlockedFrame frame;
+TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerOnStreamsBlockedFrame) {
+  // Get the current maximum allowed incoming stream count.
+  QuicStreamCount advertised_stream_count =
+      stream_id_manager_->incoming_advertised_max_streams();
+  QuicStreamsBlockedFrame frame;
 
-  // If the peer is saying it's blocked on the stream ID that
+  frame.unidirectional = IsUnidi();
+
+  // If the peer is saying it's blocked on the stream count that
   // we've advertised, it's a noop since the peer has the correct information.
-  frame.stream_id = advertised_stream_id;
-  EXPECT_TRUE(stream_id_manager_->OnStreamIdBlockedFrame(frame));
+  frame.stream_count = advertised_stream_count;
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  EXPECT_TRUE(stream_id_manager_->OnStreamsBlockedFrame(frame));
 
-  // If the peer is saying it's blocked on a stream ID that is larger
+  // If the peer is saying it's blocked on a stream count that is larger
   // than what we've advertised, the connection should get closed.
-  frame.stream_id = advertised_stream_id + kV99StreamIdIncrement;
-  EXPECT_CALL(*connection_,
-              CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
-  EXPECT_FALSE(stream_id_manager_->OnStreamIdBlockedFrame(frame));
+  frame.stream_count = advertised_stream_count + 1;
+  EXPECT_CALL(*connection_, CloseConnection(QUIC_STREAMS_BLOCKED_ERROR, _, _));
+  EXPECT_FALSE(stream_id_manager_->OnStreamsBlockedFrame(frame));
 
-  // If the peer is saying it's blocked on a stream ID that is less than
-  // what we've advertised, we send a MAX STREAM ID frame and update
+  // If the peer is saying it's blocked on a count that is less than
+  // our actual count, we send a MAX_STREAMS frame and update
   // the advertised value.
   // First, need to bump up the actual max so there is room for the MAX
-  // STREAM_ID frame to send a larger ID.
-  QuicStreamId actual_stream_id =
-      stream_id_manager_->actual_max_allowed_incoming_stream_id();
-  stream_id_manager_->OnStreamClosed(
-      stream_id_manager_->first_incoming_dynamic_stream_id());
-  EXPECT_EQ(actual_stream_id + kV99StreamIdIncrement,
-            stream_id_manager_->actual_max_allowed_incoming_stream_id());
-  EXPECT_GT(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
-            stream_id_manager_->advertised_max_allowed_incoming_stream_id());
+  // STREAMS frame to send a larger ID.
+  QuicStreamCount actual_stream_count =
+      stream_id_manager_->incoming_actual_max_streams();
 
-  // Now simulate receiving a STTREAM_ID_BLOCKED frame...
-  // Changing the actual maximum, above, forces a MAX STREAM ID frame to be
-  // sent, so the logic for that (SendMaxStreamIdFrame(), etc) is tested.
-  frame.stream_id = advertised_stream_id;
+  // Closing a stream will result in the ability to initiate one more
+  // stream
+  stream_id_manager_->OnStreamClosed(
+      QuicStreamIdManagerPeer::GetFirstIncomingStreamId(stream_id_manager_));
+  EXPECT_EQ(actual_stream_count + 1,
+            stream_id_manager_->incoming_actual_max_streams());
+  EXPECT_EQ(stream_id_manager_->incoming_actual_max_streams(),
+            stream_id_manager_->incoming_advertised_max_streams() + 1);
+
+  // Now simulate receiving a STREAMS_BLOCKED frame...
+  // Changing the actual maximum, above, forces a MAX_STREAMS frame to be
+  // sent, so the logic for that (SendMaxStreamsFrame(), etc) is tested.
+
+  // The STREAMS_BLOCKED frame contains the previous advertised count,
+  // not the one that the peer would have received as a result of the
+  // MAX_STREAMS sent earler.
+  frame.stream_count = advertised_stream_count;
+
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .Times(1)
       .WillRepeatedly(Invoke(session_.get(), &TestQuicSession::SaveFrame));
-  EXPECT_TRUE(stream_id_manager_->OnStreamIdBlockedFrame(frame));
-  EXPECT_EQ(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
-            stream_id_manager_->advertised_max_allowed_incoming_stream_id());
-  EXPECT_EQ(MAX_STREAM_ID_FRAME, session_->save_frame().type);
-  EXPECT_EQ(stream_id_manager_->advertised_max_allowed_incoming_stream_id(),
-            session_->save_frame().max_stream_id_frame.max_stream_id);
 
-  // Ensure a client initiated stream ID is rejected.
-  frame.stream_id = GetParam() ? GetNthClientInitiatedBidirectionalId(1)
-                               : GetNthClientInitiatedUnidirectionalId(1);
-  EXPECT_CALL(*connection_,
-              CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
-  EXPECT_FALSE(stream_id_manager_->OnStreamIdBlockedFrame(frame));
+  EXPECT_TRUE(stream_id_manager_->OnStreamsBlockedFrame(frame));
+  // Check that the saved frame is correct.
+  EXPECT_EQ(stream_id_manager_->incoming_actual_max_streams(),
+            stream_id_manager_->incoming_advertised_max_streams());
+  EXPECT_EQ(MAX_STREAMS_FRAME, session_->save_frame().type);
+  EXPECT_EQ(stream_id_manager_->incoming_advertised_max_streams(),
+            session_->save_frame().max_streams_frame.stream_count);
+  // Make sure that this is the only MAX_STREAMS
+  EXPECT_EQ(1u, GetControlFrameId(session_->save_frame()));
 }
 
 // Test GetNextOutgoingStream. This is client/server agnostic.
-TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerGetNextOutgoingFrame) {
+TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerGetNextOutgoingStream) {
   // Number of streams we can open and the first one we should get when
   // opening...
   int number_of_streams = kDefaultMaxStreamsPerConnection;
   QuicStreamId stream_id =
-      GetParam() ? session_->next_outgoing_bidirectional_stream_id()
-                 : session_->next_outgoing_unidirectional_stream_id();
+      IsUnidi() ? session_->next_outgoing_unidirectional_stream_id()
+                : session_->next_outgoing_bidirectional_stream_id();
 
+  // If bidi, Crypto stream default created  at start up, it is one
+  // more stream to account for since initialization is "number of
+  // request/responses" & crypto is added in to that, not streams.
+  EXPECT_EQ(number_of_streams + (IsBidi() ? 1 : 0),
+            stream_id_manager_->outgoing_max_streams());
   while (number_of_streams) {
     EXPECT_TRUE(stream_id_manager_->CanOpenNextOutgoingStream());
     EXPECT_EQ(stream_id, stream_id_manager_->GetNextOutgoingStreamId());
     stream_id += kV99StreamIdIncrement;
     number_of_streams--;
   }
-  EXPECT_EQ(stream_id - kV99StreamIdIncrement,
-            stream_id_manager_->max_allowed_outgoing_stream_id());
 
   // If we try to check that the next outgoing stream id is available it should
-  // A) fail and B) generate a STREAM_ID_BLOCKED frame.
+  // A) fail and B) generate a STREAMS_BLOCKED frame.
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .Times(1)
       .WillRepeatedly(Invoke(session_.get(), &TestQuicSession::SaveFrame));
   EXPECT_FALSE(stream_id_manager_->CanOpenNextOutgoingStream());
-  EXPECT_EQ(STREAM_ID_BLOCKED_FRAME, session_->save_frame().type);
-  EXPECT_EQ(stream_id_manager_->max_allowed_outgoing_stream_id(),
-            session_->save_frame().max_stream_id_frame.max_stream_id);
+  EXPECT_EQ(STREAMS_BLOCKED_FRAME, session_->save_frame().type);
+  // If bidi, Crypto stream default created  at start up, it is one
+  // more stream to account for since initialization is "number of
+  // request/responses" & crypto is added in to that, not streams.
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection + (IsBidi() ? 1 : 0),
+            session_->save_frame().max_streams_frame.stream_count);
   // If we try to get the next id (above the limit), it should cause a quic-bug.
   EXPECT_QUIC_BUG(
       stream_id_manager_->GetNextOutgoingStreamId(),
-      "Attempt allocate a new outgoing stream ID would exceed the limit");
+      "Attempt to allocate a new outgoing stream that would exceed the limit");
 }
 
 // Ensure that MaybeIncreaseLargestPeerStreamId works properly. This is
 // server/client agnostic.
 TEST_P(QuicStreamIdManagerTestClient,
        StreamIdManagerServerMaybeIncreaseLargestPeerStreamId) {
-  EXPECT_TRUE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(
-      stream_id_manager_->actual_max_allowed_incoming_stream_id()));
+  QuicStreamId max_stream_id =
+      StreamCountToId(stream_id_manager_->incoming_actual_max_streams(),
+                      Perspective::IS_SERVER);
+  EXPECT_TRUE(
+      stream_id_manager_->MaybeIncreaseLargestPeerStreamId(max_stream_id));
+
   QuicStreamId server_initiated_stream_id =
-      GetParam() ? GetNthServerInitiatedBidirectionalId(0)
-                 : GetNthServerInitiatedUnidirectionalId(0);
+      StreamCountToId(1u,  // get 1st id
+                      Perspective::IS_SERVER);
   EXPECT_TRUE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(
       server_initiated_stream_id));
   // A bad stream ID results in a closed connection.
   EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
   EXPECT_FALSE(stream_id_manager_->MaybeIncreaseLargestPeerStreamId(
-      stream_id_manager_->actual_max_allowed_incoming_stream_id() +
-      kV99StreamIdIncrement));
+      max_stream_id + kV99StreamIdIncrement));
 }
 
-// Test the MAX STREAM ID Window functionality.
-// Free up Stream ID space. Do not expect to see a MAX_STREAM_ID
-// until |window| stream ids are available.
-TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerServerMaxStreamId) {
-  // Test that a MAX_STREAM_ID frame is generated when the peer has less than
-  // |max_stream_id_window_| streams left that it can initiate.
+// Test the MAX STREAMS Window functionality.
+TEST_P(QuicStreamIdManagerTestClient, StreamIdManagerServerMaxStreams) {
+  // Test that a MAX_STREAMS frame is generated when the peer has less than
+  // |max_streams_window_| streams left that it can initiate.
 
-  // First, open, and then close, max_stream_id_window_ streams.  This will
-  // max_stream_id_window_ streams available for the peer -- no MAX_STREAM_ID
+  // First, open, and then close, max_streams_window_ streams.  This will
+  // max_streams_window_ streams available for the peer -- no MAX_STREAMS
   // should be sent. The -1 is because the check in
-  // QuicStreamIdManager::MaybeSendMaxStreamIdFrame sends a MAX_STREAM_ID if the
-  // number of available streams at the peer is <= |max_stream_id_window_|
-  int stream_count = stream_id_manager_->max_stream_id_window() - 1;
-
-  QuicStreamId advertised_max =
-      stream_id_manager_->advertised_max_allowed_incoming_stream_id();
-  QuicStreamId expected_actual_max_id =
-      stream_id_manager_->actual_max_allowed_incoming_stream_id();
+  // QuicStreamIdManager::MaybeSendMaxStreamsFrame sends a MAX_STREAMS if the
+  // number of available streams at the peer is <= |max_streams_window_|
+  int stream_count = stream_id_manager_->max_streams_window() - 1;
 
   // Should not get a control-frame transmission since the peer should have
   // "plenty" of stream IDs to use.
   EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
-  // This test runs as a client, so the first stream to release is 2, a
-  // server-initiated stream.
-  QuicStreamId stream_id = GetParam()
-                               ? GetNthServerInitiatedBidirectionalId(0)
-                               : GetNthServerInitiatedUnidirectionalId(0);
+
+  // Get the first incoming stream ID to try and allocate.
+  QuicStreamId stream_id = IsBidi() ? GetNthServerInitiatedBidirectionalId(0)
+                                    : GetNthServerInitiatedUnidirectionalId(0);
   size_t old_available_incoming_streams =
       stream_id_manager_->available_incoming_streams();
-
   while (stream_count) {
     EXPECT_TRUE(
         stream_id_manager_->MaybeIncreaseLargestPeerStreamId(stream_id));
 
+    // This node should think that the peer believes it has one fewer
+    // stream it can create.
     old_available_incoming_streams--;
     EXPECT_EQ(old_available_incoming_streams,
               stream_id_manager_->available_incoming_streams());
@@ -548,27 +579,31 @@
     stream_id += kV99StreamIdIncrement;
   }
 
-  // Now close them, still should get no MAX_STREAM_ID
-  stream_count = stream_id_manager_->max_stream_id_window();
-  stream_id = GetParam() ? GetNthServerInitiatedBidirectionalId(0)
-                         : GetNthServerInitiatedUnidirectionalId(0);
+  // Now close them, still should get no MAX_STREAMS
+  stream_count = stream_id_manager_->max_streams_window();
+  stream_id = IsBidi() ? GetNthServerInitiatedBidirectionalId(0)
+                       : GetNthServerInitiatedUnidirectionalId(0);
+  QuicStreamCount expected_actual_max =
+      stream_id_manager_->incoming_actual_max_streams();
+  QuicStreamCount expected_advertised_max_streams =
+      stream_id_manager_->incoming_advertised_max_streams();
   while (stream_count) {
     stream_id_manager_->OnStreamClosed(stream_id);
     stream_count--;
     stream_id += kV99StreamIdIncrement;
-    expected_actual_max_id += kV99StreamIdIncrement;
-    EXPECT_EQ(expected_actual_max_id,
-              stream_id_manager_->actual_max_allowed_incoming_stream_id());
+    expected_actual_max++;
+    EXPECT_EQ(expected_actual_max,
+              stream_id_manager_->incoming_actual_max_streams());
     // Advertised maximum should remain the same.
-    EXPECT_EQ(advertised_max,
-              stream_id_manager_->advertised_max_allowed_incoming_stream_id());
+    EXPECT_EQ(expected_advertised_max_streams,
+              stream_id_manager_->incoming_advertised_max_streams());
   }
 
   // This should not change.
   EXPECT_EQ(old_available_incoming_streams,
             stream_id_manager_->available_incoming_streams());
 
-  // Now whenever we close a stream we should get a MAX_STREAM_ID frame.
+  // Now whenever we close a stream we should get a MAX_STREAMS frame.
   // Above code closed all the open streams, so we have to open/close
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .Times(1)
@@ -577,37 +612,74 @@
   stream_id_manager_->OnStreamClosed(stream_id);
   stream_id += kV99StreamIdIncrement;
 
-  // Check that the MAX STREAM ID was sent and has the correct values.
-  EXPECT_EQ(MAX_STREAM_ID_FRAME, session_->save_frame().type);
-  EXPECT_EQ(stream_id_manager_->advertised_max_allowed_incoming_stream_id(),
-            session_->save_frame().max_stream_id_frame.max_stream_id);
+  // Check that the MAX STREAMS was sent and has the correct values.
+  EXPECT_EQ(MAX_STREAMS_FRAME, session_->save_frame().type);
+  EXPECT_EQ(stream_id_manager_->incoming_advertised_max_streams(),
+            session_->save_frame().max_streams_frame.stream_count);
 }
 
-// Test that registering static stream IDs causes the stream ID limit to rise
+// Test that registering static stream IDs causes the stream limit to rise
 // accordingly. This is server/client agnostic.
 TEST_P(QuicStreamIdManagerTestClient, TestStaticStreamAdjustment) {
   QuicStreamId first_dynamic =
-      stream_id_manager_->first_incoming_dynamic_stream_id();
-  QuicStreamId expected_max_incoming =
-      stream_id_manager_->actual_max_allowed_incoming_stream_id();
+      QuicStreamIdManagerPeer::GetFirstIncomingStreamId(stream_id_manager_);
+  QuicStreamCount actual_max =
+      stream_id_manager_->incoming_actual_max_streams();
 
   // First test will register the first dynamic stream id as being for a static
-  // stream.  This takes one stream ID out of the low-end of the dynamic range
-  // so therefore the high end should go up by 1 ID.
-  expected_max_incoming += kV99StreamIdIncrement;
+  // stream.
   stream_id_manager_->RegisterStaticStream(first_dynamic);
-  EXPECT_EQ(expected_max_incoming,
-            stream_id_manager_->actual_max_allowed_incoming_stream_id());
+  // Should go up by 1 stream/stream id.
+  EXPECT_EQ(actual_max + 1, stream_id_manager_->incoming_actual_max_streams());
+}
 
-  // Now be extreme, increase static by 100 stream ids.  A discontinuous
-  // jump is not allowed; make sure.
-  first_dynamic += kV99StreamIdIncrement * 100;
-  expected_max_incoming += kV99StreamIdIncrement * 100;
-  std::string bug_detail =
-      GetParam() ? "allocate 5 got 401" : "allocate 7 got 403";
-  EXPECT_QUIC_BUG(
-      stream_id_manager_->RegisterStaticStream(first_dynamic),
-      "Error in incoming static stream allocation, expected to " + bug_detail);
+// Check that the OnMaxStreamFrame logic properly handles all the
+// cases of offered max streams and outgoing_static_stream_count_,
+// checking for the wrap conditions. Tests in client perspective, necessary
+// because internally, some calculations depend on the client/server
+// perspective.
+TEST_P(QuicStreamIdManagerTestClient, TestMaxStreamsWrapChecks) {
+  QuicStreamCount max_stream_count =
+      QuicUtils::GetMaxStreamCount(IsUnidi(), Perspective::IS_CLIENT);
+  QuicMaxStreamsFrame frame;
+  frame.unidirectional = IsUnidi();
+
+  // Check the case where the offered stream count is less than the
+  // maximum
+  frame.stream_count = max_stream_count - 10;
+  EXPECT_TRUE(stream_id_manager_->OnMaxStreamsFrame(frame));
+  EXPECT_EQ(max_stream_count - 10, stream_id_manager_->outgoing_max_streams());
+
+  // Now check if the offered count is larger than the max.
+  // The count should be pegged at the max.
+  frame.stream_count = max_stream_count + 10;
+  EXPECT_TRUE(stream_id_manager_->OnMaxStreamsFrame(frame));
+  EXPECT_EQ(max_stream_count, stream_id_manager_->outgoing_max_streams());
+}
+
+// Check that edge conditions of the stream count in a STREAMS_BLOCKED frame
+// are. properly handled.
+TEST_P(QuicStreamIdManagerTestClient, StreamsBlockedEdgeConditions) {
+  QuicStreamsBlockedFrame frame;
+  frame.unidirectional = IsUnidi();
+
+  // Check that receipt of a STREAMS BLOCKED with stream-count = 0 does nothing
+  // when max_allowed_incoming_streams is 0.
+  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  stream_id_manager_->SetMaxOpenIncomingStreams(0);
+  frame.stream_count = 0;
+  stream_id_manager_->OnStreamsBlockedFrame(frame);
+
+  // Check that receipt of a STREAMS BLOCKED with stream-count = 0 invokes a
+  // MAX STREAMS, count = 123, when the MaxOpen... is set to 123.
+  EXPECT_CALL(*connection_, SendControlFrame(_))
+      .Times(1)
+      .WillOnce(Invoke(session_.get(), &TestQuicSession::SaveFrame));
+  stream_id_manager_->SetMaxOpenIncomingStreams(123);
+  frame.stream_count = 0;
+  stream_id_manager_->OnStreamsBlockedFrame(frame);
+  EXPECT_EQ(MAX_STREAMS_FRAME, session_->save_frame().type);
+  EXPECT_EQ(123u, session_->save_frame().max_streams_frame.stream_count);
 }
 
 // Following tests all are server-specific. They depend, in some way, on
@@ -626,42 +698,13 @@
 TEST_P(QuicStreamIdManagerTestServer, CheckMaxAllowedOutgoing) {
   const size_t kIncomingStreamCount = 123;
   stream_id_manager_->SetMaxOpenOutgoingStreams(kIncomingStreamCount);
-  EXPECT_EQ(kIncomingStreamCount,
-            stream_id_manager_->max_allowed_outgoing_streams());
-
-  // Check that the max outgoing stream id is properly calculated
-  EXPECT_EQ(stream_id_manager_->GetNextOutgoingStreamId() +
-                ((kIncomingStreamCount - 1) * kV99StreamIdIncrement),
-            stream_id_manager_->max_allowed_outgoing_stream_id());
+  EXPECT_EQ(kIncomingStreamCount, stream_id_manager_->outgoing_max_streams());
 }
 
-// This test checks that the initialization for the maximum allowed incoming
-// stream id is correct.
-TEST_P(QuicStreamIdManagerTestServer, CheckMaxAllowedIncoming) {
-  const size_t kIncomingStreamCount = 245;
-  stream_id_manager_->SetMaxOpenIncomingStreams(kIncomingStreamCount);
-  EXPECT_EQ(kIncomingStreamCount,
-            stream_id_manager_->max_allowed_incoming_streams());
-
-  // Check that the window is 1/2 (integer math) of the stream count.
-  EXPECT_EQ((kIncomingStreamCount / 2),
-            stream_id_manager_->max_stream_id_window());
-
-  // Actual- and advertised- maxima start out equal.
-  EXPECT_EQ(stream_id_manager_->actual_max_allowed_incoming_stream_id(),
-            stream_id_manager_->advertised_max_allowed_incoming_stream_id());
-
-  // First stream ID the client should use should be 3, this means that the max
-  // stream id is 491 -- ((number of stream ids-1) * 2) + first available id.
-  EXPECT_EQ(stream_id_manager_->first_incoming_dynamic_stream_id() +
-                ((kIncomingStreamCount - 1) * kV99StreamIdIncrement),
-            stream_id_manager_->actual_max_allowed_incoming_stream_id());
-}
-
-// Test that a MAX_STREAM_ID frame is generated when half the stream ids become
+// Test that a MAX_STREAMS frame is generated when half the stream ids become
 // available. This has a useful side effect of testing that when streams are
 // closed, the number of available stream ids increases.
-TEST_P(QuicStreamIdManagerTestServer, MaxStreamIdSlidingWindow) {
+TEST_P(QuicStreamIdManagerTestServer, MaxStreamsSlidingWindow) {
   // Ignore OnStreamReset calls.
   EXPECT_CALL(*connection_, OnStreamReset(_, _)).WillRepeatedly(Return());
   // Capture control frames for analysis.
@@ -669,16 +712,17 @@
       .WillRepeatedly(Invoke(session_.get(), &TestQuicSession::SaveFrame));
   // Simulate config being negotiated, causing the limits all to be initialized.
   session_->OnConfigNegotiated();
-  QuicStreamId first_advert =
-      stream_id_manager_->advertised_max_allowed_incoming_stream_id();
+  QuicStreamCount first_advert =
+      stream_id_manager_->incoming_advertised_max_streams();
 
-  // Open/close enough streams to shrink the window without causing a MAX STREAM
-  // ID to be generated. The window will open (and a MAX STREAM ID generated)
-  // when max_stream_id_window() stream IDs have been made available. The loop
+  // Open/close enough streams to shrink the window without causing a MAX
+  // STREAMS to be generated. The window will open (and a MAX STREAMS generated)
+  // when max_streams_window() stream IDs have been made available. The loop
   // will make that many stream IDs available, so the last CloseStream should
-  // cause a MAX STREAM ID frame to be generated.
-  int i = static_cast<int>(stream_id_manager_->max_stream_id_window());
-  QuicStreamId id = stream_id_manager_->first_incoming_dynamic_stream_id();
+  // cause a MAX STREAMS frame to be generated.
+  int i = static_cast<int>(stream_id_manager_->max_streams_window());
+  QuicStreamId id =
+      QuicStreamIdManagerPeer::GetFirstIncomingStreamId(stream_id_manager_);
   while (i) {
     QuicStream* stream = session_->GetOrCreateStream(id);
     EXPECT_NE(nullptr, stream);
@@ -687,10 +731,10 @@
     // to the stream being added to the locally_closed_streams_highest_offset_
     // map, and therefore not counting as truly being closed. The test requires
     // that the stream truly close, so that new streams become available,
-    // causing the MAX_STREAM_ID to be sent.
+    // causing the MAX_STREAMS to be sent.
     stream->set_fin_received(true);
     EXPECT_EQ(id, stream->id());
-    if (GetParam()) {
+    if (IsBidi()) {
       // Only send reset for incoming bidirectional streams.
       EXPECT_CALL(*session_, SendRstStream(_, _, _));
     }
@@ -698,147 +742,111 @@
     i--;
     id += kV99StreamIdIncrement;
   }
-  EXPECT_EQ(MAX_STREAM_ID_FRAME, session_->save_frame().type);
-  QuicStreamId second_advert =
-      session_->save_frame().max_stream_id_frame.max_stream_id;
-  EXPECT_EQ(first_advert + (stream_id_manager_->max_stream_id_window() *
-                            kV99StreamIdIncrement),
+  EXPECT_EQ(MAX_STREAMS_FRAME, session_->save_frame().type);
+  QuicStreamCount second_advert =
+      session_->save_frame().max_streams_frame.stream_count;
+  EXPECT_EQ(first_advert + stream_id_manager_->max_streams_window(),
             second_advert);
 }
 
 // Tast that an attempt to create an outgoing stream does not exceed the limit
-// and that it generates an appropriate STREAM_ID_BLOCKED frame.
+// and that it generates an appropriate STREAMS_BLOCKED frame.
 TEST_P(QuicStreamIdManagerTestServer, NewStreamDoesNotExceedLimit) {
-  size_t stream_count = stream_id_manager_->max_allowed_outgoing_streams();
+  size_t stream_count = stream_id_manager_->outgoing_max_streams();
   EXPECT_NE(0u, stream_count);
   TestQuicStream* stream;
   while (stream_count) {
-    stream = GetParam() ? session_->CreateOutgoingBidirectionalStream()
-                        : session_->CreateOutgoingUnidirectionalStream();
+    stream = IsBidi() ? session_->CreateOutgoingBidirectionalStream()
+                      : session_->CreateOutgoingUnidirectionalStream();
     EXPECT_NE(stream, nullptr);
     stream_count--;
   }
-  // Quis Custodiet Ipsos Custodes.
-  EXPECT_EQ(stream->id(), stream_id_manager_->max_allowed_outgoing_stream_id());
-  // Create another, it should fail. Should also send a STREAM_ID_BLOCKED
+
+  EXPECT_EQ(stream_id_manager_->outgoing_stream_count(),
+            stream_id_manager_->outgoing_max_streams());
+  // Create another, it should fail. Should also send a STREAMS_BLOCKED
   // control frame.
   EXPECT_CALL(*connection_, SendControlFrame(_));
-  stream = GetParam() ? session_->CreateOutgoingBidirectionalStream()
-                      : session_->CreateOutgoingUnidirectionalStream();
+  stream = IsBidi() ? session_->CreateOutgoingBidirectionalStream()
+                    : session_->CreateOutgoingUnidirectionalStream();
   EXPECT_EQ(nullptr, stream);
 }
 
-// Test that a server will reject a MAX_STREAM_ID that specifies a
-// client-initiated stream ID.
-TEST_P(QuicStreamIdManagerTestServer, RejectClientMaxStreamId) {
-  QuicStreamId id = stream_id_manager_->max_allowed_outgoing_stream_id();
-
-  // Ensure that the ID that will be in the MAX_STREAM_ID is larger than the
-  // current MAX.
-  id += (kV99StreamIdIncrement * 2);
-
-  // Turn it into a client-initiated ID (even).
-  id &= ~0x1;
-  EXPECT_TRUE(QuicUtils::IsClientInitiatedStreamId(QUIC_VERSION_99, id));
-
-  // Generate a MAX_STREAM_ID frame and process it; the connection should close.
-  QuicMaxStreamIdFrame frame(0, id);
-  EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAM_ID_ERROR, _, _));
-  session_->OnMaxStreamIdFrame(frame);
-}
-
-// Test that a server will reject a STREAM_ID_BLOCKED that specifies a
-// server-initiated stream ID. STREAM_ID_BLOCKED from a client should specify an
-// even (client-initiated_ ID) generate one with an odd ID and check that the
-// connection is closed.
-TEST_P(QuicStreamIdManagerTestServer, RejectClientStreamIdBlocked) {
-  QuicStreamId id = stream_id_manager_->max_allowed_outgoing_stream_id();
-
-  // Ensure that the ID that will be in the MAX_STREAM_ID is larger than the
-  // current MAX.
-  id += (kV99StreamIdIncrement * 2);
-
-  // Make the ID odd, so it looks like the client is trying to specify a
-  // server-initiated ID.
-  id |= 0x1;
-  EXPECT_FALSE(QuicUtils::IsClientInitiatedStreamId(QUIC_VERSION_99, id));
-
-  // Generate a STREAM_ID_BLOCKED frame and process it; the connection should
-  // close.
-  QuicStreamIdBlockedFrame frame(0, id);
-  EXPECT_CALL(*connection_,
-              CloseConnection(QUIC_STREAM_ID_BLOCKED_ERROR, _, _));
-  session_->OnStreamIdBlockedFrame(frame);
-}
-
 // Check that the parameters used by the stream ID manager are properly
 // initialized
 TEST_P(QuicStreamIdManagerTestServer, StreamIdManagerServerInitialization) {
   // These fields are inited via the QuicSession constructor to default
   // values defined as a constant.
   EXPECT_EQ(kDefaultMaxStreamsPerConnection,
-            stream_id_manager_->max_allowed_incoming_streams());
+            stream_id_manager_->incoming_initial_max_open_streams());
+  // If bidi, Crypto stream default created  at start up, it is one
+  // more stream to account for since initialization is "number of
+  // request/responses" & crypto is added in to that, not streams.
+  // Since this is the server, the stream is incoming.
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection + (IsBidi() ? 1 : 0),
+            stream_id_manager_->incoming_actual_max_streams());
   EXPECT_EQ(kDefaultMaxStreamsPerConnection,
-            stream_id_manager_->max_allowed_outgoing_streams());
+            stream_id_manager_->outgoing_max_streams());
 
   // The window for advertising updates to the MAX STREAM ID is half the number
   // of stream allowed.
-  EXPECT_EQ(kDefaultMaxStreamsPerConnection / kMaxStreamIdWindowDivisor,
-            stream_id_manager_->max_stream_id_window());
-
-  // This test runs as a server, so it initiates (that is to say, outgoing)
-  // even-numbered stream IDs. The -1 in the calculation is because the value
-  // being tested is the maximum allowed stream ID, not the first unallowed
-  // stream id.
-  const QuicStreamId kExpectedMaxOutgoingStreamId =
-      (GetParam() ? session_->next_outgoing_bidirectional_stream_id()
-                  : session_->next_outgoing_unidirectional_stream_id()) +
-      ((kDefaultMaxStreamsPerConnection - 1) * kV99StreamIdIncrement);
-  EXPECT_EQ(kExpectedMaxOutgoingStreamId,
-            stream_id_manager_->max_allowed_outgoing_stream_id());
-
-  // Same for IDs of incoming streams... But they are client initiated, so are
-  // even.
-  const QuicStreamId kExpectedMaxIncomingStreamId =
-      GetParam() ? GetNthClientInitiatedBidirectionalId(
-                       kDefaultMaxStreamsPerConnection - 1)
-                 : GetNthClientInitiatedUnidirectionalId(
-                       kDefaultMaxStreamsPerConnection - 1);
-  EXPECT_EQ(kExpectedMaxIncomingStreamId,
-            stream_id_manager_->actual_max_allowed_incoming_stream_id());
-  EXPECT_EQ(kExpectedMaxIncomingStreamId,
-            stream_id_manager_->advertised_max_allowed_incoming_stream_id());
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection / kMaxStreamsWindowDivisor,
+            stream_id_manager_->max_streams_window());
 }
 
 TEST_P(QuicStreamIdManagerTestServer, AvailableStreams) {
   stream_id_manager_->MaybeIncreaseLargestPeerStreamId(
-      GetParam() ? GetNthClientInitiatedBidirectionalId(3)
-                 : GetNthClientInitiatedUnidirectionalId(3));
+      IsBidi() ? GetNthClientInitiatedBidirectionalId(3)
+               : GetNthClientInitiatedUnidirectionalId(3));
   EXPECT_TRUE(stream_id_manager_->IsAvailableStream(
-      GetParam() ? GetNthClientInitiatedBidirectionalId(1)
-                 : GetNthClientInitiatedUnidirectionalId(1)));
+      IsBidi() ? GetNthClientInitiatedBidirectionalId(1)
+               : GetNthClientInitiatedUnidirectionalId(1)));
   EXPECT_TRUE(stream_id_manager_->IsAvailableStream(
-      GetParam() ? GetNthClientInitiatedBidirectionalId(2)
-                 : GetNthClientInitiatedUnidirectionalId(2)));
+      IsBidi() ? GetNthClientInitiatedBidirectionalId(2)
+               : GetNthClientInitiatedUnidirectionalId(2)));
 }
 
 // Tests that if MaybeIncreaseLargestPeerStreamId is given an extremely
 // large stream ID (larger than the limit) it is rejected.
 // This is a regression for Chromium bugs 909987 and 910040
 TEST_P(QuicStreamIdManagerTestServer, ExtremeMaybeIncreaseLargestPeerStreamId) {
-  QuicStreamId too_big_stream_id =
-      stream_id_manager_->actual_max_allowed_incoming_stream_id() +
-      kV99StreamIdIncrement * 20;
-
+  QuicStreamId too_big_stream_id = StreamCountToId(
+      stream_id_manager_->incoming_actual_max_streams() + 20,
+      Perspective::IS_CLIENT);  // This node is a server, incoming stream
+                                // ids must be client-originated.
   std::string error_details =
-      GetParam() ? "Stream id 480 above 400" : "Stream id 478 above 398";
+      IsBidi() ? "Stream id 480 would exceed stream count limit 101"
+               : "Stream id 478 would exceed stream count limit 100";
   EXPECT_CALL(*connection_,
               CloseConnection(QUIC_INVALID_STREAM_ID, error_details, _));
-
   EXPECT_FALSE(
       stream_id_manager_->MaybeIncreaseLargestPeerStreamId(too_big_stream_id));
 }
 
+// Check that the OnMaxStreamFrame logic properly handles all the
+// cases of offered max streams and outgoing_static_stream_count_,
+// checking for the wrap conditions. Tests in server perspective, necessary
+// because internally, some calculations depend on the client/server
+// perspective.
+TEST_P(QuicStreamIdManagerTestServer, TestMaxStreamsWrapChecks) {
+  QuicStreamCount max_stream_count =
+      QuicUtils::GetMaxStreamCount(IsUnidi(), Perspective::IS_SERVER);
+  QuicMaxStreamsFrame frame;
+  frame.unidirectional = IsUnidi();
+
+  // Check the case where the offered stream count is less than the
+  // implementation maximum,
+  frame.stream_count = max_stream_count - 10;
+  EXPECT_TRUE(stream_id_manager_->OnMaxStreamsFrame(frame));
+  EXPECT_EQ(max_stream_count - 10, stream_id_manager_->outgoing_max_streams());
+
+  // Check the case where the offered stream count is greater than the
+  // implementation maximum. The count should peg at the maximum.
+  frame.stream_count = max_stream_count + 10;
+  EXPECT_TRUE(stream_id_manager_->OnMaxStreamsFrame(frame));
+  EXPECT_EQ(max_stream_count, stream_id_manager_->outgoing_max_streams());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_trace_visitor.cc b/quic/core/quic_trace_visitor.cc
index 5fdab2d..5e152ea 100644
--- a/quic/core/quic_trace_visitor.cc
+++ b/quic/core/quic_trace_visitor.cc
@@ -78,8 +78,8 @@
       // New IETF frames, not used in current gQUIC version.
       case NEW_CONNECTION_ID_FRAME:
       case RETIRE_CONNECTION_ID_FRAME:
-      case MAX_STREAM_ID_FRAME:
-      case STREAM_ID_BLOCKED_FRAME:
+      case MAX_STREAMS_FRAME:
+      case STREAMS_BLOCKED_FRAME:
       case PATH_RESPONSE_FRAME:
       case PATH_CHALLENGE_FRAME:
       case STOP_SENDING_FRAME:
@@ -208,8 +208,8 @@
     // New IETF frames, not used in current gQUIC version.
     case NEW_CONNECTION_ID_FRAME:
     case RETIRE_CONNECTION_ID_FRAME:
-    case MAX_STREAM_ID_FRAME:
-    case STREAM_ID_BLOCKED_FRAME:
+    case MAX_STREAMS_FRAME:
+    case STREAMS_BLOCKED_FRAME:
     case PATH_RESPONSE_FRAME:
     case PATH_CHALLENGE_FRAME:
     case STOP_SENDING_FRAME:
diff --git a/quic/core/quic_types.h b/quic/core/quic_types.h
index 1b22967..9b114f1 100644
--- a/quic/core/quic_types.h
+++ b/quic/core/quic_types.h
@@ -27,6 +27,10 @@
 // TODO(fkastenholz): Should update this to 64 bits for V99.
 typedef uint32_t QuicStreamId;
 
+// Count of stream IDs. Used in MAX_STREAMS and STREAMS_BLOCKED
+// frames.
+typedef uint32_t QuicStreamCount;
+
 typedef uint64_t QuicByteCount;
 typedef uint64_t QuicPacketCount;
 typedef uint64_t QuicPublicResetNonceProof;
@@ -198,8 +202,8 @@
   // QUIC has been negotiated. Values are not important, they are not
   // the values that are in the packets (see QuicIetfFrameType, below).
   NEW_CONNECTION_ID_FRAME,
-  MAX_STREAM_ID_FRAME,
-  STREAM_ID_BLOCKED_FRAME,
+  MAX_STREAMS_FRAME,
+  STREAMS_BLOCKED_FRAME,
   PATH_RESPONSE_FRAME,
   PATH_CHALLENGE_FRAME,
   STOP_SENDING_FRAME,
diff --git a/quic/core/quic_utils.cc b/quic/core/quic_utils.cc
index 413a244..e1caf89 100644
--- a/quic/core/quic_utils.cc
+++ b/quic/core/quic_utils.cc
@@ -543,6 +543,24 @@
       QuicEndian::NetToHost64(data_bytes[0]));
 }
 
+// Returns the maximum value that a stream count may have, taking into account
+// the fact that bidirectional, client initiated, streams have one fewer stream
+// available than the others. This is because the old crypto streams, with ID ==
+// 0 are not included in the count.
+// The version is not included in the call, nor does the method take the version
+// into account, because this is called only from code used for IETF QUIC.
+// TODO(fkastenholz): Remove this method and replace calls to it with direct
+// references to kMaxQuicStreamIdCount when streamid 0 becomes a normal stream
+// id.
+// static
+QuicStreamCount QuicUtils::GetMaxStreamCount(bool unidirectional,
+                                             Perspective perspective) {
+  if (!unidirectional && perspective == Perspective::IS_CLIENT) {
+    return kMaxQuicStreamCount >> 2;
+  }
+  return (kMaxQuicStreamCount >> 2) + 1;
+}
+
 // static
 PacketNumberSpace QuicUtils::GetPacketNumberSpace(
     EncryptionLevel encryption_level) {
diff --git a/quic/core/quic_utils.h b/quic/core/quic_utils.h
index cacbc60..befeee7 100644
--- a/quic/core/quic_utils.h
+++ b/quic/core/quic_utils.h
@@ -190,6 +190,12 @@
   // Determines encryption level to send packets in |packet_number_space|.
   static EncryptionLevel GetEncryptionLevel(
       PacketNumberSpace packet_number_space);
+
+  // Get the maximum value for a V99/IETF QUIC stream count. If a count
+  // exceeds this value, it will result in a stream ID that exceeds the
+  // implementation limit on stream ID size.
+  static QuicStreamCount GetMaxStreamCount(bool unidirectional,
+                                           Perspective perspective);
 };
 
 }  // namespace quic
diff --git a/quic/core/uber_quic_stream_id_manager.cc b/quic/core/uber_quic_stream_id_manager.cc
index 51ea994..63abc3f 100644
--- a/quic/core/uber_quic_stream_id_manager.cc
+++ b/quic/core/uber_quic_stream_id_manager.cc
@@ -19,35 +19,16 @@
 
 UberQuicStreamIdManager::UberQuicStreamIdManager(
     QuicSession* session,
-    size_t max_open_outgoing_streams,
-    size_t max_open_incoming_streams)
-    : bidirectional_stream_id_manager_(
-          session,
-          QuicUtils::GetFirstBidirectionalStreamId(
-              session->connection()->transport_version(),
-              session->perspective()),
-          session->perspective() == Perspective::IS_SERVER
-              ? QuicUtils::GetCryptoStreamId(
-                    session->connection()->transport_version())
-              : QuicUtils::GetInvalidStreamId(
-                    session->connection()->transport_version()),
-          QuicUtils::GetFirstBidirectionalStreamId(
-              session->connection()->transport_version(),
-              Reverse(session->perspective())),
-          max_open_outgoing_streams,
-          max_open_incoming_streams),
-      unidirectional_stream_id_manager_(
-          session,
-          QuicUtils::GetFirstUnidirectionalStreamId(
-              session->connection()->transport_version(),
-              session->perspective()),
-          QuicUtils::GetInvalidStreamId(
-              session->connection()->transport_version()),
-          QuicUtils::GetFirstUnidirectionalStreamId(
-              session->connection()->transport_version(),
-              Reverse(session->perspective())),
-          max_open_outgoing_streams,
-          max_open_incoming_streams) {}
+    QuicStreamCount max_open_outgoing_streams,
+    QuicStreamCount max_open_incoming_streams)
+    : bidirectional_stream_id_manager_(session,
+                                       /*unidirectional=*/false,
+                                       max_open_outgoing_streams,
+                                       max_open_incoming_streams),
+      unidirectional_stream_id_manager_(session,
+                                        /*unidirectional=*/true,
+                                        max_open_outgoing_streams,
+                                        max_open_incoming_streams) {}
 
 void UberQuicStreamIdManager::RegisterStaticStream(QuicStreamId id) {
   if (QuicUtils::IsBidirectionalStreamId(id)) {
@@ -57,14 +38,36 @@
   unidirectional_stream_id_manager_.RegisterStaticStream(id);
 }
 
-void UberQuicStreamIdManager::SetMaxOpenOutgoingStreams(size_t max_streams) {
-  bidirectional_stream_id_manager_.SetMaxOpenOutgoingStreams(max_streams);
-  unidirectional_stream_id_manager_.SetMaxOpenOutgoingStreams(max_streams);
+void UberQuicStreamIdManager::ConfigureMaxOpenOutgoingStreams(
+    size_t max_streams) {
+  // TODO(fkastenholz): When transport configuration negotiation knows uni- vs
+  // bi- directionality, this method needs modifying to select the correct
+  // manager to configure.
+  bidirectional_stream_id_manager_.ConfigureMaxOpenOutgoingStreams(max_streams);
+  unidirectional_stream_id_manager_.ConfigureMaxOpenOutgoingStreams(
+      max_streams);
 }
 
-void UberQuicStreamIdManager::SetMaxOpenIncomingStreams(size_t max_streams) {
-  bidirectional_stream_id_manager_.SetMaxOpenIncomingStreams(max_streams);
-  unidirectional_stream_id_manager_.SetMaxOpenIncomingStreams(max_streams);
+void UberQuicStreamIdManager::AdjustMaxOpenOutgoingStreams(size_t max_streams) {
+  // TODO(fkastenholz): When transport configuration negotiation knows uni- vs
+  // bi- directionality, this method needs modifying to select the correct
+  // manager to configure.
+  bidirectional_stream_id_manager_.AdjustMaxOpenOutgoingStreams(max_streams);
+  unidirectional_stream_id_manager_.AdjustMaxOpenOutgoingStreams(max_streams);
+}
+
+// TODO(fkastenholz): SetMax is cognizant of the number of static streams and
+// sets the maximum to be max_streams + number_of_statics. This should
+// eventually be removed from IETF QUIC.
+void UberQuicStreamIdManager::SetMaxOpenOutgoingStreams(
+    size_t max_open_streams) {
+  bidirectional_stream_id_manager_.SetMaxOpenOutgoingStreams(max_open_streams);
+  unidirectional_stream_id_manager_.SetMaxOpenOutgoingStreams(max_open_streams);
+}
+void UberQuicStreamIdManager::SetMaxOpenIncomingStreams(
+    size_t max_open_streams) {
+  bidirectional_stream_id_manager_.SetMaxOpenIncomingStreams(max_open_streams);
+  unidirectional_stream_id_manager_.SetMaxOpenIncomingStreams(max_open_streams);
 }
 
 bool UberQuicStreamIdManager::CanOpenNextOutgoingBidirectionalStream() {
@@ -100,20 +103,20 @@
   unidirectional_stream_id_manager_.OnStreamClosed(id);
 }
 
-bool UberQuicStreamIdManager::OnMaxStreamIdFrame(
-    const QuicMaxStreamIdFrame& frame) {
-  if (QuicUtils::IsBidirectionalStreamId(frame.max_stream_id)) {
-    return bidirectional_stream_id_manager_.OnMaxStreamIdFrame(frame);
+bool UberQuicStreamIdManager::OnMaxStreamsFrame(
+    const QuicMaxStreamsFrame& frame) {
+  if (frame.unidirectional) {
+    return unidirectional_stream_id_manager_.OnMaxStreamsFrame(frame);
   }
-  return unidirectional_stream_id_manager_.OnMaxStreamIdFrame(frame);
+  return bidirectional_stream_id_manager_.OnMaxStreamsFrame(frame);
 }
 
-bool UberQuicStreamIdManager::OnStreamIdBlockedFrame(
-    const QuicStreamIdBlockedFrame& frame) {
-  if (QuicUtils::IsBidirectionalStreamId(frame.stream_id)) {
-    return bidirectional_stream_id_manager_.OnStreamIdBlockedFrame(frame);
+bool UberQuicStreamIdManager::OnStreamsBlockedFrame(
+    const QuicStreamsBlockedFrame& frame) {
+  if (frame.unidirectional) {
+    return unidirectional_stream_id_manager_.OnStreamsBlockedFrame(frame);
   }
-  return unidirectional_stream_id_manager_.OnStreamIdBlockedFrame(frame);
+  return bidirectional_stream_id_manager_.OnStreamsBlockedFrame(frame);
 }
 
 bool UberQuicStreamIdManager::IsIncomingStream(QuicStreamId id) const {
@@ -132,12 +135,12 @@
 
 size_t UberQuicStreamIdManager::GetMaxAllowdIncomingBidirectionalStreams()
     const {
-  return bidirectional_stream_id_manager_.max_allowed_incoming_streams();
+  return bidirectional_stream_id_manager_.incoming_initial_max_open_streams();
 }
 
 size_t UberQuicStreamIdManager::GetMaxAllowdIncomingUnidirectionalStreams()
     const {
-  return unidirectional_stream_id_manager_.max_allowed_incoming_streams();
+  return unidirectional_stream_id_manager_.incoming_initial_max_open_streams();
 }
 
 void UberQuicStreamIdManager::SetLargestPeerCreatedStreamId(
@@ -161,50 +164,37 @@
   return unidirectional_stream_id_manager_.next_outgoing_stream_id();
 }
 
-QuicStreamId
-UberQuicStreamIdManager::max_allowed_outgoing_bidirectional_stream_id() const {
-  return bidirectional_stream_id_manager_.max_allowed_outgoing_stream_id();
-}
-
-QuicStreamId
-UberQuicStreamIdManager::max_allowed_outgoing_unidirectional_stream_id() const {
-  return unidirectional_stream_id_manager_.max_allowed_outgoing_stream_id();
-}
-
 size_t UberQuicStreamIdManager::max_allowed_outgoing_bidirectional_streams()
     const {
-  return bidirectional_stream_id_manager_.max_allowed_outgoing_streams();
+  return bidirectional_stream_id_manager_.outgoing_max_streams();
 }
 
 size_t UberQuicStreamIdManager::max_allowed_outgoing_unidirectional_streams()
     const {
-  return unidirectional_stream_id_manager_.max_allowed_outgoing_streams();
+  return unidirectional_stream_id_manager_.outgoing_max_streams();
 }
 
-QuicStreamId
-UberQuicStreamIdManager::actual_max_allowed_incoming_bidirectional_stream_id()
+QuicStreamCount
+UberQuicStreamIdManager::actual_max_allowed_incoming_bidirectional_streams()
     const {
-  return bidirectional_stream_id_manager_
-      .actual_max_allowed_incoming_stream_id();
+  return bidirectional_stream_id_manager_.incoming_actual_max_streams();
 }
 
-QuicStreamId
-UberQuicStreamIdManager::actual_max_allowed_incoming_unidirectional_stream_id()
+QuicStreamCount
+UberQuicStreamIdManager::actual_max_allowed_incoming_unidirectional_streams()
     const {
-  return unidirectional_stream_id_manager_
-      .actual_max_allowed_incoming_stream_id();
+  return unidirectional_stream_id_manager_.incoming_actual_max_streams();
 }
 
-QuicStreamId UberQuicStreamIdManager::
-    advertised_max_allowed_incoming_bidirectional_stream_id() const {
-  return bidirectional_stream_id_manager_
-      .advertised_max_allowed_incoming_stream_id();
+QuicStreamCount
+UberQuicStreamIdManager::advertised_max_allowed_incoming_bidirectional_streams()
+    const {
+  return bidirectional_stream_id_manager_.incoming_advertised_max_streams();
 }
 
-QuicStreamId UberQuicStreamIdManager::
-    advertised_max_allowed_incoming_unidirectional_stream_id() const {
-  return unidirectional_stream_id_manager_
-      .advertised_max_allowed_incoming_stream_id();
+QuicStreamCount UberQuicStreamIdManager::
+    advertised_max_allowed_incoming_unidirectional_streams() const {
+  return unidirectional_stream_id_manager_.incoming_advertised_max_streams();
 }
 
 }  // namespace quic
diff --git a/quic/core/uber_quic_stream_id_manager.h b/quic/core/uber_quic_stream_id_manager.h
index bf5a588..41fa12b 100644
--- a/quic/core/uber_quic_stream_id_manager.h
+++ b/quic/core/uber_quic_stream_id_manager.h
@@ -11,6 +11,7 @@
 
 namespace test {
 class QuicSessionPeer;
+class UberQuicStreamIdManagerPeer;
 }  // namespace test
 
 class QuicSession;
@@ -20,18 +21,30 @@
 class QUIC_EXPORT_PRIVATE UberQuicStreamIdManager {
  public:
   UberQuicStreamIdManager(QuicSession* session,
-                          size_t max_open_outgoing_streams,
-                          size_t max_open_incoming_streams);
+                          QuicStreamCount max_open_outgoing_streams,
+                          QuicStreamCount max_open_incoming_streams);
 
   // Called when a stream with |stream_id| is registered as a static stream.
   void RegisterStaticStream(QuicStreamId id);
 
-  // Initialize the maximum allowed outgoing stream id, number of streams, and
-  // MAX_STREAM_ID advertisement window.
-  void SetMaxOpenOutgoingStreams(size_t max_streams);
+  // Sets the maximum outgoing stream count as a result of doing the transport
+  // configuration negotiation. Forces the limit to max_streams, regardless of
+  // static streams.
+  void ConfigureMaxOpenOutgoingStreams(size_t max_streams);
 
-  // Initialize the maximum allowed incoming stream id and number of streams.
-  void SetMaxOpenIncomingStreams(size_t max_streams);
+  // Sets the limits to max_open_streams + number of static streams
+  // in existence. SetMaxOpenOutgoingStreams will QUIC_BUG if it is called
+  // after getting the first MAX_STREAMS frame.
+  // TODO(fkastenholz): SetMax is cognizant of the number of static streams and
+  // sets the maximum to be max_streams + number_of_statics. This should
+  // eventually be removed from IETF QUIC.
+  void SetMaxOpenOutgoingStreams(size_t max_open_streams);
+  void SetMaxOpenIncomingStreams(size_t max_open_streams);
+
+  // Sets the outgoing stream count to the number of static streams + max
+  // outgoing streams.  Unlike SetMaxOpenOutgoingStreams, this method will
+  // not QUIC_BUG if called after getting  the first MAX_STREAMS frame.
+  void AdjustMaxOpenOutgoingStreams(size_t max_streams);
 
   // Returns true if next outgoing bidirectional stream ID can be allocated.
   bool CanOpenNextOutgoingBidirectionalStream();
@@ -51,11 +64,11 @@
   // Called when |id| is released.
   void OnStreamClosed(QuicStreamId id);
 
-  // Called when a MAX_STREAM_ID frame is received.
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame);
+  // Called when a MAX_STREAMS frame is received.
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame);
 
-  // Called when a STREAM_ID_BLOCKED frame is received.
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame);
+  // Called when a STREAMS_BLOCKED frame is received.
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame);
 
   // Return true if |id| is peer initiated.
   bool IsIncomingStream(QuicStreamId id) const;
@@ -73,20 +86,19 @@
   QuicStreamId next_outgoing_bidirectional_stream_id() const;
   QuicStreamId next_outgoing_unidirectional_stream_id() const;
 
-  QuicStreamId max_allowed_outgoing_bidirectional_stream_id() const;
-  QuicStreamId max_allowed_outgoing_unidirectional_stream_id() const;
-
   size_t max_allowed_outgoing_bidirectional_streams() const;
   size_t max_allowed_outgoing_unidirectional_streams() const;
 
-  QuicStreamId actual_max_allowed_incoming_bidirectional_stream_id() const;
-  QuicStreamId actual_max_allowed_incoming_unidirectional_stream_id() const;
+  QuicStreamCount actual_max_allowed_incoming_bidirectional_streams() const;
+  QuicStreamCount actual_max_allowed_incoming_unidirectional_streams() const;
 
-  QuicStreamId advertised_max_allowed_incoming_bidirectional_stream_id() const;
-  QuicStreamId advertised_max_allowed_incoming_unidirectional_stream_id() const;
+  QuicStreamCount advertised_max_allowed_incoming_bidirectional_streams() const;
+  QuicStreamCount advertised_max_allowed_incoming_unidirectional_streams()
+      const;
 
  private:
   friend class test::QuicSessionPeer;
+  friend class test::UberQuicStreamIdManagerPeer;
 
   // Manages stream IDs of bidirectional streams.
   QuicStreamIdManager bidirectional_stream_id_manager_;
diff --git a/quic/core/uber_quic_stream_id_manager_test.cc b/quic/core/uber_quic_stream_id_manager_test.cc
index 929d0a1..ed08d8f 100644
--- a/quic/core/uber_quic_stream_id_manager_test.cc
+++ b/quic/core/uber_quic_stream_id_manager_test.cc
@@ -7,6 +7,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.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_test_utils.h"
 
 using testing::_;
@@ -59,6 +60,16 @@
            kV99StreamIdIncrement * n;
   }
 
+  QuicStreamId StreamCountToId(QuicStreamCount stream_count,
+                               Perspective perspective,
+                               bool bidirectional) {
+    return ((bidirectional) ? QuicUtils::GetFirstBidirectionalStreamId(
+                                  QUIC_VERSION_99, perspective)
+                            : QuicUtils::GetFirstUnidirectionalStreamId(
+                                  QUIC_VERSION_99, perspective)) +
+           ((stream_count - 1) * QuicUtils::StreamIdDelta(QUIC_VERSION_99));
+  }
+
   MockQuicConnectionHelper helper_;
   MockAlarmFactory alarm_factory_;
   MockQuicConnection* connection_;
@@ -96,27 +107,24 @@
           ? GetNthClientInitiatedUnidirectionalId(0)
           : GetNthServerInitiatedUnidirectionalId(0);
 
-  QuicStreamId actual_max_allowed_incoming_bidirectional_stream_id =
-      manager_->actual_max_allowed_incoming_bidirectional_stream_id();
-  QuicStreamId actual_max_allowed_incoming_unidirectional_stream_id =
-      manager_->actual_max_allowed_incoming_unidirectional_stream_id();
+  QuicStreamCount actual_max_allowed_incoming_bidirectional_streams =
+      manager_->actual_max_allowed_incoming_bidirectional_streams();
+  QuicStreamCount actual_max_allowed_incoming_unidirectional_streams =
+      manager_->actual_max_allowed_incoming_unidirectional_streams();
   manager_->RegisterStaticStream(first_incoming_bidirectional_stream_id);
-  // Verify actual_max_allowed_incoming_bidirectional_stream_id increases.
-  EXPECT_EQ(actual_max_allowed_incoming_bidirectional_stream_id +
-                kV99StreamIdIncrement,
-            manager_->actual_max_allowed_incoming_bidirectional_stream_id());
-  // Verify actual_max_allowed_incoming_unidirectional_stream_id does not
+  // Verify actual_max_allowed_incoming_bidirectional_streams increases.
+  EXPECT_EQ(actual_max_allowed_incoming_bidirectional_streams + 1u,
+            manager_->actual_max_allowed_incoming_bidirectional_streams());
+  // Verify actual_max_allowed_incoming_unidirectional_streams does not
   // change.
-  EXPECT_EQ(actual_max_allowed_incoming_unidirectional_stream_id,
-            manager_->actual_max_allowed_incoming_unidirectional_stream_id());
+  EXPECT_EQ(actual_max_allowed_incoming_unidirectional_streams,
+            manager_->actual_max_allowed_incoming_unidirectional_streams());
 
   manager_->RegisterStaticStream(first_incoming_unidirectional_stream_id);
-  EXPECT_EQ(actual_max_allowed_incoming_bidirectional_stream_id +
-                kV99StreamIdIncrement,
-            manager_->actual_max_allowed_incoming_bidirectional_stream_id());
-  EXPECT_EQ(actual_max_allowed_incoming_unidirectional_stream_id +
-                kV99StreamIdIncrement,
-            manager_->actual_max_allowed_incoming_unidirectional_stream_id());
+  EXPECT_EQ(actual_max_allowed_incoming_bidirectional_streams + 1u,
+            manager_->actual_max_allowed_incoming_bidirectional_streams());
+  EXPECT_EQ(actual_max_allowed_incoming_unidirectional_streams + 1u,
+            manager_->actual_max_allowed_incoming_unidirectional_streams());
 }
 
 TEST_P(UberQuicStreamIdManagerTest, SetMaxOpenOutgoingStreams) {
@@ -135,27 +143,10 @@
             manager_->GetMaxAllowdIncomingBidirectionalStreams());
   EXPECT_EQ(kNumMaxIncomingStreams,
             manager_->GetMaxAllowdIncomingUnidirectionalStreams());
-  EXPECT_EQ(
-      manager_->actual_max_allowed_incoming_bidirectional_stream_id(),
-      manager_->advertised_max_allowed_incoming_bidirectional_stream_id());
-  EXPECT_EQ(
-      manager_->actual_max_allowed_incoming_unidirectional_stream_id(),
-      manager_->advertised_max_allowed_incoming_unidirectional_stream_id());
-
-  QuicStreamId first_incoming_bidirectional_stream_id =
-      GetParam() == Perspective::IS_SERVER
-          ? GetNthClientInitiatedBidirectionalId(0)
-          : GetNthServerInitiatedBidirectionalId(0);
-  QuicStreamId first_incoming_unidirectional_stream_id =
-      GetParam() == Perspective::IS_SERVER
-          ? GetNthClientInitiatedUnidirectionalId(0)
-          : GetNthServerInitiatedUnidirectionalId(0);
-  EXPECT_EQ(first_incoming_bidirectional_stream_id +
-                (kNumMaxIncomingStreams - 1) * kV99StreamIdIncrement,
-            manager_->actual_max_allowed_incoming_bidirectional_stream_id());
-  EXPECT_EQ(first_incoming_unidirectional_stream_id +
-                (kNumMaxIncomingStreams - 1) * kV99StreamIdIncrement,
-            manager_->actual_max_allowed_incoming_unidirectional_stream_id());
+  EXPECT_EQ(manager_->actual_max_allowed_incoming_bidirectional_streams(),
+            manager_->advertised_max_allowed_incoming_bidirectional_streams());
+  EXPECT_EQ(manager_->actual_max_allowed_incoming_unidirectional_streams(),
+            manager_->advertised_max_allowed_incoming_unidirectional_streams());
 }
 
 TEST_P(UberQuicStreamIdManagerTest, GetNextOutgoingStreamId) {
@@ -214,86 +205,116 @@
 
 TEST_P(UberQuicStreamIdManagerTest, MaybeIncreaseLargestPeerStreamId) {
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
-  EXPECT_TRUE(manager_->MaybeIncreaseLargestPeerStreamId(
-      manager_->actual_max_allowed_incoming_bidirectional_stream_id()));
-  EXPECT_TRUE(manager_->MaybeIncreaseLargestPeerStreamId(
-      manager_->actual_max_allowed_incoming_unidirectional_stream_id()));
+  EXPECT_TRUE(manager_->MaybeIncreaseLargestPeerStreamId(StreamCountToId(
+      manager_->actual_max_allowed_incoming_bidirectional_streams(),
+      /* Perspective=*/GetParam() == Perspective::IS_SERVER
+          ? Perspective::IS_CLIENT
+          : Perspective::IS_SERVER,
+      /* bidirectional=*/true)));
+  EXPECT_TRUE(manager_->MaybeIncreaseLargestPeerStreamId(StreamCountToId(
+      manager_->actual_max_allowed_incoming_bidirectional_streams(),
+      /* Perspective=*/GetParam() == Perspective::IS_SERVER
+          ? Perspective::IS_CLIENT
+          : Perspective::IS_SERVER,
+      /* bidirectional=*/false)));
 
-  std::string error_details = GetParam() == Perspective::IS_SERVER
-                                  ? "Stream id 404 above 400"
-                                  : "Stream id 401 above 397";
+  std::string error_details =
+      GetParam() == Perspective::IS_SERVER
+          ? "Stream id 404 would exceed stream count limit 100"
+          : "Stream id 401 would exceed stream count limit 100";
+
   EXPECT_CALL(*connection_,
               CloseConnection(QUIC_INVALID_STREAM_ID, error_details, _));
-  EXPECT_FALSE(manager_->MaybeIncreaseLargestPeerStreamId(
-      manager_->actual_max_allowed_incoming_bidirectional_stream_id() +
-      kV99StreamIdIncrement));
+  EXPECT_FALSE(manager_->MaybeIncreaseLargestPeerStreamId(StreamCountToId(
+      manager_->actual_max_allowed_incoming_bidirectional_streams() + 1,
+      /* Perspective=*/GetParam() == Perspective::IS_SERVER
+          ? Perspective::IS_CLIENT
+          : Perspective::IS_SERVER,
+      /* bidirectional=*/true)));
   error_details = GetParam() == Perspective::IS_SERVER
-                      ? "Stream id 402 above 398"
-                      : "Stream id 403 above 399";
+                      ? "Stream id 402 would exceed stream count limit 100"
+                      : "Stream id 403 would exceed stream count limit 100";
   EXPECT_CALL(*connection_,
               CloseConnection(QUIC_INVALID_STREAM_ID, error_details, _));
-  EXPECT_FALSE(manager_->MaybeIncreaseLargestPeerStreamId(
-      manager_->actual_max_allowed_incoming_unidirectional_stream_id() +
-      kV99StreamIdIncrement));
+  EXPECT_FALSE(manager_->MaybeIncreaseLargestPeerStreamId(StreamCountToId(
+      manager_->actual_max_allowed_incoming_bidirectional_streams() + 1,
+      /* Perspective=*/GetParam() == Perspective::IS_SERVER
+          ? Perspective::IS_CLIENT
+          : Perspective::IS_SERVER,
+      /* bidirectional=*/false)));
 }
 
-TEST_P(UberQuicStreamIdManagerTest, OnMaxStreamIdFrame) {
-  QuicStreamId max_allowed_outgoing_bidirectional_stream_id =
-      manager_->max_allowed_outgoing_bidirectional_stream_id();
-  QuicStreamId max_allowed_outgoing_unidirectional_stream_id =
-      manager_->max_allowed_outgoing_unidirectional_stream_id();
+TEST_P(UberQuicStreamIdManagerTest, OnMaxStreamsFrame) {
+  QuicStreamCount max_allowed_outgoing_bidirectional_stream_count =
+      manager_->max_allowed_outgoing_bidirectional_streams();
 
-  QuicMaxStreamIdFrame frame(kInvalidControlFrameId,
-                             max_allowed_outgoing_bidirectional_stream_id);
-  EXPECT_TRUE(manager_->OnMaxStreamIdFrame(frame));
-  EXPECT_EQ(max_allowed_outgoing_bidirectional_stream_id,
-            manager_->max_allowed_outgoing_bidirectional_stream_id());
-  frame.max_stream_id = max_allowed_outgoing_unidirectional_stream_id;
-  EXPECT_TRUE(manager_->OnMaxStreamIdFrame(frame));
-  EXPECT_EQ(max_allowed_outgoing_unidirectional_stream_id,
-            manager_->max_allowed_outgoing_unidirectional_stream_id());
+  QuicStreamCount max_allowed_outgoing_unidirectional_stream_count =
+      manager_->max_allowed_outgoing_unidirectional_streams();
 
-  frame.max_stream_id =
-      max_allowed_outgoing_bidirectional_stream_id + kV99StreamIdIncrement;
-  EXPECT_TRUE(manager_->OnMaxStreamIdFrame(frame));
-  EXPECT_EQ(
-      max_allowed_outgoing_bidirectional_stream_id + kV99StreamIdIncrement,
-      manager_->max_allowed_outgoing_bidirectional_stream_id());
-  EXPECT_EQ(max_allowed_outgoing_unidirectional_stream_id,
-            manager_->max_allowed_outgoing_unidirectional_stream_id());
+  // Inject a MAX_STREAMS frame that does not increase the limit and then
+  // check that there are no changes. First try the bidirectional manager.
+  QuicMaxStreamsFrame frame(kInvalidControlFrameId,
+                            max_allowed_outgoing_bidirectional_stream_count,
+                            /*unidirectional=*/false);
+  EXPECT_TRUE(manager_->OnMaxStreamsFrame(frame));
+  EXPECT_EQ(max_allowed_outgoing_bidirectional_stream_count,
+            manager_->max_allowed_outgoing_bidirectional_streams());
 
-  frame.max_stream_id =
-      max_allowed_outgoing_unidirectional_stream_id + kV99StreamIdIncrement;
-  EXPECT_TRUE(manager_->OnMaxStreamIdFrame(frame));
-  EXPECT_EQ(
-      max_allowed_outgoing_bidirectional_stream_id + kV99StreamIdIncrement,
-      manager_->max_allowed_outgoing_bidirectional_stream_id());
-  EXPECT_EQ(
-      max_allowed_outgoing_unidirectional_stream_id + kV99StreamIdIncrement,
-      manager_->max_allowed_outgoing_unidirectional_stream_id());
+  // Now try the unidirectioanl manager
+  frame.stream_count = max_allowed_outgoing_unidirectional_stream_count;
+  frame.unidirectional = true;
+  EXPECT_TRUE(manager_->OnMaxStreamsFrame(frame));
+  EXPECT_EQ(max_allowed_outgoing_unidirectional_stream_count,
+            manager_->max_allowed_outgoing_unidirectional_streams());
+
+  // Now try to increase the bidirectional stream count.
+  frame.stream_count = max_allowed_outgoing_bidirectional_stream_count + 1;
+  frame.unidirectional = false;
+  EXPECT_TRUE(manager_->OnMaxStreamsFrame(frame));
+  EXPECT_EQ(max_allowed_outgoing_bidirectional_stream_count + 1,
+            manager_->max_allowed_outgoing_bidirectional_streams());
+  // Make sure that the unidirectional state does not change.
+  EXPECT_EQ(max_allowed_outgoing_unidirectional_stream_count,
+            manager_->max_allowed_outgoing_unidirectional_streams());
+
+  // Now check that a MAX_STREAMS for the unidirectional manager increases
+  // just the unidirectiomal manager's state.
+  frame.stream_count = max_allowed_outgoing_unidirectional_stream_count + 1;
+  frame.unidirectional = true;
+  EXPECT_TRUE(manager_->OnMaxStreamsFrame(frame));
+  EXPECT_EQ(max_allowed_outgoing_bidirectional_stream_count + 1,
+            manager_->max_allowed_outgoing_bidirectional_streams());
+  EXPECT_EQ(max_allowed_outgoing_unidirectional_stream_count + 1,
+            manager_->max_allowed_outgoing_unidirectional_streams());
 }
 
-TEST_P(UberQuicStreamIdManagerTest, OnStreamIdBlockedFrame) {
+TEST_P(UberQuicStreamIdManagerTest, OnStreamsBlockedFrame) {
+  // 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).
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .WillRepeatedly(
           Invoke(this, &UberQuicStreamIdManagerTest::SaveControlFrame));
 
-  QuicStreamId stream_id =
-      manager_->advertised_max_allowed_incoming_bidirectional_stream_id() -
-      kV99StreamIdIncrement;
-  QuicStreamIdBlockedFrame frame(kInvalidControlFrameId, stream_id);
-  session_->OnStreamIdBlockedFrame(frame);
-  EXPECT_EQ(MAX_STREAM_ID_FRAME, frame_.type);
-  EXPECT_EQ(manager_->actual_max_allowed_incoming_bidirectional_stream_id(),
-            frame_.max_stream_id_frame.max_stream_id);
+  QuicStreamCount stream_count =
+      manager_->advertised_max_allowed_incoming_bidirectional_streams() - 1;
 
-  frame.stream_id =
-      manager_->advertised_max_allowed_incoming_unidirectional_stream_id() -
-      kV99StreamIdIncrement;
-  session_->OnStreamIdBlockedFrame(frame);
-  EXPECT_EQ(MAX_STREAM_ID_FRAME, frame_.type);
-  EXPECT_EQ(manager_->actual_max_allowed_incoming_unidirectional_stream_id(),
-            frame_.max_stream_id_frame.max_stream_id);
+  QuicStreamsBlockedFrame frame(kInvalidControlFrameId, stream_count,
+                                /*unidirectional=*/false);
+  session_->OnStreamsBlockedFrame(frame);
+  EXPECT_EQ(MAX_STREAMS_FRAME, frame_.type);
+  EXPECT_EQ(manager_->actual_max_allowed_incoming_bidirectional_streams(),
+            frame_.max_streams_frame.stream_count);
+
+  stream_count =
+      manager_->advertised_max_allowed_incoming_unidirectional_streams() - 1;
+  frame.stream_count = stream_count;
+  frame.unidirectional = true;
+
+  session_->OnStreamsBlockedFrame(frame);
+  EXPECT_EQ(MAX_STREAMS_FRAME, frame_.type);
+  EXPECT_EQ(manager_->actual_max_allowed_incoming_unidirectional_streams(),
+            frame_.max_streams_frame.stream_count);
 }
 
 TEST_P(UberQuicStreamIdManagerTest, IsIncomingStream) {
diff --git a/quic/test_tools/quic_framer_peer.cc b/quic/test_tools/quic_framer_peer.cc
index 13b3150..23486ea 100644
--- a/quic/test_tools/quic_framer_peer.cc
+++ b/quic/test_tools/quic_framer_peer.cc
@@ -199,7 +199,7 @@
 
 // static
 bool QuicFramerPeer::AppendMaxStreamsFrame(QuicFramer* framer,
-                                           const QuicMaxStreamIdFrame& frame,
+                                           const QuicMaxStreamsFrame& frame,
                                            QuicDataWriter* writer) {
   return framer->AppendMaxStreamsFrame(frame, writer);
 }
@@ -207,7 +207,7 @@
 // static
 bool QuicFramerPeer::ProcessMaxStreamsFrame(QuicFramer* framer,
                                             QuicDataReader* reader,
-                                            QuicMaxStreamIdFrame* frame,
+                                            QuicMaxStreamsFrame* frame,
                                             uint64_t frame_type) {
   return framer->ProcessMaxStreamsFrame(reader, frame, frame_type);
 }
@@ -243,7 +243,7 @@
 // static
 bool QuicFramerPeer::AppendStreamsBlockedFrame(
     QuicFramer* framer,
-    const QuicStreamIdBlockedFrame& frame,
+    const QuicStreamsBlockedFrame& frame,
     QuicDataWriter* writer) {
   return framer->AppendStreamsBlockedFrame(frame, writer);
 }
@@ -251,7 +251,7 @@
 // static
 bool QuicFramerPeer::ProcessStreamsBlockedFrame(QuicFramer* framer,
                                                 QuicDataReader* reader,
-                                                QuicStreamIdBlockedFrame* frame,
+                                                QuicStreamsBlockedFrame* frame,
                                                 uint64_t frame_type) {
   return framer->ProcessStreamsBlockedFrame(reader, frame, frame_type);
 }
diff --git a/quic/test_tools/quic_framer_peer.h b/quic/test_tools/quic_framer_peer.h
index 7b23189..4a5efa6 100644
--- a/quic/test_tools/quic_framer_peer.h
+++ b/quic/test_tools/quic_framer_peer.h
@@ -111,11 +111,11 @@
                                         QuicDataReader* reader,
                                         QuicWindowUpdateFrame* frame);
   static bool AppendMaxStreamsFrame(QuicFramer* framer,
-                                    const QuicMaxStreamIdFrame& frame,
+                                    const QuicMaxStreamsFrame& frame,
                                     QuicDataWriter* writer);
   static bool ProcessMaxStreamsFrame(QuicFramer* framer,
                                      QuicDataReader* reader,
-                                     QuicMaxStreamIdFrame* frame,
+                                     QuicMaxStreamsFrame* frame,
                                      uint64_t frame_type);
   static bool AppendIetfBlockedFrame(QuicFramer* framer,
                                      const QuicBlockedFrame& frame,
@@ -132,11 +132,11 @@
                                         QuicBlockedFrame* frame);
 
   static bool AppendStreamsBlockedFrame(QuicFramer* framer,
-                                        const QuicStreamIdBlockedFrame& frame,
+                                        const QuicStreamsBlockedFrame& frame,
                                         QuicDataWriter* writer);
   static bool ProcessStreamsBlockedFrame(QuicFramer* framer,
                                          QuicDataReader* reader,
-                                         QuicStreamIdBlockedFrame* frame,
+                                         QuicStreamsBlockedFrame* frame,
                                          uint64_t frame_type);
 
   static bool AppendNewConnectionIdFrame(QuicFramer* framer,
diff --git a/quic/test_tools/quic_session_peer.cc b/quic/test_tools/quic_session_peer.cc
index 37e5013..176e22a 100644
--- a/quic/test_tools/quic_session_peer.cc
+++ b/quic/test_tools/quic_session_peer.cc
@@ -6,6 +6,7 @@
 
 #include "net/third_party/quiche/src/quic/core/quic_session.h"
 #include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_map_util.h"
 
 namespace quic {
@@ -130,7 +131,7 @@
 bool QuicSessionPeer::IsStreamAvailable(QuicSession* session, QuicStreamId id) {
   DCHECK_NE(0u, id);
   if (session->connection()->transport_version() == QUIC_VERSION_99) {
-    if (id % kV99StreamIdIncrement < 2) {
+    if (id % QuicUtils::StreamIdDelta(QUIC_VERSION_99) < 2) {
       return QuicContainsKey(
           session->v99_streamid_manager_.bidirectional_stream_id_manager_
               .available_streams_,
diff --git a/quic/test_tools/quic_stream_id_manager_peer.cc b/quic/test_tools/quic_stream_id_manager_peer.cc
index 705ee27..3ce5f1f 100644
--- a/quic/test_tools/quic_stream_id_manager_peer.cc
+++ b/quic/test_tools/quic_stream_id_manager_peer.cc
@@ -4,33 +4,26 @@
 #include "net/third_party/quiche/src/quic/test_tools/quic_stream_id_manager_peer.h"
 
 #include "net/third_party/quiche/src/quic/core/quic_stream_id_manager.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/uber_quic_stream_id_manager.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
 
 namespace quic {
 namespace test {
 
 // static
-void QuicStreamIdManagerPeer::IncrementMaximumAllowedOutgoingStreamId(
+void QuicStreamIdManagerPeer::set_incoming_actual_max_streams(
     QuicStreamIdManager* stream_id_manager,
-    int increment) {
-  stream_id_manager->max_allowed_outgoing_stream_id_ +=
-      (increment * kV99StreamIdIncrement);
+    QuicStreamCount count) {
+  stream_id_manager->incoming_actual_max_streams_ = count;
 }
 
 // static
-void QuicStreamIdManagerPeer::IncrementMaximumAllowedIncomingStreamId(
-    QuicStreamIdManager* stream_id_manager,
-    int increment) {
-  stream_id_manager->actual_max_allowed_incoming_stream_id_ +=
-      (increment * kV99StreamIdIncrement);
-  stream_id_manager->advertised_max_allowed_incoming_stream_id_ +=
-      (increment * kV99StreamIdIncrement);
+QuicStreamId QuicStreamIdManagerPeer::GetFirstIncomingStreamId(
+    QuicStreamIdManager* stream_id_manager) {
+  return stream_id_manager->GetFirstIncomingStreamId();
 }
 
-// static
-void QuicStreamIdManagerPeer::SetMaxOpenIncomingStreams(
-    QuicStreamIdManager* stream_id_manager,
-    size_t max_streams) {
-  stream_id_manager->SetMaxOpenIncomingStreams(max_streams);
-}
 }  // 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 2ec07b1..cc78aee 100644
--- a/quic/test_tools/quic_stream_id_manager_peer.h
+++ b/quic/test_tools/quic_stream_id_manager_peer.h
@@ -6,23 +6,25 @@
 
 #include <stddef.h>
 
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+
 namespace quic {
 
 class QuicStreamIdManager;
+class UberQuicStreamIdManager;
 
 namespace test {
 
 class QuicStreamIdManagerPeer {
  public:
   QuicStreamIdManagerPeer() = delete;
-  static void IncrementMaximumAllowedOutgoingStreamId(
+
+  static void set_incoming_actual_max_streams(
       QuicStreamIdManager* stream_id_manager,
-      int increment);
-  static void IncrementMaximumAllowedIncomingStreamId(
-      QuicStreamIdManager* stream_id_manager,
-      int increment);
-  static void SetMaxOpenIncomingStreams(QuicStreamIdManager* stream_id_manager,
-                                        size_t max_streams);
+      QuicStreamCount count);
+
+  static QuicStreamId GetFirstIncomingStreamId(
+      QuicStreamIdManager* stream_id_manager);
 };
 
 }  // namespace test
diff --git a/quic/test_tools/quic_test_utils.cc b/quic/test_tools/quic_test_utils.cc
index 870a7b8..cacb28a 100644
--- a/quic/test_tools/quic_test_utils.cc
+++ b/quic/test_tools/quic_test_utils.cc
@@ -202,9 +202,8 @@
   ON_CALL(*this, OnPathResponseFrame(_)).WillByDefault(testing::Return(true));
 
   ON_CALL(*this, OnGoAwayFrame(_)).WillByDefault(testing::Return(true));
-  ON_CALL(*this, OnMaxStreamIdFrame(_)).WillByDefault(testing::Return(true));
-  ON_CALL(*this, OnStreamIdBlockedFrame(_))
-      .WillByDefault(testing::Return(true));
+  ON_CALL(*this, OnMaxStreamsFrame(_)).WillByDefault(testing::Return(true));
+  ON_CALL(*this, OnStreamsBlockedFrame(_)).WillByDefault(testing::Return(true));
 }
 
 MockFramerVisitor::~MockFramerVisitor() {}
@@ -310,12 +309,12 @@
   return true;
 }
 
-bool NoOpFramerVisitor::OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) {
+bool NoOpFramerVisitor::OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) {
   return true;
 }
 
-bool NoOpFramerVisitor::OnStreamIdBlockedFrame(
-    const QuicStreamIdBlockedFrame& frame) {
+bool NoOpFramerVisitor::OnStreamsBlockedFrame(
+    const QuicStreamsBlockedFrame& frame) {
   return true;
 }
 
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index c888fef..3ce3e76 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -278,9 +278,9 @@
   MOCK_METHOD1(OnPathChallengeFrame, bool(const QuicPathChallengeFrame& frame));
   MOCK_METHOD1(OnPathResponseFrame, bool(const QuicPathResponseFrame& frame));
   MOCK_METHOD1(OnGoAwayFrame, bool(const QuicGoAwayFrame& frame));
-  MOCK_METHOD1(OnMaxStreamIdFrame, bool(const QuicMaxStreamIdFrame& frame));
-  MOCK_METHOD1(OnStreamIdBlockedFrame,
-               bool(const QuicStreamIdBlockedFrame& frame));
+  MOCK_METHOD1(OnMaxStreamsFrame, bool(const QuicMaxStreamsFrame& frame));
+  MOCK_METHOD1(OnStreamsBlockedFrame,
+               bool(const QuicStreamsBlockedFrame& frame));
   MOCK_METHOD1(OnWindowUpdateFrame, bool(const QuicWindowUpdateFrame& frame));
   MOCK_METHOD1(OnBlockedFrame, bool(const QuicBlockedFrame& frame));
   MOCK_METHOD1(OnMessageFrame, bool(const QuicMessageFrame& frame));
@@ -329,8 +329,8 @@
   bool OnPathChallengeFrame(const QuicPathChallengeFrame& frame) override;
   bool OnPathResponseFrame(const QuicPathResponseFrame& frame) override;
   bool OnGoAwayFrame(const QuicGoAwayFrame& frame) override;
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override;
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override;
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override;
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override;
   bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override;
   bool OnBlockedFrame(const QuicBlockedFrame& frame) override;
   bool OnMessageFrame(const QuicMessageFrame& frame) override;
@@ -377,9 +377,9 @@
   MOCK_METHOD0(SendPing, void());
   MOCK_CONST_METHOD0(AllowSelfAddressChange, bool());
   MOCK_METHOD0(OnForwardProgressConfirmed, void());
-  MOCK_METHOD1(OnMaxStreamIdFrame, bool(const QuicMaxStreamIdFrame& frame));
-  MOCK_METHOD1(OnStreamIdBlockedFrame,
-               bool(const QuicStreamIdBlockedFrame& frame));
+  MOCK_METHOD1(OnMaxStreamsFrame, bool(const QuicMaxStreamsFrame& frame));
+  MOCK_METHOD1(OnStreamsBlockedFrame,
+               bool(const QuicStreamsBlockedFrame& frame));
   MOCK_METHOD1(OnStopSendingFrame, bool(const QuicStopSendingFrame& frame));
 };
 
diff --git a/quic/test_tools/simple_quic_framer.cc b/quic/test_tools/simple_quic_framer.cc
index 6a90a79..028e2cd 100644
--- a/quic/test_tools/simple_quic_framer.cc
+++ b/quic/test_tools/simple_quic_framer.cc
@@ -160,13 +160,13 @@
     goaway_frames_.push_back(frame);
     return true;
   }
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override {
-    max_stream_id_frames_.push_back(frame);
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override {
+    max_streams_frames_.push_back(frame);
     return true;
   }
 
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override {
-    stream_id_blocked_frames_.push_back(frame);
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override {
+    streams_blocked_frames_.push_back(frame);
     return true;
   }
 
@@ -206,12 +206,11 @@
   const std::vector<QuicGoAwayFrame>& goaway_frames() const {
     return goaway_frames_;
   }
-  const std::vector<QuicMaxStreamIdFrame>& max_stream_id_frames() const {
-    return max_stream_id_frames_;
+  const std::vector<QuicMaxStreamsFrame>& max_streams_frames() const {
+    return max_streams_frames_;
   }
-  const std::vector<QuicStreamIdBlockedFrame>& stream_id_blocked_frames()
-      const {
-    return stream_id_blocked_frames_;
+  const std::vector<QuicStreamsBlockedFrame>& streams_blocked_frames() const {
+    return streams_blocked_frames_;
   }
   const std::vector<QuicRstStreamFrame>& rst_stream_frames() const {
     return rst_stream_frames_;
@@ -261,8 +260,8 @@
   std::vector<std::unique_ptr<QuicCryptoFrame>> crypto_frames_;
   std::vector<QuicRstStreamFrame> rst_stream_frames_;
   std::vector<QuicGoAwayFrame> goaway_frames_;
-  std::vector<QuicStreamIdBlockedFrame> stream_id_blocked_frames_;
-  std::vector<QuicMaxStreamIdFrame> max_stream_id_frames_;
+  std::vector<QuicStreamsBlockedFrame> streams_blocked_frames_;
+  std::vector<QuicMaxStreamsFrame> max_streams_frames_;
   std::vector<QuicConnectionCloseFrame> connection_close_frames_;
   std::vector<QuicStopSendingFrame> stop_sending_frames_;
   std::vector<QuicPathChallengeFrame> path_challenge_frames_;
diff --git a/quic/test_tools/simulator/quic_endpoint.h b/quic/test_tools/simulator/quic_endpoint.h
index 955ac8f..7693b7b 100644
--- a/quic/test_tools/simulator/quic_endpoint.h
+++ b/quic/test_tools/simulator/quic_endpoint.h
@@ -107,10 +107,10 @@
   void SendPing() override {}
   bool AllowSelfAddressChange() const override;
   void OnForwardProgressConfirmed() override {}
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override {
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override {
     return true;
   }
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override {
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override {
     return true;
   }
   bool OnStopSendingFrame(const QuicStopSendingFrame& frame) override {
diff --git a/quic/tools/quic_packet_printer_bin.cc b/quic/tools/quic_packet_printer_bin.cc
index c8cfc11..207b357 100644
--- a/quic/tools/quic_packet_printer_bin.cc
+++ b/quic/tools/quic_packet_printer_bin.cc
@@ -168,12 +168,12 @@
     std::cerr << "OnGoAwayFrame: " << frame;
     return true;
   }
-  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override {
-    std::cerr << "OnMaxStreamIdFrame: " << frame;
+  bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) override {
+    std::cerr << "OnMaxStreamsFrame: " << frame;
     return true;
   }
-  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override {
-    std::cerr << "OnStreamIdBlockedFrame: " << frame;
+  bool OnStreamsBlockedFrame(const QuicStreamsBlockedFrame& frame) override {
+    std::cerr << "OnStreamsBlockedFrame: " << frame;
     return true;
   }
   bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override {
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index 7203f12..330862a 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -178,13 +178,13 @@
     return true;
   }
 
-  // The function ensures that A) the max stream id frames get properly deleted
+  // The function ensures that A) the MAX_STREAMS frames get properly deleted
   // (since the test uses a 'did we leak memory' check ... if we just lose the
   // frame, the test fails) and B) returns true (instead of the default, false)
   // which ensures that the rest of the system thinks that the frame actually
   // was transmitted.
-  bool ClearMaxStreamIdControlFrame(const QuicFrame& frame) {
-    if (frame.type == MAX_STREAM_ID_FRAME) {
+  bool ClearMaxStreamsControlFrame(const QuicFrame& frame) {
+    if (frame.type == MAX_STREAMS_FRAME) {
       DeleteFrame(&const_cast<QuicFrame&>(frame));
       return true;
     }
@@ -227,8 +227,7 @@
     if (IsVersion99()) {
       EXPECT_CALL(*connection_, SendControlFrame(_))
           .WillRepeatedly(Invoke(
-              this,
-              &QuicSimpleServerSessionTest::ClearMaxStreamIdControlFrame));
+              this, &QuicSimpleServerSessionTest::ClearMaxStreamsControlFrame));
     }
     session_->OnConfigNegotiated();
   }
@@ -566,7 +565,7 @@
     if (IsVersion99()) {
       EXPECT_CALL(*connection_, SendControlFrame(_))
           .WillRepeatedly(Invoke(this, &QuicSimpleServerSessionServerPushTest::
-                                           ClearMaxStreamIdControlFrame));
+                                           ClearMaxStreamsControlFrame));
     }
     session_->OnConfigNegotiated();
 
@@ -708,12 +707,13 @@
   if (IsVersion99()) {
     // The PromisePushedResources call, above, will have used all available
     // stream ids.  For version 99, stream ids are not made available until
-    // a MAX_STREAM_ID frame is received. This emulates the reception of one.
+    // a MAX_STREAMS frame is received. This emulates the reception of one.
     // For pre-v-99, the node monitors its own stream usage and makes streams
     // available as it closes/etc them.
-    session_->OnMaxStreamIdFrame(
-        QuicMaxStreamIdFrame(0, GetNthServerInitiatedUnidirectionalId(10)));
+    session_->OnMaxStreamsFrame(
+        QuicMaxStreamsFrame(0, num_resources, /*unidirectional=*/true));
   }
+
   session_->StreamDraining(GetNthServerInitiatedUnidirectionalId(0));
   // Number of open outgoing streams should still be the same, because a new
   // stream is opened. And the queue should be empty.
@@ -730,8 +730,8 @@
   // when opened stream become close, only one will become open.
   size_t num_resources = kMaxStreamsForTest + 2;
   if (IsVersion99()) {
-    // V99 will send out a stream-id-blocked frame when the we desired to exceed
-    // the limit. This will clear the frames so that they do not block the later
+    // V99 will send out a STREAMS_BLOCKED frame when it tries to exceed the
+    // limit. This will clear the frames so that they do not block the later
     // rst-stream frame.
     EXPECT_CALL(*connection_, SendControlFrame(_))
         .WillOnce(Invoke(
@@ -773,11 +773,11 @@
   if (IsVersion99()) {
     // The PromisePushedResources call, above, will have used all available
     // stream ids.  For version 99, stream ids are not made available until
-    // a MAX_STREAM_ID frame is received. This emulates the reception of one.
+    // a MAX_STREAMS frame is received. This emulates the reception of one.
     // For pre-v-99, the node monitors its own stream usage and makes streams
     // available as it closes/etc them.
-    session_->OnMaxStreamIdFrame(
-        QuicMaxStreamIdFrame(0, GetNthServerInitiatedUnidirectionalId(11)));
+    session_->OnMaxStreamsFrame(
+        QuicMaxStreamsFrame(0, num_resources, /*unidirectional=*/true));
   }
   session_->StreamDraining(GetNthServerInitiatedUnidirectionalId(0));
   session_->StreamDraining(GetNthServerInitiatedUnidirectionalId(1));
@@ -828,11 +828,11 @@
   if (IsVersion99()) {
     // The PromisePushedResources call, above, will have used all available
     // stream ids.  For version 99, stream ids are not made available until
-    // a MAX_STREAM_ID frame is received. This emulates the reception of one.
+    // a MAX_STREAMS frame is received. This emulates the reception of one.
     // For pre-v-99, the node monitors its own stream usage and makes streams
     // available as it closes/etc them.
-    session_->OnMaxStreamIdFrame(
-        QuicMaxStreamIdFrame(0, GetNthServerInitiatedUnidirectionalId(10)));
+    session_->OnMaxStreamsFrame(
+        QuicMaxStreamsFrame(0, num_resources, /*unidirectional=*/true));
   }
   visitor_->OnRstStream(rst);
   // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a