Accept empty frame with no FIN in IETF QUIC. Protected by FLAGS_quic_reloadable_flag_quic_accept_empty_stream_frame_with_no_fin. PiperOrigin-RevId: 348789512 Change-Id: I931a10c28d44dcacbfb5cf4bf2ef62b876e9c3c4
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc index e4b5dc0..0d7152a 100644 --- a/quic/core/http/quic_spdy_session_test.cc +++ b/quic/core/http/quic_spdy_session_test.cc
@@ -1907,10 +1907,12 @@ GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 0); // A bad stream frame with no data and no fin. QuicStreamFrame data1(stream_id1, false, 0, 0); - EXPECT_CALL(*connection_, CloseConnection(_, _, _)) - .WillOnce( - Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); - EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _)); + if (!GetQuicReloadableFlag(quic_accept_empty_stream_frame_with_no_fin)) { + EXPECT_CALL(*connection_, CloseConnection(_, _, _)) + .WillOnce( + Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); + EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _)); + } session_.OnStreamFrame(data1); }
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h index 8e99f37..04a2244 100644 --- a/quic/core/quic_flags_list.h +++ b/quic/core/quic_flags_list.h
@@ -6,6 +6,7 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_use_fast_huffman_encoder, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_abort_qpack_on_stream_reset, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_accept_empty_stream_frame_with_no_fin, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ack_delay_alarm_granularity, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_stream_info_to_idle_close_detail, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allocate_stream_sequencer_buffer_blocks_on_demand, false)
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc index add2715..4650f97 100644 --- a/quic/core/quic_stream.cc +++ b/quic/core/quic_stream.cc
@@ -113,6 +113,7 @@ PendingStream::PendingStream(QuicStreamId id, QuicSession* session) : id_(id), + version_(session->version()), stream_delegate_(session), stream_bytes_read_(0), fin_received_(false), @@ -157,6 +158,10 @@ return id_; } +ParsedQuicVersion PendingStream::version() const { + return version_; +} + void PendingStream::OnStreamFrame(const QuicStreamFrame& frame) { DCHECK_EQ(frame.stream_id, id_); @@ -897,6 +902,10 @@ return send_buffer_.stream_offset() > stream_bytes_written(); } +ParsedQuicVersion QuicStream::version() const { + return session_->version(); +} + QuicTransportVersion QuicStream::transport_version() const { return session_->transport_version(); }
diff --git a/quic/core/quic_stream.h b/quic/core/quic_stream.h index 50fc5e1..6138873 100644 --- a/quic/core/quic_stream.h +++ b/quic/core/quic_stream.h
@@ -62,6 +62,7 @@ void OnUnrecoverableError(QuicErrorCode error, const std::string& details) override; QuicStreamId id() const override; + ParsedQuicVersion version() const override; // Buffers the contents of |frame|. Frame must have a non-zero offset. // If the data violates flow control, the connection will be closed. @@ -90,6 +91,9 @@ // ID of this stream. QuicStreamId id_; + // QUIC version being used by this stream. + ParsedQuicVersion version_; + // |stream_delegate_| must outlive this stream. StreamDelegateInterface* stream_delegate_; @@ -146,6 +150,7 @@ // QuicStreamSequencer::StreamInterface implementation. QuicStreamId id() const override { return id_; } + ParsedQuicVersion version() const override; // Called by the stream subclass after it has consumed the final incoming // data. void OnFinRead() override;
diff --git a/quic/core/quic_stream_sequencer.cc b/quic/core/quic_stream_sequencer.cc index a1861dc..dae5513 100644 --- a/quic/core/quic_stream_sequencer.cc +++ b/quic/core/quic_stream_sequencer.cc
@@ -56,6 +56,14 @@ (!CloseStreamAtOffset(frame.offset + data_len) || data_len == 0)) { return; } + if (GetQuicReloadableFlag(quic_accept_empty_stream_frame_with_no_fin)) { + QUIC_RELOADABLE_FLAG_COUNT(quic_accept_empty_stream_frame_with_no_fin); + if (stream_->version().HasIetfQuicFrames() && data_len == 0) { + DCHECK(!frame.fin); + // Ignore empty frame with no fin. + return; + } + } OnFrameData(byte_offset, data_len, frame.data_buffer); }
diff --git a/quic/core/quic_stream_sequencer.h b/quic/core/quic_stream_sequencer.h index a74526b..ec88c14 100644 --- a/quic/core/quic_stream_sequencer.h +++ b/quic/core/quic_stream_sequencer.h
@@ -44,6 +44,9 @@ const std::string& details) = 0; // Returns the stream id of this stream. virtual QuicStreamId id() const = 0; + + // Returns the QUIC version being used by this stream. + virtual ParsedQuicVersion version() const = 0; }; explicit QuicStreamSequencer(StreamInterface* quic_stream);
diff --git a/quic/core/quic_stream_sequencer_test.cc b/quic/core/quic_stream_sequencer_test.cc index a996ee2..1aa614a 100644 --- a/quic/core/quic_stream_sequencer_test.cc +++ b/quic/core/quic_stream_sequencer_test.cc
@@ -41,6 +41,9 @@ MOCK_METHOD(void, AddBytesConsumed, (QuicByteCount bytes), (override)); QuicStreamId id() const override { return 1; } + ParsedQuicVersion version() const override { + return CurrentSupportedVersions()[0]; + } }; namespace { @@ -243,7 +246,11 @@ } TEST_F(QuicStreamSequencerTest, EmptyFrame) { - EXPECT_CALL(stream_, OnUnrecoverableError(QUIC_EMPTY_STREAM_FRAME_NO_FIN, _)); + if (!GetQuicReloadableFlag(quic_accept_empty_stream_frame_with_no_fin) || + !stream_.version().HasIetfQuicFrames()) { + EXPECT_CALL(stream_, + OnUnrecoverableError(QUIC_EMPTY_STREAM_FRAME_NO_FIN, _)); + } OnFrame(0, ""); EXPECT_EQ(0u, NumBufferedBytes()); EXPECT_EQ(0u, sequencer_->NumBytesConsumed());
diff --git a/quic/core/quic_stream_test.cc b/quic/core/quic_stream_test.cc index 37d0247..2fb2187 100644 --- a/quic/core/quic_stream_test.cc +++ b/quic/core/quic_stream_test.cc
@@ -1679,6 +1679,23 @@ stream_->OnStreamReset(rst); } +// Regression test for b/176073284. +TEST_P(QuicStreamTest, EmptyStreamFrameWithNoFin) { + Initialize(); + QuicStreamFrame empty_stream_frame(stream_->id(), false, 0, ""); + if (GetQuicReloadableFlag(quic_accept_empty_stream_frame_with_no_fin) && + stream_->version().HasIetfQuicFrames()) { + EXPECT_CALL(*connection_, + CloseConnection(QUIC_EMPTY_STREAM_FRAME_NO_FIN, _, _)) + .Times(0); + } else { + EXPECT_CALL(*connection_, + CloseConnection(QUIC_EMPTY_STREAM_FRAME_NO_FIN, _, _)); + } + EXPECT_CALL(*stream_, OnDataAvailable()).Times(0); + stream_->OnStreamFrame(empty_stream_frame); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/tls_chlo_extractor.h b/quic/core/tls_chlo_extractor.h index 0063808..ec7b115 100644 --- a/quic/core/tls_chlo_extractor.h +++ b/quic/core/tls_chlo_extractor.h
@@ -179,6 +179,7 @@ void OnUnrecoverableError(QuicErrorCode error, const std::string& details) override; QuicStreamId id() const override { return 0; } + ParsedQuicVersion version() const override { return framer_->version(); } private: // Parses the length of the CHLO message by looking at the first four bytes.