Redirect WindowUpdate frame to pending streams if they are present, and closes connection when a WindowUpdate frame is received on a READ_UNIDIRECTIONAL stream. We determined that at least in the foreseeable future, pending streams will only be READ_UNIDIRECTIONAL, so they will close connection when receives WindowUpdate frame. gfe-relnote: protected by gfe2_reloadable_flag_quic_no_window_update_on_read_only_stream. PiperOrigin-RevId: 256041719 Change-Id: Ie7caf100a890f770bb1863d6479d66a5e0820e2a
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc index cf95e1d..f13eec4 100644 --- a/quic/core/quic_error_codes.cc +++ b/quic/core/quic_error_codes.cc
@@ -155,6 +155,8 @@ RETURN_STRING_LITERAL(QUIC_HTTP_DECODER_ERROR); RETURN_STRING_LITERAL(QUIC_STALE_CONNECTION_CANCELLED); RETURN_STRING_LITERAL(QUIC_IETF_GQUIC_ERROR_MISSING); + RETURN_STRING_LITERAL( + QUIC_WINDOW_UPDATE_RECEIVED_ON_READ_UNIDIRECTIONAL_STREAM); RETURN_STRING_LITERAL(QUIC_LAST_ERROR); // Intentionally have no default case, so we'll break the build
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h index 34019de..abc666d 100644 --- a/quic/core/quic_error_codes.h +++ b/quic/core/quic_error_codes.h
@@ -331,8 +331,11 @@ // not available in a received CONNECTION_CLOSE frame. QUIC_IETF_GQUIC_ERROR_MISSING = 122, + // Received WindowUpdate on a READ_UNIDIRECTIONAL stream. + QUIC_WINDOW_UPDATE_RECEIVED_ON_READ_UNIDIRECTIONAL_STREAM = 123, + // No error. Used as bound while iterating. - QUIC_LAST_ERROR = 123, + QUIC_LAST_ERROR = 124, }; // QuicErrorCodes is encoded as a single octet on-the-wire. static_assert(static_cast<int>(QUIC_LAST_ERROR) <=
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc index d7aa91e..829b3d3 100644 --- a/quic/core/quic_session.cc +++ b/quic/core/quic_session.cc
@@ -528,6 +528,18 @@ flow_controller_.UpdateSendWindowOffset(frame.byte_offset); return; } + + if (VersionHasIetfQuicFrames(connection_->transport_version()) && + QuicUtils::GetStreamType(stream_id, perspective(), + IsIncomingStream(stream_id)) == + READ_UNIDIRECTIONAL) { + connection()->CloseConnection( + QUIC_WINDOW_UPDATE_RECEIVED_ON_READ_UNIDIRECTIONAL_STREAM, + "WindowUpdateFrame received on READ_UNIDIRECTIONAL stream.", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } + QuicStream* stream = GetOrCreateStream(stream_id); if (stream != nullptr) { stream->OnWindowUpdateFrame(frame); @@ -1279,6 +1291,7 @@ } QuicStream* QuicSession::GetOrCreateStream(const QuicStreamId stream_id) { + DCHECK(!QuicContainsKey(pending_stream_map_, stream_id)); if (eliminate_static_stream_map_ && QuicUtils::IsCryptoStreamId(connection_->transport_version(), stream_id)) {
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc index 5ea8633..6188b05 100644 --- a/quic/core/quic_session_test.cc +++ b/quic/core/quic_session_test.cc
@@ -1766,6 +1766,27 @@ EXPECT_EQ(0, session_.num_incoming_streams_created()); } +TEST_P(QuicSessionTestServer, PendingStreamOnWindowUpdate) { + if (!VersionHasIetfQuicFrames(transport_version())) { + return; + } + + session_.set_uses_pending_streams(true); + QuicStreamId stream_id = QuicUtils::GetFirstUnidirectionalStreamId( + transport_version(), Perspective::IS_CLIENT); + QuicStreamFrame data1(stream_id, true, 10, QuicStringPiece("HT")); + session_.OnStreamFrame(data1); + EXPECT_EQ(0, session_.num_incoming_streams_created()); + QuicWindowUpdateFrame window_update_frame(kInvalidControlFrameId, stream_id, + 0); + EXPECT_CALL( + *connection_, + CloseConnection( + QUIC_WINDOW_UPDATE_RECEIVED_ON_READ_UNIDIRECTIONAL_STREAM, + "WindowUpdateFrame received on READ_UNIDIRECTIONAL stream.", _)); + session_.OnWindowUpdateFrame(window_update_frame); +} + TEST_P(QuicSessionTestServer, DrainingStreamsDoNotCountAsOpened) { // Verify that a draining stream (which has received a FIN but not consumed // it) does not count against the open quota (because it is closed from the
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc index b0699d4..8ef4717 100644 --- a/quic/core/quic_stream.cc +++ b/quic/core/quic_stream.cc
@@ -737,6 +737,15 @@ } void QuicStream::OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) { + if (GetQuicReloadableFlag(quic_no_window_update_on_read_only_stream) && + type_ == READ_UNIDIRECTIONAL) { + QUIC_RELOADABLE_FLAG_COUNT(quic_no_window_update_on_read_only_stream); + CloseConnectionWithDetails( + QUIC_WINDOW_UPDATE_RECEIVED_ON_READ_UNIDIRECTIONAL_STREAM, + "WindowUpdateFrame received on READ_UNIDIRECTIONAL stream."); + return; + } + if (flow_controller_->UpdateSendWindowOffset(frame.byte_offset)) { // Let session unblock this stream. session_->MarkConnectionLevelWriteBlocked(id_);
diff --git a/quic/core/quic_stream_test.cc b/quic/core/quic_stream_test.cc index 45fae55..ee9676f 100644 --- a/quic/core/quic_stream_test.cc +++ b/quic/core/quic_stream_test.cc
@@ -1693,6 +1693,23 @@ EXPECT_TRUE(stream_->write_side_closed()); } +TEST_P(QuicStreamTest, WindowUpdateForReadOnlyStream) { + SetQuicReloadableFlag(quic_no_window_update_on_read_only_stream, true); + Initialize(); + + QuicStreamId stream_id = QuicUtils::GetFirstUnidirectionalStreamId( + connection_->transport_version(), Perspective::IS_CLIENT); + TestStream stream(stream_id, session_.get(), READ_UNIDIRECTIONAL); + QuicWindowUpdateFrame window_update_frame(kInvalidControlFrameId, stream_id, + 0); + EXPECT_CALL( + *connection_, + CloseConnection( + QUIC_WINDOW_UPDATE_RECEIVED_ON_READ_UNIDIRECTIONAL_STREAM, + "WindowUpdateFrame received on READ_UNIDIRECTIONAL stream.", _)); + stream.OnWindowUpdateFrame(window_update_frame); +} + } // namespace } // namespace test } // namespace quic