gfe-relnote: Close connection with H3_CLOSED_CRITICAL_STREAM if peer closes a critical stream. Protected by gfe2_reloadable_flag_quic_enable_version_draft_25_v3 and gfe2_reloadable_flag_quic_enable_version_draft_27.
Note that write (send) unidirectional streams can only receive STOP_SENDING, and
read (received) unidirectional streams can only receive RESET_STREAM. The wrong
kind of frame would not reach the stream object from the transport layer.
PiperOrigin-RevId: 300733944
Change-Id: I2780dc928b6f9ed093854329378c12dcd94ecb0a
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index d87ec83..f77bdea 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -160,10 +160,9 @@
void QuicReceiveControlStream::OnStreamReset(
const QuicRstStreamFrame& /*frame*/) {
- // TODO(renjietang) Change the error code to H/3 specific
- // HTTP_CLOSED_CRITICAL_STREAM.
- stream_delegate()->OnStreamError(QUIC_INVALID_STREAM_ID,
- "Attempt to reset receive control stream");
+ stream_delegate()->OnStreamError(
+ QUIC_HTTP_CLOSED_CRITICAL_STREAM,
+ "RESET_STREAM received for receive control stream");
}
void QuicReceiveControlStream::OnDataAvailable() {
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
index 0997fc0..8bbf789 100644
--- a/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -147,7 +147,8 @@
QuicRstStreamFrame rst_frame(kInvalidControlFrameId,
receive_control_stream_->id(),
QUIC_STREAM_CANCELLED, 1234);
- EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM, _, _));
receive_control_stream_->OnStreamReset(rst_frame);
}
diff --git a/quic/core/http/quic_send_control_stream.cc b/quic/core/http/quic_send_control_stream.cc
index 0d8616c..01c03dd 100644
--- a/quic/core/http/quic_send_control_stream.cc
+++ b/quic/core/http/quic_send_control_stream.cc
@@ -32,10 +32,14 @@
max_inbound_header_list_size_(max_inbound_header_list_size) {}
void QuicSendControlStream::OnStreamReset(const QuicRstStreamFrame& /*frame*/) {
- // TODO(renjietang) Change the error code to H/3 specific
- // HTTP_CLOSED_CRITICAL_STREAM.
- stream_delegate()->OnStreamError(QUIC_INVALID_STREAM_ID,
- "Attempt to reset send control stream");
+ QUIC_BUG << "OnStreamReset() called for write unidirectional stream.";
+}
+
+bool QuicSendControlStream::OnStopSending(uint16_t /* code */) {
+ stream_delegate()->OnStreamError(
+ QUIC_HTTP_CLOSED_CRITICAL_STREAM,
+ "STOP_SENDING received for send control stream");
+ return false;
}
void QuicSendControlStream::MaybeSendSettingsFrame() {
diff --git a/quic/core/http/quic_send_control_stream.h b/quic/core/http/quic_send_control_stream.h
index 0095317..899abb9 100644
--- a/quic/core/http/quic_send_control_stream.h
+++ b/quic/core/http/quic_send_control_stream.h
@@ -30,9 +30,10 @@
QuicSendControlStream& operator=(const QuicSendControlStream&) = delete;
~QuicSendControlStream() override = default;
- // Overriding QuicStream::OnStreamReset to make sure control stream is never
+ // Overriding QuicStream::OnStopSending() to make sure control stream is never
// closed before connection.
void OnStreamReset(const QuicRstStreamFrame& frame) override;
+ bool OnStopSending(uint16_t code) override;
// Send SETTINGS frame if it hasn't been sent yet. Settings frame must be the
// first frame sent on this stream.
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc
index a5ff591..8332cec 100644
--- a/quic/core/http/quic_send_control_stream_test.cc
+++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -185,13 +185,11 @@
send_control_stream_->WritePriorityUpdate(frame);
}
-TEST_P(QuicSendControlStreamTest, ResetControlStream) {
+TEST_P(QuicSendControlStreamTest, CloseControlStream) {
Initialize();
- QuicRstStreamFrame rst_frame(kInvalidControlFrameId,
- send_control_stream_->id(),
- QUIC_STREAM_CANCELLED, 1234);
- EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
- send_control_stream_->OnStreamReset(rst_frame);
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM, _, _));
+ send_control_stream_->OnStopSending(QUIC_STREAM_CANCELLED);
}
TEST_P(QuicSendControlStreamTest, ReceiveDataOnSendControlStream) {
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 0412a76..408b0fa 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -1187,6 +1187,7 @@
TEST_P(QuicSpdySessionTestServer, OnRstStreamStaticStreamId) {
QuicStreamId id;
+ QuicErrorCode expected_error;
std::string error_message;
// Initialize HTTP/3 control stream.
if (VersionUsesHttp3(transport_version())) {
@@ -1195,9 +1196,11 @@
QuicStreamFrame data1(id, false, 0, quiche::QuicheStringPiece(type, 1));
session_.OnStreamFrame(data1);
- error_message = "Attempt to reset receive control stream";
+ expected_error = QUIC_HTTP_CLOSED_CRITICAL_STREAM;
+ error_message = "RESET_STREAM received for receive control stream";
} else {
id = QuicUtils::GetHeadersStreamId(transport_version());
+ expected_error = QUIC_INVALID_STREAM_ID;
error_message = "Attempt to reset headers stream";
}
@@ -1206,7 +1209,7 @@
QUIC_ERROR_PROCESSING_STREAM, 0);
EXPECT_CALL(
*connection_,
- CloseConnection(QUIC_INVALID_STREAM_ID, error_message,
+ CloseConnection(expected_error, error_message,
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET));
session_.OnRstStream(rst1);
}
@@ -2879,6 +2882,82 @@
session_.OnStreamFrame(data);
}
+TEST_P(QuicSpdySessionTestServer, PeerClosesCriticalReceiveStream) {
+ if (!VersionUsesHttp3(transport_version())) {
+ return;
+ }
+
+ struct {
+ char type;
+ const char* error_details;
+ } kTestData[] = {
+ {kControlStream, "RESET_STREAM received for receive control stream"},
+ {kQpackEncoderStream, "RESET_STREAM received for QPACK receive stream"},
+ {kQpackDecoderStream, "RESET_STREAM received for QPACK receive stream"},
+ };
+ for (size_t i = 0; i < QUICHE_ARRAYSIZE(kTestData); ++i) {
+ QuicStreamId stream_id =
+ GetNthClientInitiatedUnidirectionalStreamId(transport_version(), i + 1);
+ const QuicByteCount data_length = 1;
+ QuicStreamFrame data(
+ stream_id, false, 0,
+ quiche::QuicheStringPiece(&kTestData[i].type, data_length));
+ session_.OnStreamFrame(data);
+
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM,
+ kTestData[i].error_details, _));
+
+ QuicRstStreamFrame rst(kInvalidControlFrameId, stream_id,
+ QUIC_STREAM_CANCELLED, data_length);
+ session_.OnRstStream(rst);
+ }
+}
+
+TEST_P(QuicSpdySessionTestServer, PeerClosesCriticalSendStream) {
+ if (!VersionUsesHttp3(transport_version())) {
+ return;
+ }
+
+ QuicSendControlStream* control_stream =
+ QuicSpdySessionPeer::GetSendControlStream(&session_);
+ ASSERT_TRUE(control_stream);
+
+ QuicStopSendingFrame stop_sending_control_stream(
+ kInvalidControlFrameId, control_stream->id(),
+ static_cast<QuicApplicationErrorCode>(QUIC_STREAM_CANCELLED));
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM,
+ "STOP_SENDING received for send control stream", _));
+ session_.OnStopSendingFrame(stop_sending_control_stream);
+
+ QpackSendStream* decoder_stream =
+ QuicSpdySessionPeer::GetQpackDecoderSendStream(&session_);
+ ASSERT_TRUE(decoder_stream);
+
+ QuicStopSendingFrame stop_sending_decoder_stream(
+ kInvalidControlFrameId, decoder_stream->id(),
+ static_cast<QuicApplicationErrorCode>(QUIC_STREAM_CANCELLED));
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM,
+ "STOP_SENDING received for QPACK send stream", _));
+ session_.OnStopSendingFrame(stop_sending_decoder_stream);
+
+ QpackSendStream* encoder_stream =
+ QuicSpdySessionPeer::GetQpackEncoderSendStream(&session_);
+ ASSERT_TRUE(encoder_stream);
+
+ QuicStopSendingFrame stop_sending_encoder_stream(
+ kInvalidControlFrameId, encoder_stream->id(),
+ static_cast<QuicApplicationErrorCode>(QUIC_STREAM_CANCELLED));
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM,
+ "STOP_SENDING received for QPACK send stream", _));
+ session_.OnStopSendingFrame(stop_sending_encoder_stream);
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/qpack/qpack_receive_stream.cc b/quic/core/qpack/qpack_receive_stream.cc
index f896ca4..d2fc88b 100644
--- a/quic/core/qpack/qpack_receive_stream.cc
+++ b/quic/core/qpack/qpack_receive_stream.cc
@@ -14,10 +14,9 @@
receiver_(receiver) {}
void QpackReceiveStream::OnStreamReset(const QuicRstStreamFrame& /*frame*/) {
- // TODO(renjietang) Change the error code to H/3 specific
- // HTTP_CLOSED_CRITICAL_STREAM.
- stream_delegate()->OnStreamError(QUIC_INVALID_STREAM_ID,
- "Attempt to reset Qpack receive stream");
+ stream_delegate()->OnStreamError(
+ QUIC_HTTP_CLOSED_CRITICAL_STREAM,
+ "RESET_STREAM received for QPACK receive stream");
}
void QpackReceiveStream::OnDataAvailable() {
diff --git a/quic/core/qpack/qpack_receive_stream_test.cc b/quic/core/qpack/qpack_receive_stream_test.cc
index 6aeeeae..bc47d9d 100644
--- a/quic/core/qpack/qpack_receive_stream_test.cc
+++ b/quic/core/qpack/qpack_receive_stream_test.cc
@@ -86,7 +86,8 @@
QuicRstStreamFrame rst_frame(kInvalidControlFrameId,
qpack_receive_stream_->id(),
QUIC_STREAM_CANCELLED, 1234);
- EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM, _, _));
qpack_receive_stream_->OnStreamReset(rst_frame);
}
diff --git a/quic/core/qpack/qpack_send_stream.cc b/quic/core/qpack/qpack_send_stream.cc
index 57493e3..15525b5 100644
--- a/quic/core/qpack/qpack_send_stream.cc
+++ b/quic/core/qpack/qpack_send_stream.cc
@@ -17,10 +17,14 @@
stream_type_sent_(false) {}
void QpackSendStream::OnStreamReset(const QuicRstStreamFrame& /*frame*/) {
- // TODO(renjietang) Change the error code to H/3 specific
- // HTTP_CLOSED_CRITICAL_STREAM.
- stream_delegate()->OnStreamError(QUIC_INVALID_STREAM_ID,
- "Attempt to reset qpack send stream");
+ QUIC_BUG << "OnStreamReset() called for write unidirectional stream.";
+}
+
+bool QpackSendStream::OnStopSending(uint16_t /* code */) {
+ stream_delegate()->OnStreamError(
+ QUIC_HTTP_CLOSED_CRITICAL_STREAM,
+ "STOP_SENDING received for QPACK send stream");
+ return false;
}
void QpackSendStream::WriteStreamData(quiche::QuicheStringPiece data) {
diff --git a/quic/core/qpack/qpack_send_stream.h b/quic/core/qpack/qpack_send_stream.h
index bc2d9c0..50d2808 100644
--- a/quic/core/qpack/qpack_send_stream.h
+++ b/quic/core/qpack/qpack_send_stream.h
@@ -30,9 +30,10 @@
QpackSendStream& operator=(const QpackSendStream&) = delete;
~QpackSendStream() override = default;
- // Overriding QuicStream::OnStreamReset to make sure QPACK stream is
- // never closed before connection.
+ // Overriding QuicStream::OnStopSending() to make sure QPACK stream is never
+ // closed before connection.
void OnStreamReset(const QuicRstStreamFrame& frame) override;
+ bool OnStopSending(uint16_t code) override;
// The send QPACK stream is write unidirectional, so this method
// should never be called.
diff --git a/quic/core/qpack/qpack_send_stream_test.cc b/quic/core/qpack/qpack_send_stream_test.cc
index d6a4df7..c77a621 100644
--- a/quic/core/qpack/qpack_send_stream_test.cc
+++ b/quic/core/qpack/qpack_send_stream_test.cc
@@ -106,11 +106,10 @@
qpack_send_stream_->MaybeSendStreamType();
}
-TEST_P(QpackSendStreamTest, ResetQpackStream) {
- QuicRstStreamFrame rst_frame(kInvalidControlFrameId, qpack_send_stream_->id(),
- QUIC_STREAM_CANCELLED, 1234);
- EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _));
- qpack_send_stream_->OnStreamReset(rst_frame);
+TEST_P(QpackSendStreamTest, StopSendingQpackStream) {
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_HTTP_CLOSED_CRITICAL_STREAM, _, _));
+ qpack_send_stream_->OnStopSending(QUIC_STREAM_CANCELLED);
}
TEST_P(QpackSendStreamTest, ReceiveDataOnSendStream) {
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index e24e839..d897aac 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -174,6 +174,7 @@
RETURN_STRING_LITERAL(QUIC_HTTP_DUPLICATE_UNIDIRECTIONAL_STREAM);
RETURN_STRING_LITERAL(QUIC_HTTP_SERVER_INITIATED_BIDIRECTIONAL_STREAM);
RETURN_STRING_LITERAL(QUIC_HTTP_STREAM_WRONG_DIRECTION);
+ RETURN_STRING_LITERAL(QUIC_HTTP_CLOSED_CRITICAL_STREAM);
RETURN_STRING_LITERAL(QUIC_HPACK_INDEX_VARINT_ERROR);
RETURN_STRING_LITERAL(QUIC_HPACK_NAME_LENGTH_VARINT_ERROR);
RETURN_STRING_LITERAL(QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR);
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index 3ef34a5..dc575d3 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -373,6 +373,9 @@
// Server opens stream with stream ID corresponding to client-initiated
// stream or vice versa.
QUIC_HTTP_STREAM_WRONG_DIRECTION = 155,
+ // Peer closes one of the six critical unidirectional streams (control, QPACK
+ // encoder or decoder, in either direction).
+ QUIC_HTTP_CLOSED_CRITICAL_STREAM = 156,
// HPACK header block decoding errors.
// Index varint beyond implementation limit.
@@ -409,7 +412,7 @@
QUIC_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT = 150,
// No error. Used as bound while iterating.
- QUIC_LAST_ERROR = 156,
+ QUIC_LAST_ERROR = 157,
};
// QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
// or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
diff --git a/quic/core/quic_types.cc b/quic/core/quic_types.cc
index 5198eec..6282358 100644
--- a/quic/core/quic_types.cc
+++ b/quic/core/quic_types.cc
@@ -489,6 +489,10 @@
QuicHttp3ErrorCode::IETF_QUIC_HTTP3_STREAM_CREATION_ERROR)}};
case QUIC_HTTP_STREAM_WRONG_DIRECTION:
return {true, {static_cast<uint64_t>(STREAM_STATE_ERROR)}};
+ case QUIC_HTTP_CLOSED_CRITICAL_STREAM:
+ return {false,
+ {static_cast<uint64_t>(
+ QuicHttp3ErrorCode::IETF_QUIC_HTTP3_CLOSED_CRITICAL_STREAM)}};
case QUIC_HPACK_INDEX_VARINT_ERROR:
return {false, {static_cast<uint64_t>(QUIC_HPACK_INDEX_VARINT_ERROR)}};
case QUIC_HPACK_NAME_LENGTH_VARINT_ERROR: