Include offset information in BLOCKED frames

We use the latest send window offset info from the stream or connection flow controller. Also, include the offset when we log the receipt of a blocked frame.

Protected by FLAGS_quic_reloadable_flag_quic_include_offset_in_blocked_frames.

PiperOrigin-RevId: 449514365
diff --git a/quiche/quic/core/frames/quic_blocked_frame.cc b/quiche/quic/core/frames/quic_blocked_frame.cc
index a3e8ebe..594de27 100644
--- a/quiche/quic/core/frames/quic_blocked_frame.cc
+++ b/quiche/quic/core/frames/quic_blocked_frame.cc
@@ -11,13 +11,6 @@
 QuicBlockedFrame::QuicBlockedFrame() : QuicInlinedFrame(BLOCKED_FRAME) {}
 
 QuicBlockedFrame::QuicBlockedFrame(QuicControlFrameId control_frame_id,
-                                   QuicStreamId stream_id)
-    : QuicInlinedFrame(BLOCKED_FRAME),
-      control_frame_id(control_frame_id),
-      stream_id(stream_id),
-      offset(0) {}
-
-QuicBlockedFrame::QuicBlockedFrame(QuicControlFrameId control_frame_id,
                                    QuicStreamId stream_id,
                                    QuicStreamOffset offset)
     : QuicInlinedFrame(BLOCKED_FRAME),
@@ -28,7 +21,8 @@
 std::ostream& operator<<(std::ostream& os,
                          const QuicBlockedFrame& blocked_frame) {
   os << "{ control_frame_id: " << blocked_frame.control_frame_id
-     << ", stream_id: " << blocked_frame.stream_id << " }\n";
+     << ", stream_id: " << blocked_frame.stream_id
+     << ", offset: " << blocked_frame.offset << " }\n";
   return os;
 }
 
diff --git a/quiche/quic/core/frames/quic_blocked_frame.h b/quiche/quic/core/frames/quic_blocked_frame.h
index c3f45f3..982e1e8 100644
--- a/quiche/quic/core/frames/quic_blocked_frame.h
+++ b/quiche/quic/core/frames/quic_blocked_frame.h
@@ -20,7 +20,6 @@
 struct QUIC_EXPORT_PRIVATE QuicBlockedFrame
     : public QuicInlinedFrame<QuicBlockedFrame> {
   QuicBlockedFrame();
-  QuicBlockedFrame(QuicControlFrameId control_frame_id, QuicStreamId stream_id);
   QuicBlockedFrame(QuicControlFrameId control_frame_id, QuicStreamId stream_id,
                    QuicStreamOffset offset);
 
diff --git a/quiche/quic/core/frames/quic_frames_test.cc b/quiche/quic/core/frames/quic_frames_test.cc
index 5626914..671e772 100644
--- a/quiche/quic/core/frames/quic_frames_test.cc
+++ b/quiche/quic/core/frames/quic_frames_test.cc
@@ -252,9 +252,10 @@
   SetControlFrameId(4, &frame);
   EXPECT_EQ(4u, GetControlFrameId(frame));
   frame.blocked_frame.stream_id = 1;
+  frame.blocked_frame.offset = 2;
   std::ostringstream stream;
   stream << frame.blocked_frame;
-  EXPECT_EQ("{ control_frame_id: 4, stream_id: 1 }\n", stream.str());
+  EXPECT_EQ("{ control_frame_id: 4, stream_id: 1, offset: 2 }\n", stream.str());
   EXPECT_TRUE(IsControlFrame(frame.type));
 }
 
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 25a3af7..761f592 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -17,10 +17,12 @@
 #include "absl/time/time.h"
 #include "quiche/quic/core/crypto/null_encrypter.h"
 #include "quiche/quic/core/crypto/quic_client_session_cache.h"
+#include "quiche/quic/core/frames/quic_blocked_frame.h"
 #include "quiche/quic/core/http/http_constants.h"
 #include "quiche/quic/core/http/quic_spdy_client_stream.h"
 #include "quiche/quic/core/http/web_transport_http3.h"
 #include "quiche/quic/core/quic_connection.h"
+#include "quiche/quic/core/quic_constants.h"
 #include "quiche/quic/core/quic_data_writer.h"
 #include "quiche/quic/core/quic_epoll_connection_helper.h"
 #include "quiche/quic/core/quic_error_codes.h"
@@ -4476,6 +4478,72 @@
   int64_t body_bytes_;
 };
 
+class BlockedFrameObserver : public QuicConnectionDebugVisitor {
+ public:
+  std::vector<QuicBlockedFrame> blocked_frames() const {
+    return blocked_frames_;
+  }
+
+  void OnBlockedFrame(const QuicBlockedFrame& frame) override {
+    blocked_frames_.push_back(frame);
+  }
+
+ private:
+  std::vector<QuicBlockedFrame> blocked_frames_;
+};
+
+TEST_P(EndToEndTest, BlockedFrameIncludesOffset) {
+  if (!version_.HasIetfQuicFrames()) {
+    // For Google QUIC, the BLOCKED frame offset is ignored.
+    Initialize();
+    return;
+  }
+
+  set_smaller_flow_control_receive_window();
+  ASSERT_TRUE(Initialize());
+
+  // Observe the connection for BLOCKED frames.
+  BlockedFrameObserver observer;
+  QuicConnection* client_connection = GetClientConnection();
+  ASSERT_TRUE(client_connection);
+  client_connection->set_debug_visitor(&observer);
+
+  // Set the response body larger than the flow control window so the server
+  // must receive a window update from the client before it can finish sending
+  // it (hence, causing the server to send a BLOCKED frame)
+  uint32_t response_body_size =
+      client_config_.GetInitialSessionFlowControlWindowToSend() + 10;
+  std::string response_body(response_body_size, 'a');
+  AddToCache("/blocked", 200, response_body);
+  SendSynchronousRequestAndCheckResponse("/blocked", response_body);
+  client_->Disconnect();
+
+  bool include_offset_flag =
+      GetQuicReloadableFlag(quic_include_offset_in_blocked_frames);
+  QuicStreamOffset expected_connection_offset =
+      include_offset_flag
+          ? client_config_.GetInitialSessionFlowControlWindowToSend()
+          : 0;
+  QuicStreamOffset expected_stream_offset =
+      include_offset_flag
+          ? client_config_.GetInitialStreamFlowControlWindowToSend()
+          : 0;
+
+  ASSERT_GE(observer.blocked_frames().size(), static_cast<uint64_t>(0));
+  for (const QuicBlockedFrame& frame : observer.blocked_frames()) {
+    if (frame.stream_id ==
+        QuicUtils::GetInvalidStreamId(version_.transport_version)) {
+      // connection-level BLOCKED frame
+      ASSERT_EQ(frame.offset, expected_connection_offset);
+    } else {
+      // stream-level BLOCKED frame
+      ASSERT_EQ(frame.offset, expected_stream_offset);
+    }
+  }
+
+  client_connection->set_debug_visitor(nullptr);
+}
+
 TEST_P(EndToEndTest, EarlyResponseFinRecording) {
   set_smaller_flow_control_receive_window();
 
diff --git a/quiche/quic/core/http/quic_spdy_stream_test.cc b/quiche/quic/core/http/quic_spdy_stream_test.cc
index 3c00c7b..834c51a 100644
--- a/quiche/quic/core/http/quic_spdy_stream_test.cc
+++ b/quiche/quic/core/http/quic_spdy_stream_test.cc
@@ -999,7 +999,7 @@
   }
   EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _))
       .WillOnce(Return(QuicConsumedData(kWindow - kHeaderLength, true)));
-  EXPECT_CALL(*session_, SendBlocked(_));
+  EXPECT_CALL(*session_, SendBlocked(_, _));
   EXPECT_CALL(*connection_, SendControlFrame(_));
   stream_->WriteOrBufferBody(body, false);
 
@@ -1254,7 +1254,8 @@
   std::string body = "";
   bool fin = true;
 
-  EXPECT_CALL(*session_, SendBlocked(GetNthClientInitiatedBidirectionalId(0)))
+  EXPECT_CALL(*session_,
+              SendBlocked(GetNthClientInitiatedBidirectionalId(0), _))
       .Times(0);
   EXPECT_CALL(*session_, WritevData(_, 0, _, FIN, _, _));
 
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index cc7e38e..505c809 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -6931,7 +6931,7 @@
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   EXPECT_CALL(debug_visitor, OnPacketSent(_, _, _, _, _, _, _, _)).Times(1);
   EXPECT_EQ(0u, connection_.GetStats().blocked_frames_sent);
-  connection_.SendControlFrame(QuicFrame(QuicBlockedFrame(1, 3)));
+  connection_.SendControlFrame(QuicFrame(QuicBlockedFrame(1, 3, 0)));
   EXPECT_EQ(1u, connection_.GetStats().blocked_frames_sent);
   EXPECT_FALSE(connection_.HasQueuedData());
 }
@@ -6942,7 +6942,7 @@
   }
   MockQuicConnectionDebugVisitor debug_visitor;
   connection_.set_debug_visitor(&debug_visitor);
-  QuicBlockedFrame blocked(1, 3);
+  QuicBlockedFrame blocked(1, 3, 0);
 
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
   EXPECT_CALL(debug_visitor, OnPacketSent(_, _, _, _, _, _, _, _)).Times(0);
@@ -9264,14 +9264,14 @@
         .Times(3)
         .WillRepeatedly([this](const QuicCryptoFrame& /*frame*/) {
           // QuicFrame takes ownership of the QuicBlockedFrame.
-          connection_.SendControlFrame(QuicFrame(QuicBlockedFrame(1, 3)));
+          connection_.SendControlFrame(QuicFrame(QuicBlockedFrame(1, 3, 0)));
         });
   } else {
     EXPECT_CALL(visitor_, OnStreamFrame(_))
         .Times(3)
         .WillRepeatedly([this](const QuicStreamFrame& /*frame*/) {
           // QuicFrame takes ownership of the QuicBlockedFrame.
-          connection_.SendControlFrame(QuicFrame(QuicBlockedFrame(1, 3)));
+          connection_.SendControlFrame(QuicFrame(QuicBlockedFrame(1, 3, 0)));
         });
   }
 
diff --git a/quiche/quic/core/quic_control_frame_manager.cc b/quiche/quic/core/quic_control_frame_manager.cc
index df7b263..d22ab04 100644
--- a/quiche/quic/core/quic_control_frame_manager.cc
+++ b/quiche/quic/core/quic_control_frame_manager.cc
@@ -17,6 +17,7 @@
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
 #include "quiche/quic/platform/api/quic_flag_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
 
 namespace quic {
 
@@ -81,10 +82,17 @@
       QuicWindowUpdateFrame(++last_control_frame_id_, id, byte_offset)));
 }
 
-void QuicControlFrameManager::WriteOrBufferBlocked(QuicStreamId id) {
+void QuicControlFrameManager::WriteOrBufferBlocked(
+    QuicStreamId id, QuicStreamOffset byte_offset) {
   QUIC_DVLOG(1) << "Writing BLOCKED_FRAME";
-  WriteOrBufferQuicFrame(
-      QuicFrame(QuicBlockedFrame(++last_control_frame_id_, id)));
+  if (GetQuicReloadableFlag(quic_include_offset_in_blocked_frames)) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_include_offset_in_blocked_frames);
+    WriteOrBufferQuicFrame(
+        QuicFrame(QuicBlockedFrame(++last_control_frame_id_, id, byte_offset)));
+  } else {
+    WriteOrBufferQuicFrame(
+        QuicFrame(QuicBlockedFrame(++last_control_frame_id_, id, 0)));
+  }
 }
 
 void QuicControlFrameManager::WriteOrBufferStreamsBlocked(QuicStreamCount count,
diff --git a/quiche/quic/core/quic_control_frame_manager.h b/quiche/quic/core/quic_control_frame_manager.h
index b885301..46a0f47 100644
--- a/quiche/quic/core/quic_control_frame_manager.h
+++ b/quiche/quic/core/quic_control_frame_manager.h
@@ -69,7 +69,7 @@
 
   // Tries to send a BLOCKED_FRAME. Buffers the frame if it cannot be sent
   // immediately.
-  void WriteOrBufferBlocked(QuicStreamId id);
+  void WriteOrBufferBlocked(QuicStreamId id, QuicStreamOffset byte_offset);
 
   // Tries to send a STREAMS_BLOCKED Frame. Buffers the frame if it cannot be
   // sent immediately.
diff --git a/quiche/quic/core/quic_control_frame_manager_test.cc b/quiche/quic/core/quic_control_frame_manager_test.cc
index 821bcca..e1d2538 100644
--- a/quiche/quic/core/quic_control_frame_manager_test.cc
+++ b/quiche/quic/core/quic_control_frame_manager_test.cc
@@ -72,7 +72,7 @@
     manager_->WriteOrBufferGoAway(QUIC_PEER_GOING_AWAY, kTestStreamId,
                                   "Going away.");
     manager_->WriteOrBufferWindowUpdate(kTestStreamId, 100);
-    manager_->WriteOrBufferBlocked(kTestStreamId);
+    manager_->WriteOrBufferBlocked(kTestStreamId, 0);
     manager_->WriteOrBufferStopSending(
         QuicResetStreamError::FromInternal(kTestStopSendingCode),
         kTestStreamId);
@@ -93,7 +93,7 @@
   QuicGoAwayFrame goaway_ = {2, QUIC_PEER_GOING_AWAY, kTestStreamId,
                              "Going away."};
   QuicWindowUpdateFrame window_update_ = {3, kTestStreamId, 100};
-  QuicBlockedFrame blocked_ = {4, kTestStreamId};
+  QuicBlockedFrame blocked_ = {4, kTestStreamId, 0};
   QuicStopSendingFrame stop_sending_ = {5, kTestStreamId, kTestStopSendingCode};
   MockQuicConnectionHelper helper_;
   MockAlarmFactory alarm_factory_;
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 96db0f0..ddb7948 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -63,6 +63,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_flush_pending_frames_and_padding_bytes_on_migration, true)
 // If true, ietf connection migration is no longer conditioned on connection option RVCM.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_remove_connection_migration_connection_option_v2, false)
+// If true, include offset in QUIC STREAM_DATA_BLOCKED and DATA_BLOCKED frames.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_include_offset_in_blocked_frames, false)
 // If true, include stream information in idle timeout connection close detail.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_stream_info_to_idle_close_detail, true)
 // If true, quic server will send ENABLE_CONNECT_PROTOCOL setting and and endpoint will validate required request/response headers and extended CONNECT mechanism and update code counts of valid/invalid headers.
diff --git a/quiche/quic/core/quic_flow_controller.cc b/quiche/quic/core/quic_flow_controller.cc
index 47d3c28..4acdd51 100644
--- a/quiche/quic/core/quic_flow_controller.cc
+++ b/quiche/quic/core/quic_flow_controller.cc
@@ -232,10 +232,10 @@
   SendWindowUpdate();
 }
 
-bool QuicFlowController::ShouldSendBlocked() {
+void QuicFlowController::MaybeSendBlocked() {
   if (SendWindowSize() != 0 ||
       last_blocked_send_window_offset_ >= send_window_offset_) {
-    return false;
+    return;
   }
   QUIC_DLOG(INFO) << ENDPOINT << LogLabel() << " is flow control blocked. "
                   << "Send window: " << SendWindowSize()
@@ -247,7 +247,7 @@
   // Keep track of when we last sent a BLOCKED frame so that we only send one
   // at a given send offset.
   last_blocked_send_window_offset_ = send_window_offset_;
-  return true;
+  session_->SendBlocked(id_, last_blocked_send_window_offset_);
 }
 
 bool QuicFlowController::UpdateSendWindowOffset(
diff --git a/quiche/quic/core/quic_flow_controller.h b/quiche/quic/core/quic_flow_controller.h
index 7f68ff5..eaf660a 100644
--- a/quiche/quic/core/quic_flow_controller.h
+++ b/quiche/quic/core/quic_flow_controller.h
@@ -78,8 +78,8 @@
 
   QuicByteCount receive_window_size() const { return receive_window_size_; }
 
-  // Returns whether a BLOCKED frame should be sent.
-  bool ShouldSendBlocked();
+  // Sends a BLOCKED frame if needed.
+  void MaybeSendBlocked();
 
   // Returns true if flow control send limits have been reached.
   bool IsBlocked() const;
diff --git a/quiche/quic/core/quic_flow_controller_test.cc b/quiche/quic/core/quic_flow_controller_test.cc
index 20cb599..a03cff0 100644
--- a/quiche/quic/core/quic_flow_controller_test.cc
+++ b/quiche/quic/core/quic_flow_controller_test.cc
@@ -18,6 +18,7 @@
 
 using testing::_;
 using testing::Invoke;
+using testing::StrictMock;
 
 namespace quic {
 namespace test {
@@ -43,7 +44,7 @@
     connection_->SetEncrypter(
         ENCRYPTION_FORWARD_SECURE,
         std::make_unique<NullEncrypter>(connection_->perspective()));
-    session_ = std::make_unique<MockQuicSession>(connection_);
+    session_ = std::make_unique<StrictMock<MockQuicSession>>(connection_);
     flow_controller_ = std::make_unique<QuicFlowController>(
         session_.get(), stream_id_, /*is_connection_flow_controller*/ false,
         send_window_, receive_window_, kStreamReceiveWindowLimit,
@@ -58,7 +59,7 @@
   MockQuicConnectionHelper helper_;
   MockAlarmFactory alarm_factory_;
   MockQuicConnection* connection_;
-  std::unique_ptr<MockQuicSession> session_;
+  std::unique_ptr<StrictMock<MockQuicSession>> session_;
   MockFlowController session_flow_controller_;
   bool should_auto_tune_receive_window_ = false;
 };
@@ -81,7 +82,8 @@
   EXPECT_EQ(0u, flow_controller_->SendWindowSize());
 
   // BLOCKED frame should get sent.
-  EXPECT_TRUE(flow_controller_->ShouldSendBlocked());
+  EXPECT_CALL(*session_, SendBlocked(_, _)).Times(1);
+  flow_controller_->MaybeSendBlocked();
 
   // Update the send window, and verify this has unblocked.
   EXPECT_TRUE(flow_controller_->UpdateSendWindowOffset(2 * send_window_));
@@ -164,14 +166,16 @@
   EXPECT_EQ(0u, flow_controller_->SendWindowSize());
 
   // BLOCKED frame should get sent.
-  EXPECT_TRUE(flow_controller_->ShouldSendBlocked());
+  EXPECT_CALL(*session_, SendBlocked(_, _)).Times(1);
+  flow_controller_->MaybeSendBlocked();
 
   // BLOCKED frame should not get sent again until our send offset changes.
-  EXPECT_FALSE(flow_controller_->ShouldSendBlocked());
-  EXPECT_FALSE(flow_controller_->ShouldSendBlocked());
-  EXPECT_FALSE(flow_controller_->ShouldSendBlocked());
-  EXPECT_FALSE(flow_controller_->ShouldSendBlocked());
-  EXPECT_FALSE(flow_controller_->ShouldSendBlocked());
+  EXPECT_CALL(*session_, SendBlocked(_, _)).Times(0);
+  flow_controller_->MaybeSendBlocked();
+  flow_controller_->MaybeSendBlocked();
+  flow_controller_->MaybeSendBlocked();
+  flow_controller_->MaybeSendBlocked();
+  flow_controller_->MaybeSendBlocked();
 
   // Update the send window, then send enough bytes to block again.
   EXPECT_TRUE(flow_controller_->UpdateSendWindowOffset(2 * send_window_));
@@ -182,7 +186,8 @@
   EXPECT_EQ(0u, flow_controller_->SendWindowSize());
 
   // BLOCKED frame should get sent as send offset has changed.
-  EXPECT_TRUE(flow_controller_->ShouldSendBlocked());
+  EXPECT_CALL(*session_, SendBlocked(_, _)).Times(1);
+  flow_controller_->MaybeSendBlocked();
 }
 
 TEST_F(QuicFlowControllerTest, ReceivingBytesFastIncreasesFlowWindow) {
diff --git a/quiche/quic/core/quic_session.cc b/quiche/quic/core/quic_session.cc
index 807bfdd..8cd343d 100644
--- a/quiche/quic/core/quic_session.cc
+++ b/quiche/quic/core/quic_session.cc
@@ -543,7 +543,7 @@
   //                streams: if we have a large window then maybe something
   //                had gone wrong with the flow control accounting.
   QUIC_DLOG(INFO) << ENDPOINT << "Received BLOCKED frame with stream id: "
-                  << frame.stream_id;
+                  << frame.stream_id << ", offset: " << frame.offset;
 }
 
 bool QuicSession::CheckStreamNotBusyLooping(QuicStream* stream,
@@ -939,8 +939,8 @@
       reason);
 }
 
-void QuicSession::SendBlocked(QuicStreamId id) {
-  control_frame_manager_.WriteOrBufferBlocked(id);
+void QuicSession::SendBlocked(QuicStreamId id, QuicStreamOffset byte_offset) {
+  control_frame_manager_.WriteOrBufferBlocked(id, byte_offset);
 }
 
 void QuicSession::SendWindowUpdate(QuicStreamId id,
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h
index c6d5ec2..6cc9795 100644
--- a/quiche/quic/core/quic_session.h
+++ b/quiche/quic/core/quic_session.h
@@ -266,7 +266,7 @@
   virtual void SendGoAway(QuicErrorCode error_code, const std::string& reason);
 
   // Sends a BLOCKED frame.
-  virtual void SendBlocked(QuicStreamId id);
+  virtual void SendBlocked(QuicStreamId id, QuicStreamOffset byte_offset);
 
   // Sends a WINDOW_UPDATE frame.
   virtual void SendWindowUpdate(QuicStreamId id, QuicStreamOffset byte_offset);
diff --git a/quiche/quic/core/quic_stream.cc b/quiche/quic/core/quic_stream.cc
index c07f2de..dcd07fb 100644
--- a/quiche/quic/core/quic_stream.cc
+++ b/quiche/quic/core/quic_stream.cc
@@ -741,15 +741,12 @@
         << ENDPOINT << "MaybeSendBlocked called on stream without flow control";
     return;
   }
-  if (flow_controller_->ShouldSendBlocked()) {
-    session_->SendBlocked(id_);
-  }
+  flow_controller_->MaybeSendBlocked();
   if (!stream_contributes_to_connection_flow_control_) {
     return;
   }
-  if (connection_flow_controller_->ShouldSendBlocked()) {
-    session_->SendBlocked(QuicUtils::GetInvalidStreamId(transport_version()));
-  }
+  connection_flow_controller_->MaybeSendBlocked();
+
   // If the stream is blocked by connection-level flow control but not by
   // stream-level flow control, add the stream to the write blocked list so that
   // the stream will be given a chance to write when a connection-level
diff --git a/quiche/quic/core/quic_stream_test.cc b/quiche/quic/core/quic_stream_test.cc
index dad793f..e96a99d 100644
--- a/quiche/quic/core/quic_stream_test.cc
+++ b/quiche/quic/core/quic_stream_test.cc
@@ -1510,8 +1510,7 @@
 
   EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _))
       .WillRepeatedly(Invoke(session_.get(), &MockQuicSession::ConsumeData));
-  EXPECT_CALL(*session_, WriteControlFrame(_, _))
-      .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
+  EXPECT_CALL(*session_, SendBlocked(_, _)).Times(1);
   std::string data(1024, '.');
   stream->WriteOrBufferData(data, false, nullptr);
   EXPECT_FALSE(HasWriteBlockedStreams());
@@ -1544,8 +1543,7 @@
   std::string data(100, '.');
   EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _))
       .WillRepeatedly(Invoke(session_.get(), &MockQuicSession::ConsumeData));
-  EXPECT_CALL(*session_, WriteControlFrame(_, _))
-      .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
+  EXPECT_CALL(*session_, SendBlocked(_, _)).Times(1);
   stream->WriteOrBufferData(data, false, nullptr);
   EXPECT_FALSE(HasWriteBlockedStreams());
 
diff --git a/quiche/quic/core/quic_unacked_packet_map_test.cc b/quiche/quic/core/quic_unacked_packet_map_test.cc
index 0540164..fc8ea98 100644
--- a/quiche/quic/core/quic_unacked_packet_map_test.cc
+++ b/quiche/quic/core/quic_unacked_packet_map_test.cc
@@ -551,7 +551,7 @@
   QuicWindowUpdateFrame window_update(1, 5, 100);
   QuicStreamFrame stream_frame1(3, false, 0, 100);
   QuicStreamFrame stream_frame2(3, false, 100, 100);
-  QuicBlockedFrame blocked(2, 5);
+  QuicBlockedFrame blocked(2, 5, 0);
   QuicGoAwayFrame go_away(3, QUIC_PEER_GOING_AWAY, 5, "Going away.");
 
   QuicTransmissionInfo info1;
diff --git a/quiche/quic/test_tools/quic_test_utils.cc b/quiche/quic/test_tools/quic_test_utils.cc
index 741159a..6582ee7 100644
--- a/quiche/quic/test_tools/quic_test_utils.cc
+++ b/quiche/quic/test_tools/quic_test_utils.cc
@@ -657,9 +657,10 @@
         return QuicSpdySession::SendWindowUpdate(id, byte_offset);
       });
 
-  ON_CALL(*this, SendBlocked(_)).WillByDefault([this](QuicStreamId id) {
-    return QuicSpdySession::SendBlocked(id);
-  });
+  ON_CALL(*this, SendBlocked(_, _))
+      .WillByDefault([this](QuicStreamId id, QuicStreamOffset byte_offset) {
+        return QuicSpdySession::SendBlocked(id, byte_offset);
+      });
 
   ON_CALL(*this, OnCongestionWindowChange(_)).WillByDefault(testing::Return());
 }
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index df742be..822e6c4 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -779,6 +779,8 @@
               (override));
   MOCK_METHOD(void, MaybeSendStopSendingFrame,
               (QuicStreamId stream_id, QuicResetStreamError error), (override));
+  MOCK_METHOD(void, SendBlocked,
+              (QuicStreamId stream_id, QuicStreamOffset offset), (override));
 
   MOCK_METHOD(bool, ShouldKeepConnectionAlive, (), (const, override));
   MOCK_METHOD(std::vector<std::string>, GetAlpnsToOffer, (), (const, override));
@@ -928,7 +930,8 @@
               (QuicStreamId stream_id, QuicResetStreamError error), (override));
   MOCK_METHOD(void, SendWindowUpdate,
               (QuicStreamId id, QuicStreamOffset byte_offset), (override));
-  MOCK_METHOD(void, SendBlocked, (QuicStreamId id), (override));
+  MOCK_METHOD(void, SendBlocked,
+              (QuicStreamId id, QuicStreamOffset byte_offset), (override));
   MOCK_METHOD(void, OnStreamHeadersPriority,
               (QuicStreamId stream_id,
                const spdy::SpdyStreamPrecedence& precedence),
diff --git a/quiche/quic/tools/quic_simple_server_session_test.cc b/quiche/quic/tools/quic_simple_server_session_test.cc
index 9ede727..d1db8c9 100644
--- a/quiche/quic/tools/quic_simple_server_session_test.cc
+++ b/quiche/quic/tools/quic_simple_server_session_test.cc
@@ -179,7 +179,7 @@
                const spdy::Http2HeaderBlock& headers),
               ());
 
-  MOCK_METHOD(void, SendBlocked, (QuicStreamId), (override));
+  MOCK_METHOD(void, SendBlocked, (QuicStreamId, QuicStreamOffset), (override));
   MOCK_METHOD(bool, WriteControlFrame,
               (const QuicFrame& frame, TransmissionType type), (override));
 };