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.