Prevent QuicSession from directly accessing streams' flow controller.

This change provides the following advantages:
1. QuicSession is no longer able to go cross QuicStream to modify its flow controller, and is limited on what it can do with stream's flow controller.
2. QuicStream::IsFlowControlBlocked() can be potentially transitioned to a stream state.

This CL also removes some tests in quic_spdy_session_test that are redundant with quic_session_test.

No behavior change. not protected.

PiperOrigin-RevId: 325899044
Change-Id: I1b676da2736507aec0a4438afdbc596ca274ed13
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 32aed2b..55bd75b 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -573,12 +573,10 @@
   }
 
   static void ExpectFlowControlsSynced(QuicStream* client, QuicStream* server) {
-    EXPECT_EQ(
-        QuicFlowControllerPeer::SendWindowSize(client->flow_controller()),
-        QuicFlowControllerPeer::ReceiveWindowSize(server->flow_controller()));
-    EXPECT_EQ(
-        QuicFlowControllerPeer::ReceiveWindowSize(client->flow_controller()),
-        QuicFlowControllerPeer::SendWindowSize(server->flow_controller()));
+    EXPECT_EQ(QuicStreamPeer::SendWindowSize(client),
+              QuicStreamPeer::ReceiveWindowSize(server));
+    EXPECT_EQ(QuicStreamPeer::ReceiveWindowSize(client),
+              QuicStreamPeer::SendWindowSize(server));
   }
 
   // Must be called before Initialize to have effect.
@@ -1748,9 +1746,9 @@
   QuicSpdyClientStream* stream = client_->GetOrCreateStream();
   QuicSession* session = GetClientSession();
   ASSERT_TRUE(session);
-  QuicFlowControllerPeer::SetSendWindowOffset(stream->flow_controller(), 0);
+  QuicStreamPeer::SetSendWindowOffset(stream, 0);
   QuicFlowControllerPeer::SetSendWindowOffset(session->flow_controller(), 0);
-  EXPECT_TRUE(stream->flow_controller()->IsBlocked());
+  EXPECT_TRUE(stream->IsFlowControlBlocked());
   EXPECT_TRUE(session->flow_controller()->IsBlocked());
 
   // Make sure that the stream has data pending so that it will be marked as
@@ -2404,8 +2402,7 @@
                   ->config()
                   ->ReceivedInitialSessionFlowControlWindowBytes());
   }
-  EXPECT_EQ(kServerStreamIFCW, QuicFlowControllerPeer::SendWindowOffset(
-                                   stream->flow_controller()));
+  EXPECT_EQ(kServerStreamIFCW, QuicStreamPeer::SendWindowOffset(stream));
   QuicSpdyClientSession* client_session = GetClientSession();
   ASSERT_TRUE(client_session);
   EXPECT_EQ(kServerSessionIFCW, QuicFlowControllerPeer::SendWindowOffset(
@@ -2480,8 +2477,7 @@
               client_session->config()
                   ->ReceivedInitialSessionFlowControlWindowBytes());
   }
-  EXPECT_EQ(kExpectedStreamIFCW, QuicFlowControllerPeer::SendWindowOffset(
-                                     stream->flow_controller()));
+  EXPECT_EQ(kExpectedStreamIFCW, QuicStreamPeer::SendWindowOffset(stream));
   EXPECT_EQ(kExpectedSessionIFCW, QuicFlowControllerPeer::SendWindowOffset(
                                       client_session->flow_controller()));
 }
@@ -2512,9 +2508,7 @@
   // In v47 and later, the crypto handshake (sent in CRYPTO frames) is not
   // subject to flow control.
   if (!version_.UsesCryptoFrames()) {
-    EXPECT_LT(QuicFlowControllerPeer::SendWindowSize(
-                  crypto_stream->flow_controller()),
-              kStreamIFCW);
+    EXPECT_LT(QuicStreamPeer::SendWindowSize(crypto_stream), kStreamIFCW);
   }
   // When stream type is enabled, control streams will send settings and
   // contribute to flow control windows, so this expectation is no longer valid.
@@ -2535,9 +2529,7 @@
   QuicHeadersStream* headers_stream =
       QuicSpdySessionPeer::GetHeadersStream(client_session);
   ASSERT_TRUE(headers_stream);
-  EXPECT_LT(
-      QuicFlowControllerPeer::SendWindowSize(headers_stream->flow_controller()),
-      kStreamIFCW);
+  EXPECT_LT(QuicStreamPeer::SendWindowSize(headers_stream), kStreamIFCW);
   EXPECT_EQ(kSessionIFCW, QuicFlowControllerPeer::SendWindowSize(
                               client_session->flow_controller()));
 
@@ -2618,29 +2610,25 @@
                               kDefaultMaxUncompressedHeaderSize);
     SpdySerializedFrame frame(spdy_framer.SerializeFrame(settings_frame));
 
-    QuicFlowController* client_header_stream_flow_controller =
-        QuicSpdySessionPeer::GetHeadersStream(client_session)
-            ->flow_controller();
-    QuicFlowController* server_header_stream_flow_controller =
-        QuicSpdySessionPeer::GetHeadersStream(server_session)
-            ->flow_controller();
+    QuicHeadersStream* client_header_stream =
+        QuicSpdySessionPeer::GetHeadersStream(client_session);
+    QuicHeadersStream* server_header_stream =
+        QuicSpdySessionPeer::GetHeadersStream(server_session);
     // Both client and server are sending this SETTINGS frame, and the send
     // window is consumed. But because of timing issue, the server may send or
     // not send the frame, and the client may send/ not send / receive / not
     // receive the frame.
     // TODO(fayang): Rewrite this part because it is hacky.
-    QuicByteCount win_difference1 = QuicFlowControllerPeer::ReceiveWindowSize(
-                                        server_header_stream_flow_controller) -
-                                    QuicFlowControllerPeer::SendWindowSize(
-                                        client_header_stream_flow_controller);
+    QuicByteCount win_difference1 =
+        QuicStreamPeer::ReceiveWindowSize(server_header_stream) -
+        QuicStreamPeer::SendWindowSize(client_header_stream);
     if (win_difference1 != 0) {
       EXPECT_EQ(frame.size(), win_difference1);
     }
 
-    QuicByteCount win_difference2 = QuicFlowControllerPeer::ReceiveWindowSize(
-                                        client_header_stream_flow_controller) -
-                                    QuicFlowControllerPeer::SendWindowSize(
-                                        server_header_stream_flow_controller);
+    QuicByteCount win_difference2 =
+        QuicStreamPeer::ReceiveWindowSize(client_header_stream) -
+        QuicStreamPeer::SendWindowSize(server_header_stream);
     if (win_difference2 != 0) {
       EXPECT_EQ(frame.size(), win_difference2);
     }
@@ -2649,14 +2637,12 @@
     // TODO(fayang): Rewrite this part because it is hacky.
     float ratio1 = static_cast<float>(QuicFlowControllerPeer::ReceiveWindowSize(
                        client_session->flow_controller())) /
-                   QuicFlowControllerPeer::ReceiveWindowSize(
-                       QuicSpdySessionPeer::GetHeadersStream(client_session)
-                           ->flow_controller());
+                   QuicStreamPeer::ReceiveWindowSize(
+                       QuicSpdySessionPeer::GetHeadersStream(client_session));
     float ratio2 = static_cast<float>(QuicFlowControllerPeer::ReceiveWindowSize(
                        client_session->flow_controller())) /
-                   (QuicFlowControllerPeer::ReceiveWindowSize(
-                        QuicSpdySessionPeer::GetHeadersStream(client_session)
-                            ->flow_controller()) +
+                   (QuicStreamPeer::ReceiveWindowSize(
+                        QuicSpdySessionPeer::GetHeadersStream(client_session)) +
                     frame.size());
     EXPECT_TRUE(ratio1 == kSessionToStreamRatio ||
                 ratio2 == kSessionToStreamRatio);
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index a6be1e6..70b7b36 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -33,6 +33,7 @@
 #include "net/third_party/quiche/src/quic/test_tools/quic_packet_creator_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
 #include "net/third_party/quiche/src/quic/test_tools/simple_session_cache.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_arraysize.h"
@@ -1031,7 +1032,7 @@
     auto* control_stream =
         QuicSpdySessionPeer::GetSendControlStream(session_.get());
     EXPECT_EQ(kInitialStreamFlowControlWindowForTest,
-              control_stream->flow_controller()->send_window_offset());
+              QuicStreamPeer::SendWindowOffset(control_stream));
     EXPECT_EQ(5u, session_->max_outbound_header_list_size());
   } else {
     auto* id_manager = QuicSessionPeer::GetStreamIdManager(session_.get());
@@ -1064,7 +1065,7 @@
                   kHttp3StaticUnidirectionalStreamCount + 1,
               id_manager->max_outgoing_unidirectional_streams());
     EXPECT_EQ(kInitialStreamFlowControlWindowForTest + 1,
-              control_stream->flow_controller()->send_window_offset());
+              QuicStreamPeer::SendWindowOffset(control_stream));
   } else {
     auto* id_manager = QuicSessionPeer::GetStreamIdManager(session_.get());
     EXPECT_EQ(kDefaultMaxStreamsPerConnection + 1,
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index ede4d2c..18d3d75 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -1305,12 +1305,12 @@
   // Create a stream, and send enough data to make it flow control blocked.
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   std::string body(kMinimumFlowControlSendWindow, '.');
-  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+  EXPECT_FALSE(stream2->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
   EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AtLeast(1));
   stream2->WriteOrBufferBody(body, false);
-  EXPECT_TRUE(stream2->flow_controller()->IsBlocked());
+  EXPECT_TRUE(stream2->IsFlowControlBlocked());
   EXPECT_TRUE(session_.IsConnectionFlowControlBlocked());
   EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
 
@@ -1319,7 +1319,7 @@
   CompleteHandshake();
   EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(&session_, stream2->id()));
   // Stream is now unblocked.
-  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+  EXPECT_FALSE(stream2->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
 }
@@ -1335,18 +1335,18 @@
   // contains a larger send window offset, the stream becomes unblocked.
   session_.set_writev_consumes_all_data(true);
   TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
-  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(crypto_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
   QuicHeadersStream* headers_stream =
       QuicSpdySessionPeer::GetHeadersStream(&session_);
-  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(headers_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .WillOnce(Invoke(&ClearControlFrame));
-  for (QuicStreamId i = 0;
-       !crypto_stream->flow_controller()->IsBlocked() && i < 1000u; i++) {
+  for (QuicStreamId i = 0; !crypto_stream->IsFlowControlBlocked() && i < 1000u;
+       i++) {
     EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
     EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
     QuicStreamOffset offset = crypto_stream->stream_bytes_written();
@@ -1358,8 +1358,8 @@
     QuicDataWriter writer(1000, buf, quiche::NETWORK_BYTE_ORDER);
     crypto_stream->WriteStreamData(offset, crypto_message.size(), &writer);
   }
-  EXPECT_TRUE(crypto_stream->flow_controller()->IsBlocked());
-  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_TRUE(crypto_stream->IsFlowControlBlocked());
+  EXPECT_FALSE(headers_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
   EXPECT_FALSE(session_.HasDataToWrite());
@@ -1371,7 +1371,7 @@
   EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(
       &session_, QuicUtils::GetCryptoStreamId(transport_version())));
   // Stream is now unblocked and will no longer have buffered data.
-  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(crypto_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
 }
@@ -1400,12 +1400,12 @@
   // contains a larger send window offset, the stream becomes unblocked.
   session_.set_writev_consumes_all_data(true);
   TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
-  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(crypto_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
   QuicHeadersStream* headers_stream =
       QuicSpdySessionPeer::GetHeadersStream(&session_);
-  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(headers_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
   QuicStreamId stream_id = 5;
@@ -1414,7 +1414,7 @@
       .WillOnce(Invoke(&ClearControlFrame));
   SpdyHeaderBlock headers;
   SimpleRandom random;
-  while (!headers_stream->flow_controller()->IsBlocked() && stream_id < 2000) {
+  while (!headers_stream->IsFlowControlBlocked() && stream_id < 2000) {
     EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
     EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
     headers["header"] = quiche::QuicheStrCat(
@@ -1430,8 +1430,8 @@
                                        spdy::SpdyStreamPrecedence(0), nullptr);
   EXPECT_TRUE(headers_stream->HasBufferedData());
 
-  EXPECT_TRUE(headers_stream->flow_controller()->IsBlocked());
-  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_TRUE(headers_stream->IsFlowControlBlocked());
+  EXPECT_FALSE(crypto_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
   EXPECT_FALSE(session_.HasDataToWrite());
@@ -1441,7 +1441,7 @@
   CompleteHandshake();
 
   // Stream is now unblocked and will no longer have buffered data.
-  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(headers_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
   EXPECT_TRUE(headers_stream->HasBufferedData());
@@ -1495,119 +1495,6 @@
   EXPECT_EQ(kByteOffset, session_.flow_controller()->bytes_consumed());
 }
 
-TEST_P(QuicSpdySessionTestServer,
-       ConnectionFlowControlAccountingFinAndLocalReset) {
-  // Test the situation where we receive a FIN on a stream, and before we fully
-  // consume all the data from the sequencer buffer we locally RST the stream.
-  // The bytes between highest consumed byte, and the final byte offset that we
-  // determined when the FIN arrived, should be marked as consumed at the
-  // connection level flow controller when the stream is reset.
-  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
-
-  const QuicStreamOffset kByteOffset =
-      kInitialSessionFlowControlWindowForTest / 2 - 1;
-  QuicStreamFrame frame(stream->id(), true, kByteOffset, ".");
-  session_.OnStreamFrame(frame);
-  EXPECT_TRUE(connection_->connected());
-
-  EXPECT_EQ(0u, stream->flow_controller()->bytes_consumed());
-  EXPECT_EQ(kByteOffset + frame.data_length,
-            stream->flow_controller()->highest_received_byte_offset());
-
-  // Reset stream locally.
-  EXPECT_CALL(*connection_, SendControlFrame(_));
-  EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
-  stream->Reset(QUIC_STREAM_CANCELLED);
-  EXPECT_EQ(kByteOffset + frame.data_length,
-            session_.flow_controller()->bytes_consumed());
-}
-
-TEST_P(QuicSpdySessionTestServer, ConnectionFlowControlAccountingFinAfterRst) {
-  CompleteHandshake();
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillRepeatedly(Invoke(&ClearControlFrame));
-  // Test that when we RST the stream (and tear down stream state), and then
-  // receive a FIN from the peer, we correctly adjust our connection level flow
-  // control receive window.
-
-  // Connection starts with some non-zero highest received byte offset,
-  // due to other active streams.
-  const uint64_t kInitialConnectionBytesConsumed = 567;
-  const uint64_t kInitialConnectionHighestReceivedOffset = 1234;
-  EXPECT_LT(kInitialConnectionBytesConsumed,
-            kInitialConnectionHighestReceivedOffset);
-  session_.flow_controller()->UpdateHighestReceivedOffset(
-      kInitialConnectionHighestReceivedOffset);
-  session_.flow_controller()->AddBytesConsumed(kInitialConnectionBytesConsumed);
-
-  // Reset our stream: this results in the stream being closed locally.
-  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
-  if (VersionUsesHttp3(transport_version())) {
-    EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
-        .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
-  }
-  EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
-  stream->Reset(QUIC_STREAM_CANCELLED);
-
-  // Now receive a response from the peer with a FIN. We should handle this by
-  // adjusting the connection level flow control receive window to take into
-  // account the total number of bytes sent by the peer.
-  const QuicStreamOffset kByteOffset = 5678;
-  std::string body = "hello";
-  QuicStreamFrame frame(stream->id(), true, kByteOffset,
-                        quiche::QuicheStringPiece(body));
-  session_.OnStreamFrame(frame);
-
-  QuicStreamOffset total_stream_bytes_sent_by_peer =
-      kByteOffset + body.length();
-  EXPECT_EQ(kInitialConnectionBytesConsumed + total_stream_bytes_sent_by_peer,
-            session_.flow_controller()->bytes_consumed());
-  EXPECT_EQ(
-      kInitialConnectionHighestReceivedOffset + total_stream_bytes_sent_by_peer,
-      session_.flow_controller()->highest_received_byte_offset());
-}
-
-TEST_P(QuicSpdySessionTestServer, ConnectionFlowControlAccountingRstAfterRst) {
-  CompleteHandshake();
-  // Test that when we RST the stream (and tear down stream state), and then
-  // receive a RST from the peer, we correctly adjust our connection level flow
-  // control receive window.
-
-  // Connection starts with some non-zero highest received byte offset,
-  // due to other active streams.
-  const uint64_t kInitialConnectionBytesConsumed = 567;
-  const uint64_t kInitialConnectionHighestReceivedOffset = 1234;
-  EXPECT_LT(kInitialConnectionBytesConsumed,
-            kInitialConnectionHighestReceivedOffset);
-  session_.flow_controller()->UpdateHighestReceivedOffset(
-      kInitialConnectionHighestReceivedOffset);
-  session_.flow_controller()->AddBytesConsumed(kInitialConnectionBytesConsumed);
-
-  // Reset our stream: this results in the stream being closed locally.
-  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
-  if (VersionUsesHttp3(transport_version())) {
-    EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
-        .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
-  }
-  EXPECT_CALL(*connection_, SendControlFrame(_));
-  EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
-  stream->Reset(QUIC_STREAM_CANCELLED);
-  EXPECT_TRUE(QuicStreamPeer::read_side_closed(stream));
-
-  // Now receive a RST from the peer. We should handle this by adjusting the
-  // connection level flow control receive window to take into account the total
-  // number of bytes sent by the peer.
-  const QuicStreamOffset kByteOffset = 5678;
-  QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream->id(),
-                               QUIC_STREAM_CANCELLED, kByteOffset);
-  session_.OnRstStream(rst_frame);
-
-  EXPECT_EQ(kInitialConnectionBytesConsumed + kByteOffset,
-            session_.flow_controller()->bytes_consumed());
-  EXPECT_EQ(kInitialConnectionHighestReceivedOffset + kByteOffset,
-            session_.flow_controller()->highest_received_byte_offset());
-}
-
 TEST_P(QuicSpdySessionTestServer, InvalidStreamFlowControlWindowInHandshake) {
   if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
     // IETF Quic doesn't require a minimum flow control window.
@@ -1624,22 +1511,6 @@
   session_.OnConfigNegotiated();
 }
 
-TEST_P(QuicSpdySessionTestServer, InvalidSessionFlowControlWindowInHandshake) {
-  if (GetParam().handshake_protocol == PROTOCOL_TLS1_3) {
-    // IETF Quic doesn't require a minimum flow control window.
-    return;
-  }
-  // Test that receipt of an invalid (< default) session flow control window
-  // from the peer results in the connection being torn down.
-  const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1;
-  QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(),
-                                                             kInvalidWindow);
-
-  EXPECT_CALL(*connection_,
-              CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
-  session_.OnConfigNegotiated();
-}
-
 TEST_P(QuicSpdySessionTestServer, TooLowUnidirectionalStreamLimitHttp3) {
   if (!VersionUsesHttp3(transport_version())) {
     return;
@@ -1666,34 +1537,6 @@
                              session_.flow_controller()));
 }
 
-TEST_P(QuicSpdySessionTestServer, FlowControlWithInvalidFinalOffset) {
-  CompleteHandshake();
-  // Test that if we receive a stream RST with a highest byte offset that
-  // violates flow control, that we close the connection.
-  const uint64_t kLargeOffset = kInitialSessionFlowControlWindowForTest + 1;
-  EXPECT_CALL(*connection_,
-              CloseConnection(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA, _, _))
-      .Times(2);
-
-  // Check that stream frame + FIN results in connection close.
-  TestStream* stream = session_.CreateOutgoingBidirectionalStream();
-  if (VersionUsesHttp3(transport_version())) {
-    EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
-        .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
-  }
-  EXPECT_CALL(*connection_, SendControlFrame(_));
-  EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
-  stream->Reset(QUIC_STREAM_CANCELLED);
-  QuicStreamFrame frame(stream->id(), true, kLargeOffset,
-                        quiche::QuicheStringPiece());
-  session_.OnStreamFrame(frame);
-
-  // Check that RST results in connection close.
-  QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream->id(),
-                               QUIC_STREAM_CANCELLED, kLargeOffset);
-  session_.OnRstStream(rst_frame);
-}
-
 TEST_P(QuicSpdySessionTestServer, WindowUpdateUnblocksHeadersStream) {
   if (VersionUsesHttp3(transport_version())) {
     // The test relies on headers stream, which no longer exists in IETF QUIC.
@@ -1706,9 +1549,8 @@
   // Set the headers stream to be flow control blocked.
   QuicHeadersStream* headers_stream =
       QuicSpdySessionPeer::GetHeadersStream(&session_);
-  QuicFlowControllerPeer::SetSendWindowOffset(headers_stream->flow_controller(),
-                                              0);
-  EXPECT_TRUE(headers_stream->flow_controller()->IsBlocked());
+  QuicStreamPeer::SetSendWindowOffset(headers_stream, 0);
+  EXPECT_TRUE(headers_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
 
@@ -1717,7 +1559,7 @@
                                             headers_stream->id(),
                                             2 * kMinimumFlowControlSendWindow);
   session_.OnWindowUpdateFrame(window_update_frame);
-  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(headers_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
 }
@@ -2048,7 +1890,7 @@
 
   EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
   QuicStream* stream = session_.GetOrCreateStream(stream_id1);
-  EXPECT_EQ(1u, stream->flow_controller()->bytes_consumed());
+  EXPECT_EQ(1u, QuicStreamPeer::bytes_consumed(stream));
   EXPECT_EQ(1u, session_.flow_controller()->bytes_consumed());
 
   // The same stream type can be encoded differently.
@@ -2060,7 +1902,7 @@
 
   EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
   stream = session_.GetOrCreateStream(stream_id2);
-  EXPECT_EQ(4u, stream->flow_controller()->bytes_consumed());
+  EXPECT_EQ(4u, QuicStreamPeer::bytes_consumed(stream));
   EXPECT_EQ(5u, session_.flow_controller()->bytes_consumed());
 }
 
@@ -2092,7 +1934,7 @@
   session_.OnStreamFrame(data1);
   EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(&session_));
   QuicStream* stream = session_.GetOrCreateStream(stream_id);
-  EXPECT_EQ(3u, stream->flow_controller()->highest_received_byte_offset());
+  EXPECT_EQ(3u, stream->highest_received_byte_offset());
 }
 
 TEST_P(QuicSpdySessionTestServer, OnStreamFrameLost) {
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 7dfe1fe..259493f 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -639,10 +639,9 @@
 
   if (VersionUsesHttp3(transport_version())) {
     if (fin) {
-      OnStreamFrame(
-          QuicStreamFrame(id(), /* fin = */ true,
-                          flow_controller()->highest_received_byte_offset(),
-                          quiche::QuicheStringPiece()));
+      OnStreamFrame(QuicStreamFrame(id(), /* fin = */ true,
+                                    highest_received_byte_offset(),
+                                    quiche::QuicheStringPiece()));
     }
     return;
   }
@@ -701,10 +700,9 @@
   }
   trailers_decompressed_ = true;
   if (fin) {
-    const QuicStreamOffset offset =
-        VersionUsesHttp3(transport_version())
-            ? flow_controller()->highest_received_byte_offset()
-            : final_byte_offset;
+    const QuicStreamOffset offset = VersionUsesHttp3(transport_version())
+                                        ? highest_received_byte_offset()
+                                        : final_byte_offset;
     OnStreamFrame(
         QuicStreamFrame(id(), fin, offset, quiche::QuicheStringPiece()));
   }
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index b556367..7746ff2 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -827,7 +827,7 @@
   EXPECT_EQ(body, std::string(static_cast<char*>(vec.iov_base), vec.iov_len));
 
   stream_->MarkConsumed(body.length());
-  EXPECT_EQ(data.length(), stream_->flow_controller()->bytes_consumed());
+  EXPECT_EQ(data.length(), QuicStreamPeer::bytes_consumed(stream_));
 }
 
 TEST_P(QuicSpdyStreamTest, ProcessHeadersAndConsumeMultipleBody) {
@@ -848,7 +848,7 @@
 
   stream_->MarkConsumed(body1.length() + body2.length());
   EXPECT_EQ(data1.length() + data2.length(),
-            stream_->flow_controller()->bytes_consumed());
+            QuicStreamPeer::bytes_consumed(stream_));
 }
 
 TEST_P(QuicSpdyStreamTest, ProcessHeadersAndBodyIncrementalReadv) {
@@ -911,10 +911,8 @@
 
   // Set a small flow control limit.
   const uint64_t kWindow = 36;
-  QuicFlowControllerPeer::SetSendWindowOffset(stream_->flow_controller(),
-                                              kWindow);
-  EXPECT_EQ(kWindow, QuicFlowControllerPeer::SendWindowOffset(
-                         stream_->flow_controller()));
+  QuicStreamPeer::SetSendWindowOffset(stream_, kWindow);
+  EXPECT_EQ(kWindow, QuicStreamPeer::SendWindowOffset(stream_));
 
   // Try to send more data than the flow control limit allows.
   const uint64_t kOverflow = 15;
@@ -931,8 +929,7 @@
   stream_->WriteOrBufferBody(body, false);
 
   // Should have sent as much as possible, resulting in no send window left.
-  EXPECT_EQ(0u,
-            QuicFlowControllerPeer::SendWindowSize(stream_->flow_controller()));
+  EXPECT_EQ(0u, QuicStreamPeer::SendWindowSize(stream_));
 
   // And we should have queued the overflowed data.
   EXPECT_EQ(kOverflow + kHeaderLength, stream_->BufferedDataBytes());
@@ -950,12 +947,8 @@
 
   // Set a small flow control receive window.
   const uint64_t kWindow = 36;
-  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
-                                                 kWindow);
-  QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
-                                              kWindow);
-  EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowOffset(
-                         stream_->flow_controller()));
+  QuicStreamPeer::SetReceiveWindowOffset(stream_, kWindow);
+  QuicStreamPeer::SetMaxReceiveWindow(stream_, kWindow);
 
   // Stream receives enough data to fill a fraction of the receive window.
   std::string body(kWindow / 3, 'a');
@@ -977,9 +970,8 @@
   QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
                          quiche::QuicheStringPiece(data));
   stream_->OnStreamFrame(frame1);
-  EXPECT_EQ(
-      kWindow - (kWindow / 3) - header_length,
-      QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+  EXPECT_EQ(kWindow - (kWindow / 3) - header_length,
+            QuicStreamPeer::ReceiveWindowSize(stream_));
 
   // Now receive another frame which results in the receive window being over
   // half full. This should all be buffered, decreasing the receive window but
@@ -988,9 +980,8 @@
                          kWindow / 3 + header_length,
                          quiche::QuicheStringPiece(data));
   stream_->OnStreamFrame(frame2);
-  EXPECT_EQ(
-      kWindow - (2 * kWindow / 3) - 2 * header_length,
-      QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+  EXPECT_EQ(kWindow - (2 * kWindow / 3) - 2 * header_length,
+            QuicStreamPeer::ReceiveWindowSize(stream_));
 }
 
 // Tests that on receipt of data, the stream updates its receive window offset
@@ -1001,12 +992,8 @@
 
   // Set a small flow control limit.
   const uint64_t kWindow = 36;
-  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
-                                                 kWindow);
-  QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
-                                              kWindow);
-  EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowOffset(
-                         stream_->flow_controller()));
+  QuicStreamPeer::SetReceiveWindowOffset(stream_, kWindow);
+  QuicStreamPeer::SetMaxReceiveWindow(stream_, kWindow);
 
   // Stream receives enough data to fill a fraction of the receive window.
   std::string body(kWindow / 3, 'a');
@@ -1029,9 +1016,8 @@
   QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
                          quiche::QuicheStringPiece(data));
   stream_->OnStreamFrame(frame1);
-  EXPECT_EQ(
-      kWindow - (kWindow / 3) - header_length,
-      QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+  EXPECT_EQ(kWindow - (kWindow / 3) - header_length,
+            QuicStreamPeer::ReceiveWindowSize(stream_));
 
   // Now receive another frame which results in the receive window being over
   // half full.  This will trigger the stream to increase its receive window
@@ -1043,8 +1029,7 @@
   EXPECT_CALL(*session_, SendWindowUpdate(_, _));
   EXPECT_CALL(*connection_, SendControlFrame(_));
   stream_->OnStreamFrame(frame2);
-  EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowSize(
-                         stream_->flow_controller()));
+  EXPECT_EQ(kWindow, QuicStreamPeer::ReceiveWindowSize(stream_));
 }
 
 // Tests that on receipt of data, the connection updates its receive window
@@ -1055,14 +1040,10 @@
 
   // Set a small flow control limit for streams and connection.
   const uint64_t kWindow = 36;
-  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
-                                                 kWindow);
-  QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
-                                              kWindow);
-  QuicFlowControllerPeer::SetReceiveWindowOffset(stream2_->flow_controller(),
-                                                 kWindow);
-  QuicFlowControllerPeer::SetMaxReceiveWindow(stream2_->flow_controller(),
-                                              kWindow);
+  QuicStreamPeer::SetReceiveWindowOffset(stream_, kWindow);
+  QuicStreamPeer::SetMaxReceiveWindow(stream_, kWindow);
+  QuicStreamPeer::SetReceiveWindowOffset(stream2_, kWindow);
+  QuicStreamPeer::SetMaxReceiveWindow(stream2_, kWindow);
   QuicFlowControllerPeer::SetReceiveWindowOffset(session_->flow_controller(),
                                                  kWindow);
   QuicFlowControllerPeer::SetMaxReceiveWindow(session_->flow_controller(),
@@ -1130,8 +1111,7 @@
 
   // Set a small flow control limit.
   const uint64_t kWindow = 50;
-  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
-                                                 kWindow);
+  QuicStreamPeer::SetReceiveWindowOffset(stream_, kWindow);
 
   ProcessHeaders(false, headers_);
 
@@ -1166,8 +1146,7 @@
   // Set a small flow control window on streams, and connection.
   const uint64_t kStreamWindow = 50;
   const uint64_t kConnectionWindow = 10;
-  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
-                                                 kStreamWindow);
+  QuicStreamPeer::SetReceiveWindowOffset(stream_, kStreamWindow);
   QuicFlowControllerPeer::SetReceiveWindowOffset(session_->flow_controller(),
                                                  kConnectionWindow);
 
@@ -1192,9 +1171,7 @@
   Initialize(kShouldProcessData);
 
   // Set a flow control limit of zero.
-  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(), 0);
-  EXPECT_EQ(0u, QuicFlowControllerPeer::ReceiveWindowOffset(
-                    stream_->flow_controller()));
+  QuicStreamPeer::SetReceiveWindowOffset(stream_, 0);
 
   // Send a frame with a FIN but no data. This should not be blocked.
   std::string body = "";
diff --git a/quic/core/quic_flow_controller.h b/quic/core/quic_flow_controller.h
index daad3d9..1a45821 100644
--- a/quic/core/quic_flow_controller.h
+++ b/quic/core/quic_flow_controller.h
@@ -194,7 +194,7 @@
   // Used to dynamically enable receive window auto-tuning.
   bool auto_tune_receive_window_;
 
-  // The session's flow controller.  null if this is stream id 0.
+  // The session's flow controller. Null if this is the session flow controller.
   // Not owned.
   QuicFlowControllerInterface* session_flow_controller_;
 
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 39d969c..59bf083 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -566,7 +566,7 @@
 
 bool QuicSession::CheckStreamWriteBlocked(QuicStream* stream) const {
   if (!stream->write_side_closed() && stream->HasBufferedData() &&
-      !stream->flow_controller()->IsBlocked() &&
+      !stream->IsFlowControlBlocked() &&
       !write_blocked_streams_.IsStreamBlocked(stream->id())) {
     QUIC_DLOG(ERROR) << ENDPOINT << "stream " << stream->id()
                      << " has buffered " << stream->BufferedDataBytes()
@@ -654,7 +654,7 @@
     QUIC_DVLOG(1) << ENDPOINT << "Removing stream "
                   << currently_writing_stream_id_ << " from write-blocked list";
     QuicStream* stream = GetOrCreateStream(currently_writing_stream_id_);
-    if (stream != nullptr && !stream->flow_controller()->IsBlocked()) {
+    if (stream != nullptr && !stream->IsFlowControlBlocked()) {
       // If the stream can't write all bytes it'll re-add itself to the blocked
       // list.
       uint64_t previous_bytes_written = stream->stream_bytes_written();
@@ -961,7 +961,7 @@
     // perspective. Do not inform stream Id manager yet.
     DCHECK(!stream->was_draining());
     InsertLocallyClosedStreamsHighestOffset(
-        stream_id, stream->flow_controller()->highest_received_byte_offset());
+        stream_id, stream->highest_received_byte_offset());
     if (!remove_zombie_streams_) {
       stream_map_.erase(it);
     }
@@ -1310,11 +1310,10 @@
   flow_controller_.UpdateReceiveWindowSize(session_window);
   // Inform all existing streams about the new window.
   for (auto const& kv : stream_map_) {
-    kv.second->flow_controller()->UpdateReceiveWindowSize(stream_window);
+    kv.second->UpdateReceiveWindowSize(stream_window);
   }
   if (!QuicVersionUsesCryptoFrames(transport_version())) {
-    GetMutableCryptoStream()->flow_controller()->UpdateReceiveWindowSize(
-        stream_window);
+    GetMutableCryptoStream()->UpdateReceiveWindowSize(stream_window);
   }
 }
 
@@ -2048,12 +2047,12 @@
 
 bool QuicSession::IsStreamFlowControlBlocked() {
   for (auto const& kv : stream_map_) {
-    if (kv.second->flow_controller()->IsBlocked()) {
+    if (kv.second->IsFlowControlBlocked()) {
       return true;
     }
   }
   if (!QuicVersionUsesCryptoFrames(transport_version()) &&
-      GetMutableCryptoStream()->flow_controller()->IsBlocked()) {
+      GetMutableCryptoStream()->IsFlowControlBlocked()) {
     return true;
   }
   return false;
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 3973bd5..e8dbce5 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -1598,12 +1598,12 @@
   // Create a stream, and send enough data to make it flow control blocked.
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   std::string body(kMinimumFlowControlSendWindow, '.');
-  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+  EXPECT_FALSE(stream2->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
   EXPECT_CALL(*connection_, SendControlFrame(_)).Times(AtLeast(1));
   stream2->WriteOrBufferData(body, false, nullptr);
-  EXPECT_TRUE(stream2->flow_controller()->IsBlocked());
+  EXPECT_TRUE(stream2->IsFlowControlBlocked());
   EXPECT_TRUE(session_.IsConnectionFlowControlBlocked());
   EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
 
@@ -1613,7 +1613,7 @@
   session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
   EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(&session_, stream2->id()));
   // Stream is now unblocked.
-  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+  EXPECT_FALSE(stream2->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
 }
@@ -1629,15 +1629,15 @@
   // contains a larger send window offset, the stream becomes unblocked.
   session_.set_writev_consumes_all_data(true);
   TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
-  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(crypto_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
   EXPECT_CALL(*connection_, SendControlFrame(_))
       .WillOnce(Invoke(&ClearControlFrame));
-  for (QuicStreamId i = 0;
-       !crypto_stream->flow_controller()->IsBlocked() && i < 1000u; i++) {
+  for (QuicStreamId i = 0; !crypto_stream->IsFlowControlBlocked() && i < 1000u;
+       i++) {
     EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
     EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
     QuicStreamOffset offset = crypto_stream->stream_bytes_written();
@@ -1649,7 +1649,7 @@
     QuicDataWriter writer(1000, buf, quiche::NETWORK_BYTE_ORDER);
     crypto_stream->WriteStreamData(offset, crypto_message.size(), &writer);
   }
-  EXPECT_TRUE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_TRUE(crypto_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
   EXPECT_FALSE(session_.HasDataToWrite());
@@ -1663,7 +1663,7 @@
       &session_,
       QuicUtils::GetCryptoStreamId(connection_->transport_version())));
   // Stream is now unblocked and will no longer have buffered data.
-  EXPECT_FALSE(crypto_stream->flow_controller()->IsBlocked());
+  EXPECT_FALSE(crypto_stream->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
 }
@@ -1711,9 +1711,9 @@
   session_.OnStreamFrame(frame);
   EXPECT_TRUE(connection_->connected());
 
-  EXPECT_EQ(0u, stream->flow_controller()->bytes_consumed());
+  EXPECT_EQ(0u, session_.flow_controller()->bytes_consumed());
   EXPECT_EQ(kByteOffset + frame.data_length,
-            stream->flow_controller()->highest_received_byte_offset());
+            stream->highest_received_byte_offset());
 
   // Reset stream locally.
   EXPECT_CALL(*connection_, SendControlFrame(_));
@@ -3057,7 +3057,7 @@
   // blocked.
   QuicSessionPeer::SetMaxOpenOutgoingBidirectionalStreams(&session_, 10);
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
-  EXPECT_TRUE(stream2->flow_controller()->IsBlocked());
+  EXPECT_TRUE(stream2->IsFlowControlBlocked());
   EXPECT_TRUE(session_.IsConnectionFlowControlBlocked());
   EXPECT_TRUE(session_.IsStreamFlowControlBlocked());
 
@@ -3069,7 +3069,7 @@
   session_.OnConfigNegotiated();
 
   // Stream is now unblocked.
-  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+  EXPECT_FALSE(stream2->IsFlowControlBlocked());
   EXPECT_FALSE(session_.IsConnectionFlowControlBlocked());
   EXPECT_FALSE(session_.IsStreamFlowControlBlocked());
 }
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc
index cafad49..c13c05c 100644
--- a/quic/core/quic_stream.cc
+++ b/quic/core/quic_stream.cc
@@ -1313,20 +1313,28 @@
   session_->SendStopSending(code, id_);
 }
 
-QuicFlowController* QuicStream::flow_controller() {
-  if (flow_controller_.has_value()) {
-    return &flow_controller_.value();
+bool QuicStream::IsFlowControlBlocked() const {
+  if (!flow_controller_.has_value()) {
+    QUIC_BUG << "Trying to access non-existent flow controller.";
+    return false;
   }
-  QUIC_BUG << "Trying to access non-existent flow controller.";
-  return nullptr;
+  return flow_controller_->IsBlocked();
 }
 
-const QuicFlowController* QuicStream::flow_controller() const {
-  if (flow_controller_.has_value()) {
-    return &flow_controller_.value();
+QuicStreamOffset QuicStream::highest_received_byte_offset() const {
+  if (!flow_controller_.has_value()) {
+    QUIC_BUG << "Trying to access non-existent flow controller.";
+    return 0;
   }
-  QUIC_BUG << "Trying to access non-existent flow controller.";
-  return nullptr;
+  return flow_controller_->highest_received_byte_offset();
+}
+
+void QuicStream::UpdateReceiveWindowSize(QuicStreamOffset size) {
+  if (!flow_controller_.has_value()) {
+    QUIC_BUG << "Trying to access non-existent flow controller.";
+    return;
+  }
+  flow_controller_->UpdateReceiveWindowSize(size);
 }
 
 // static
diff --git a/quic/core/quic_stream.h b/quic/core/quic_stream.h
index a01d163..3590190 100644
--- a/quic/core/quic_stream.h
+++ b/quic/core/quic_stream.h
@@ -228,9 +228,10 @@
   int num_frames_received() const;
   int num_duplicate_frames_received() const;
 
-  QuicFlowController* flow_controller();
-
-  const QuicFlowController* flow_controller() const;
+  // Flow controller related methods.
+  bool IsFlowControlBlocked() const;
+  QuicStreamOffset highest_received_byte_offset() const;
+  void UpdateReceiveWindowSize(QuicStreamOffset size);
 
   // Called when endpoint receives a frame which could increase the highest
   // offset.
diff --git a/quic/core/quic_stream_test.cc b/quic/core/quic_stream_test.cc
index 1c76eb9..206fb71 100644
--- a/quic/core/quic_stream_test.cc
+++ b/quic/core/quic_stream_test.cc
@@ -238,8 +238,7 @@
   EXPECT_EQ(3u, stream.stream_bytes_read());
   EXPECT_EQ(1, stream.num_duplicate_frames_received());
   EXPECT_EQ(true, stream.fin_received());
-  EXPECT_EQ(frame2.offset + 1,
-            stream.flow_controller()->highest_received_byte_offset());
+  EXPECT_EQ(frame2.offset + 1, stream.highest_received_byte_offset());
   EXPECT_EQ(frame2.offset + 1,
             session_->flow_controller()->highest_received_byte_offset());
 }
@@ -262,8 +261,7 @@
   EXPECT_EQ(2, stream->num_frames_received());
   EXPECT_EQ(2u, stream->stream_bytes_read());
   EXPECT_EQ(true, stream->fin_received());
-  EXPECT_EQ(frame2.offset + 1,
-            stream->flow_controller()->highest_received_byte_offset());
+  EXPECT_EQ(frame2.offset + 1, stream->highest_received_byte_offset());
   EXPECT_EQ(frame2.offset + 1,
             session_->flow_controller()->highest_received_byte_offset());
 }
@@ -523,16 +521,15 @@
   // want to make sure we latch the largest offset we see.
 
   // Initially should be default.
-  EXPECT_EQ(
-      kMinimumFlowControlSendWindow,
-      QuicFlowControllerPeer::SendWindowOffset(stream_->flow_controller()));
+  EXPECT_EQ(kMinimumFlowControlSendWindow,
+            QuicStreamPeer::SendWindowOffset(stream_));
 
   // Check a single WINDOW_UPDATE results in correct offset.
   QuicWindowUpdateFrame window_update_1(kInvalidControlFrameId, stream_->id(),
                                         kMinimumFlowControlSendWindow + 5);
   stream_->OnWindowUpdateFrame(window_update_1);
-  EXPECT_EQ(window_update_1.max_data, QuicFlowControllerPeer::SendWindowOffset(
-                                          stream_->flow_controller()));
+  EXPECT_EQ(window_update_1.max_data,
+            QuicStreamPeer::SendWindowOffset(stream_));
 
   // Now send a few more WINDOW_UPDATES and make sure that only the largest is
   // remembered.
@@ -545,8 +542,8 @@
   stream_->OnWindowUpdateFrame(window_update_2);
   stream_->OnWindowUpdateFrame(window_update_3);
   stream_->OnWindowUpdateFrame(window_update_4);
-  EXPECT_EQ(window_update_3.max_data, QuicFlowControllerPeer::SendWindowOffset(
-                                          stream_->flow_controller()));
+  EXPECT_EQ(window_update_3.max_data,
+            QuicStreamPeer::SendWindowOffset(stream_));
 }
 
 TEST_P(QuicStreamTest, FrameStats) {
@@ -576,8 +573,7 @@
   // higher than the receive window offset.
   QuicStreamFrame frame(stream_->id(), false,
                         kInitialSessionFlowControlWindowForTest + 1, ".");
-  EXPECT_GT(frame.offset, QuicFlowControllerPeer::ReceiveWindowOffset(
-                              stream_->flow_controller()));
+  EXPECT_GT(frame.offset, QuicStreamPeer::ReceiveWindowOffset(stream_));
 
   // Stream should not accept the frame, and the connection should be closed.
   EXPECT_CALL(*connection_,
@@ -607,9 +603,8 @@
     QuicStreamFrame frame(stream_->id(), false, offset, data);
     stream_->OnStreamFrame(frame);
   }
-  EXPECT_LT(
-      kInitialStreamFlowControlWindowForTest,
-      QuicFlowControllerPeer::ReceiveWindowOffset(stream_->flow_controller()));
+  EXPECT_LT(kInitialStreamFlowControlWindowForTest,
+            QuicStreamPeer::ReceiveWindowOffset(stream_));
 }
 
 TEST_P(QuicStreamTest, FinalByteOffsetFromFin) {
@@ -664,7 +659,7 @@
   const QuicStreamOffset kByteOffsetExceedingFlowControlWindow =
       kInitialSessionFlowControlWindowForTest + 1;
   const QuicStreamOffset current_stream_flow_control_offset =
-      QuicFlowControllerPeer::ReceiveWindowOffset(stream_->flow_controller());
+      QuicStreamPeer::ReceiveWindowOffset(stream_);
   const QuicStreamOffset current_connection_flow_control_offset =
       QuicFlowControllerPeer::ReceiveWindowOffset(session_->flow_controller());
   ASSERT_GT(kByteOffsetExceedingFlowControlWindow,
@@ -681,9 +676,8 @@
   EXPECT_TRUE(stream_->HasReceivedFinalOffset());
 
   // The flow control receive offset values should not have changed.
-  EXPECT_EQ(
-      current_stream_flow_control_offset,
-      QuicFlowControllerPeer::ReceiveWindowOffset(stream_->flow_controller()));
+  EXPECT_EQ(current_stream_flow_control_offset,
+            QuicStreamPeer::ReceiveWindowOffset(stream_));
   EXPECT_EQ(
       current_connection_flow_control_offset,
       QuicFlowControllerPeer::ReceiveWindowOffset(session_->flow_controller()));
@@ -702,8 +696,7 @@
 
   // Modify receive window offset and sequencer buffer total_bytes_read_ to
   // avoid flow control violation.
-  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
-                                                 kMaxStreamLength + 5u);
+  QuicStreamPeer::SetReceiveWindowOffset(stream_, kMaxStreamLength + 5u);
   QuicFlowControllerPeer::SetReceiveWindowOffset(session_->flow_controller(),
                                                  kMaxStreamLength + 5u);
   QuicStreamSequencerPeer::SetFrameBufferTotalBytesRead(
diff --git a/quic/test_tools/quic_stream_peer.cc b/quic/test_tools/quic_stream_peer.cc
index 50eec57..aaf4bc3 100644
--- a/quic/test_tools/quic_stream_peer.cc
+++ b/quic/test_tools/quic_stream_peer.cc
@@ -7,6 +7,8 @@
 #include <list>
 
 #include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_flow_controller_peer.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_stream_send_buffer_peer.h"
 
 namespace quic {
@@ -28,6 +30,52 @@
 }
 
 // static
+void QuicStreamPeer::SetSendWindowOffset(QuicStream* stream,
+                                         QuicStreamOffset offset) {
+  QuicFlowControllerPeer::SetSendWindowOffset(&*stream->flow_controller_,
+                                              offset);
+}
+
+// static
+QuicByteCount QuicStreamPeer::bytes_consumed(QuicStream* stream) {
+  return stream->flow_controller_->bytes_consumed();
+}
+
+// static
+void QuicStreamPeer::SetReceiveWindowOffset(QuicStream* stream,
+                                            QuicStreamOffset offset) {
+  QuicFlowControllerPeer::SetReceiveWindowOffset(&*stream->flow_controller_,
+                                                 offset);
+}
+
+// static
+void QuicStreamPeer::SetMaxReceiveWindow(QuicStream* stream,
+                                         QuicStreamOffset size) {
+  QuicFlowControllerPeer::SetMaxReceiveWindow(&*stream->flow_controller_, size);
+}
+
+// static
+QuicByteCount QuicStreamPeer::SendWindowSize(QuicStream* stream) {
+  return stream->flow_controller_->SendWindowSize();
+}
+
+// static
+QuicStreamOffset QuicStreamPeer::ReceiveWindowOffset(QuicStream* stream) {
+  return QuicFlowControllerPeer::ReceiveWindowOffset(
+      &*stream->flow_controller_);
+}
+
+// static
+QuicByteCount QuicStreamPeer::ReceiveWindowSize(QuicStream* stream) {
+  return QuicFlowControllerPeer::ReceiveWindowSize(&*stream->flow_controller_);
+}
+
+// static
+QuicStreamOffset QuicStreamPeer::SendWindowOffset(QuicStream* stream) {
+  return stream->flow_controller_->send_window_offset();
+}
+
+// static
 bool QuicStreamPeer::read_side_closed(QuicStream* stream) {
   return stream->read_side_closed_;
 }
diff --git a/quic/test_tools/quic_stream_peer.h b/quic/test_tools/quic_stream_peer.h
index e57825f..8d9a136 100644
--- a/quic/test_tools/quic_stream_peer.h
+++ b/quic/test_tools/quic_stream_peer.h
@@ -10,6 +10,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_packets.h"
 #include "net/third_party/quiche/src/quic/core/quic_stream_send_buffer.h"
 #include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
 
 namespace quic {
 
@@ -25,8 +26,17 @@
   static void SetWriteSideClosed(bool value, QuicStream* stream);
   static void SetStreamBytesWritten(QuicStreamOffset stream_bytes_written,
                                     QuicStream* stream);
+  static void SetSendWindowOffset(QuicStream* stream, QuicStreamOffset offset);
+  static void SetReceiveWindowOffset(QuicStream* stream,
+                                     QuicStreamOffset offset);
+  static void SetMaxReceiveWindow(QuicStream* stream, QuicStreamOffset size);
   static bool read_side_closed(QuicStream* stream);
   static void CloseReadSide(QuicStream* stream);
+  static QuicByteCount bytes_consumed(QuicStream* stream);
+  static QuicByteCount ReceiveWindowSize(QuicStream* stream);
+  static QuicByteCount SendWindowSize(QuicStream* stream);
+  static QuicStreamOffset SendWindowOffset(QuicStream* stream);
+  static QuicStreamOffset ReceiveWindowOffset(QuicStream* stream);
 
   static bool StreamContributesToConnectionFlowControl(QuicStream* stream);