Complete processing of RESET_STREAM_AT frames. Negotiate with transport parameters and pass the frame from QuicConnection to QuicStream. This could be an MVP; it is compliant to negotiate, process, and never send. But a sending CL will follow. PiperOrigin-RevId: 679284591
diff --git a/quiche/quic/core/crypto/transport_parameters.cc b/quiche/quic/core/crypto/transport_parameters.cc index 1aaa820..0456134 100644 --- a/quiche/quic/core/crypto/transport_parameters.cc +++ b/quiche/quic/core/crypto/transport_parameters.cc
@@ -71,6 +71,8 @@ kMinAckDelay = 0xDE1A, // draft-iyengar-quic-delayed-ack. kVersionInformation = 0xFF73DB, // draft-ietf-quic-version-negotiation. + kReliableStreamReset = 0x17F7586D2CB571, + // draft-ietf-quic-reliable-stream-reset. }; namespace { @@ -140,6 +142,8 @@ return "min_ack_delay_us"; case TransportParameters::kVersionInformation: return "version_information"; + case TransportParameters::kReliableStreamReset: + return "reliable_stream_reset"; } return absl::StrCat("Unknown(", param_id, ")"); } @@ -171,6 +175,7 @@ case TransportParameters::kGoogleQuicVersion: case TransportParameters::kMinAckDelay: case TransportParameters::kVersionInformation: + case TransportParameters::kReliableStreamReset: return true; } return false; @@ -417,6 +422,9 @@ if (disable_active_migration) { rv += " " + TransportParameterIdToString(kDisableActiveMigration); } + if (reliable_stream_reset) { + rv += " " + TransportParameterIdToString(kReliableStreamReset); + } if (preferred_address) { rv += " " + TransportParameterIdToString(kPreferredAddress) + " " + preferred_address->ToString(); @@ -489,6 +497,7 @@ kMinActiveConnectionIdLimitTransportParam, quiche::kVarInt62MaxValue), max_datagram_frame_size(kMaxDatagramFrameSize), + reliable_stream_reset(false), initial_round_trip_time_us(kInitialRoundTripTime) // Important note: any new transport parameters must be added // to TransportParameters::AreValid, SerializeTransportParameters and @@ -521,6 +530,7 @@ initial_source_connection_id(other.initial_source_connection_id), retry_source_connection_id(other.retry_source_connection_id), max_datagram_frame_size(other.max_datagram_frame_size), + reliable_stream_reset(other.reliable_stream_reset), initial_round_trip_time_us(other.initial_round_trip_time_us), google_handshake_message(other.google_handshake_message), google_connection_options(other.google_connection_options), @@ -561,6 +571,7 @@ retry_source_connection_id == rhs.retry_source_connection_id && max_datagram_frame_size.value() == rhs.max_datagram_frame_size.value() && + reliable_stream_reset == rhs.reliable_stream_reset && initial_round_trip_time_us.value() == rhs.initial_round_trip_time_us.value() && google_handshake_message == rhs.google_handshake_message && @@ -749,6 +760,7 @@ kConnectionIdParameterLength + // initial_source_connection_id kConnectionIdParameterLength + // retry_source_connection_id kIntegerParameterLength + // max_datagram_frame_size + kTypeAndValueLength + // reliable_stream_reset kIntegerParameterLength + // initial_round_trip_time_us kTypeAndValueLength + // google_handshake_message kTypeAndValueLength + // google_connection_options @@ -770,6 +782,7 @@ TransportParameters::kMinAckDelay, TransportParameters::kActiveConnectionIdLimit, TransportParameters::kMaxDatagramFrameSize, + TransportParameters::kReliableStreamReset, TransportParameters::kGoogleHandshakeMessage, TransportParameters::kInitialRoundTripTime, TransportParameters::kDisableActiveMigration, @@ -1021,6 +1034,18 @@ } } } break; + // reliable_stream_reset + case TransportParameters::kReliableStreamReset: { + if (in.reliable_stream_reset) { + if (!writer.WriteVarInt62( + TransportParameters::kReliableStreamReset) || + !writer.WriteVarInt62(/* transport parameter length */ 0)) { + QUIC_BUG(Failed to write reliable_stream_reset) + << "Failed to write reliable_stream_reset for " << in; + return false; + } + } + } break; // preferred_address case TransportParameters::kPreferredAddress: { if (in.preferred_address) { @@ -1438,6 +1463,13 @@ parse_success = out->initial_round_trip_time_us.Read(&value_reader, error_details); break; + case TransportParameters::kReliableStreamReset: + if (out->reliable_stream_reset) { + *error_details = "Received a second reliable_stream_reset"; + return false; + } + out->reliable_stream_reset = true; + break; case TransportParameters::kGoogleConnectionOptions: { if (out->google_connection_options.has_value()) { *error_details = "Received a second google_connection_options"; @@ -1612,8 +1644,11 @@ return false; } uint8_t disable_active_migration = in.disable_active_migration ? 1 : 0; + uint8_t reliable_stream_reset = in.reliable_stream_reset ? 1 : 0; if (!EVP_DigestUpdate(hash_ctx.get(), &disable_active_migration, sizeof(disable_active_migration)) || + (reliable_stream_reset && + !EVP_DigestUpdate(hash_ctx.get(), "ResetStreamAt", 13)) || !EVP_DigestFinal(hash_ctx.get(), out->data() + 1, nullptr)) { QUIC_BUG(quic_bug_10743_29) << "Unexpected failure of EVP_Digest functions when hashing "
diff --git a/quiche/quic/core/crypto/transport_parameters.h b/quiche/quic/core/crypto/transport_parameters.h index 496de82..9255793 100644 --- a/quiche/quic/core/crypto/transport_parameters.h +++ b/quiche/quic/core/crypto/transport_parameters.h
@@ -248,6 +248,9 @@ // the sender accepts. See draft-ietf-quic-datagram. IntegerParameter max_datagram_frame_size; + // Indicates support for the RESET_STREAM_AT frame. + bool reliable_stream_reset; + // Google-specific transport parameter that carries an estimate of the // initial round-trip time in microseconds. IntegerParameter initial_round_trip_time_us;
diff --git a/quiche/quic/core/crypto/transport_parameters_test.cc b/quiche/quic/core/crypto/transport_parameters_test.cc index b6d4b37..4fb1fed 100644 --- a/quiche/quic/core/crypto/transport_parameters_test.cc +++ b/quiche/quic/core/crypto/transport_parameters_test.cc
@@ -38,6 +38,7 @@ const uint64_t kFakeInitialMaxStreamsBidi = 21; const uint64_t kFakeInitialMaxStreamsUni = 22; const bool kFakeDisableMigration = true; +const bool kFakeReliableStreamReset = true; const uint64_t kFakeInitialRoundTripTime = 53; const uint8_t kFakePreferredStatelessResetTokenData[16] = { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, @@ -191,6 +192,8 @@ new_params.version_information = CreateFakeVersionInformation(); orig_params.disable_active_migration = true; new_params.disable_active_migration = true; + orig_params.reliable_stream_reset = true; + new_params.reliable_stream_reset = true; EXPECT_EQ(orig_params, new_params); EXPECT_TRUE(orig_params == new_params); EXPECT_FALSE(orig_params != new_params); @@ -285,6 +288,7 @@ orig_params.max_ack_delay.set_value(kMaxAckDelayForTest); orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest); orig_params.disable_active_migration = kFakeDisableMigration; + orig_params.reliable_stream_reset = kFakeReliableStreamReset; orig_params.preferred_address = CreateFakePreferredAddress(); orig_params.active_connection_id_limit.set_value( kActiveConnectionIdLimitForTest); @@ -325,6 +329,7 @@ orig_params.max_ack_delay.set_value(kMaxAckDelayForTest); orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest); orig_params.disable_active_migration = kFakeDisableMigration; + orig_params.reliable_stream_reset = kFakeReliableStreamReset; orig_params.active_connection_id_limit.set_value( kActiveConnectionIdLimitForTest); orig_params.initial_source_connection_id = @@ -376,6 +381,7 @@ orig_params.max_ack_delay.set_value(kMaxAckDelayForTest); orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest); orig_params.disable_active_migration = kFakeDisableMigration; + orig_params.reliable_stream_reset = kFakeReliableStreamReset; orig_params.preferred_address = CreateFakePreferredAddress(); orig_params.active_connection_id_limit.set_value( kActiveConnectionIdLimitForTest); @@ -556,6 +562,9 @@ // disable_active_migration 0x0c, // parameter id 0x00, // length + // reliable_stream_reset + 0xc0, 0x17, 0xf7, 0x58, 0x6d, 0x2c, 0xb5, 0x71, // parameter id + 0x00, // length // active_connection_id_limit 0x0e, // parameter id 0x01, // length @@ -631,6 +640,7 @@ EXPECT_EQ(kMaxAckDelayForTest, new_params.max_ack_delay.value()); EXPECT_EQ(kMinAckDelayUsForTest, new_params.min_ack_delay_us.value()); EXPECT_EQ(kFakeDisableMigration, new_params.disable_active_migration); + EXPECT_EQ(kFakeReliableStreamReset, new_params.reliable_stream_reset); EXPECT_EQ(kActiveConnectionIdLimitForTest, new_params.active_connection_id_limit.value()); ASSERT_TRUE(new_params.initial_source_connection_id.has_value()); @@ -803,6 +813,9 @@ // disable_active_migration 0x0c, // parameter id 0x00, // length + // reliable_stream_reset + 0xc0, 0x17, 0xf7, 0x58, 0x6d, 0x2c, 0xb5, 0x71, // parameter id + 0x00, // length // preferred_address 0x0d, // parameter id 0x31, // length @@ -897,6 +910,7 @@ EXPECT_EQ(kMaxAckDelayForTest, new_params.max_ack_delay.value()); EXPECT_EQ(kMinAckDelayUsForTest, new_params.min_ack_delay_us.value()); EXPECT_EQ(kFakeDisableMigration, new_params.disable_active_migration); + EXPECT_EQ(kFakeReliableStreamReset, new_params.reliable_stream_reset); ASSERT_NE(nullptr, new_params.preferred_address.get()); EXPECT_EQ(CreateFakeV4SocketAddress(), new_params.preferred_address->ipv4_socket_address); @@ -1028,6 +1042,7 @@ orig_params.max_ack_delay.set_value(kMaxAckDelayForTest); orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest); orig_params.disable_active_migration = kFakeDisableMigration; + orig_params.reliable_stream_reset = kFakeReliableStreamReset; orig_params.active_connection_id_limit.set_value( kActiveConnectionIdLimitForTest); orig_params.initial_source_connection_id = @@ -1072,6 +1087,7 @@ orig_params.max_ack_delay.set_value(kMaxAckDelayForTest); orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest); orig_params.disable_active_migration = kFakeDisableMigration; + orig_params.reliable_stream_reset = kFakeReliableStreamReset; orig_params.active_connection_id_limit.set_value( kActiveConnectionIdLimitForTest); orig_params.initial_source_connection_id = @@ -1130,6 +1146,7 @@ original_params_.max_ack_delay.set_value(kMaxAckDelayForTest); original_params_.min_ack_delay_us.set_value(kMinAckDelayUsForTest); original_params_.disable_active_migration = kFakeDisableMigration; + original_params_.reliable_stream_reset = kFakeReliableStreamReset; original_params_.preferred_address = CreateFakePreferredAddress(); original_params_.active_connection_id_limit.set_value( kActiveConnectionIdLimitForTest);
diff --git a/quiche/quic/core/quic_config.cc b/quiche/quic/core/quic_config.cc index 480afcb..47027c5 100644 --- a/quiche/quic/core/quic_config.cc +++ b/quiche/quic/core/quic_config.cc
@@ -1034,6 +1034,7 @@ SetAckDelayExponentToSend(kDefaultAckDelayExponent); SetMaxPacketSizeToSend(kMaxIncomingPacketSize); SetMaxDatagramFrameSizeToSend(kMaxAcceptedDatagramFrameSize); + SetReliableStreamReset(false); } void QuicConfig::ToHandshakeMessage( @@ -1278,6 +1279,8 @@ params->google_handshake_message = google_handshake_message_to_send_; } + params->reliable_stream_reset = reliable_stream_reset_; + params->custom_parameters = custom_transport_parameters_to_send_; return true; @@ -1413,6 +1416,10 @@ received_custom_transport_parameters_ = params.custom_parameters; + if (reliable_stream_reset_) { + reliable_stream_reset_ = params.reliable_stream_reset; + } + if (!is_resumption) { negotiated_ = true; } @@ -1481,4 +1488,12 @@ GetQuicFlag(quic_always_support_server_preferred_address); } +void QuicConfig::SetReliableStreamReset(bool reliable_stream_reset) { + reliable_stream_reset_ = reliable_stream_reset; +} + +bool QuicConfig::SupportsReliableStreamReset() const { + return reliable_stream_reset_; +} + } // namespace quic
diff --git a/quiche/quic/core/quic_config.h b/quiche/quic/core/quic_config.h index 34c143c..dd72974 100644 --- a/quiche/quic/core/quic_config.h +++ b/quiche/quic/core/quic_config.h
@@ -441,6 +441,10 @@ // quic_always_support_server_preferred_address. bool SupportsServerPreferredAddress(Perspective perspective) const; + // Returns true if this config supports reliable stream reset. + void SetReliableStreamReset(bool reliable_stream_reset); + bool SupportsReliableStreamReset() const; + // Original destination connection ID. void SetOriginalConnectionIdToSend( const QuicConnectionId& original_destination_connection_id); @@ -706,6 +710,9 @@ // Google internal handshake message. std::optional<std::string> google_handshake_message_to_send_; std::optional<std::string> received_google_handshake_message_; + + // Support for RESET_STREAM_AT frame. + bool reliable_stream_reset_; }; } // namespace quic
diff --git a/quiche/quic/core/quic_config_test.cc b/quiche/quic/core/quic_config_test.cc index 4cf30e9..c706285 100644 --- a/quiche/quic/core/quic_config_test.cc +++ b/quiche/quic/core/quic_config_test.cc
@@ -476,6 +476,7 @@ config_.SetRetrySourceConnectionIdToSend(TestConnectionId(0x3333)); config_.SetMinAckDelayMs(kDefaultMinAckDelayTimeMs); config_.SetGoogleHandshakeMessageToSend(kFakeGoogleHandshakeMessage); + config_.SetReliableStreamReset(true); QuicIpAddress host; host.FromString("127.0.3.1"); @@ -536,6 +537,8 @@ ¶ms.preferred_address->stateless_reset_token.front()), new_stateless_reset_token); EXPECT_EQ(kFakeGoogleHandshakeMessage, params.google_handshake_message); + + EXPECT_TRUE(params.reliable_stream_reset); } TEST_P(QuicConfigTest, DNATPreferredAddress) {
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index 1251c49..8ab8459 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -32,6 +32,7 @@ #include "quiche/quic/core/crypto/crypto_utils.h" #include "quiche/quic/core/crypto/quic_decrypter.h" #include "quiche/quic/core/crypto/quic_encrypter.h" +#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/quic_bandwidth.h" #include "quiche/quic/core/quic_config.h" #include "quiche/quic/core/quic_connection_id.h" @@ -578,6 +579,9 @@ multi_port_migration_enabled_ = true; } } + + reliable_stream_reset_ = config.SupportsReliableStreamReset(); + framer_.set_process_reset_stream_at(reliable_stream_reset_); } void QuicConnection::AddDispatcherSentPackets( @@ -2066,10 +2070,15 @@ if (!UpdatePacketContent(RESET_STREAM_AT_FRAME)) { return false; } - - // TODO(b/278878322): implement. + if (!reliable_stream_reset_) { + CloseConnection(IETF_QUIC_PROTOCOL_VIOLATION, + "Received RESET_STREAM_AT while not negotiated.", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return false; + } MaybeUpdateAckTimeout(); + visitor_->OnResetStreamAt(frame); return true; }
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h index b31e7d0..c76527b 100644 --- a/quiche/quic/core/quic_connection.h +++ b/quiche/quic/core/quic_connection.h
@@ -35,6 +35,7 @@ #include "quiche/quic/core/frames/quic_max_streams_frame.h" #include "quiche/quic/core/frames/quic_new_connection_id_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" +#include "quiche/quic/core/frames/quic_rst_stream_frame.h" #include "quiche/quic/core/quic_alarm.h" #include "quiche/quic/core/quic_alarm_factory.h" #include "quiche/quic/core/quic_blocked_writer_interface.h" @@ -108,6 +109,7 @@ // Called when the stream is reset by the peer. virtual void OnRstStream(const QuicRstStreamFrame& frame) = 0; + virtual void OnResetStreamAt(const QuicResetStreamAtFrame& frame) = 0; // Called when the connection is going away according to the peer. virtual void OnGoAway(const QuicGoAwayFrame& frame) = 0; @@ -2517,6 +2519,9 @@ // might be different from the next codepoint in per_packet_options_. QuicEcnCodepoint last_ecn_codepoint_sent_ = ECN_NOT_ECT; + // If true, the peer has indicated that it supports the RESET_STREAM_AT frame. + bool reliable_stream_reset_ = false; + const bool quic_limit_new_streams_per_loop_2_ = GetQuicReloadableFlag(quic_limit_new_streams_per_loop_2);
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc index b4f67c5..a79d5ac 100644 --- a/quiche/quic/core/quic_connection_test.cc +++ b/quiche/quic/core/quic_connection_test.cc
@@ -26,6 +26,7 @@ #include "quiche/quic/core/frames/quic_connection_close_frame.h" #include "quiche/quic/core/frames/quic_new_connection_id_frame.h" #include "quiche/quic/core/frames/quic_path_response_frame.h" +#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/frames/quic_rst_stream_frame.h" #include "quiche/quic/core/quic_connection_id.h" #include "quiche/quic/core/quic_constants.h" @@ -15460,6 +15461,11 @@ if (!version().HasIetfQuicFrames()) { return; } + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + QuicConfig config; + config.SetReliableStreamReset(true); + connection_.SetFromConfig(config); + EXPECT_CALL(connection_id_generator_, GenerateNextConnectionId(TestConnectionId(12))) .WillOnce(Return(TestConnectionId(456))); @@ -15468,6 +15474,7 @@ .WillOnce(Return(TestConnectionId(789))); EXPECT_CALL(visitor_, SendNewConnectionId(_)).Times(2); EXPECT_CALL(visitor_, OnRstStream(_)); + EXPECT_CALL(visitor_, OnResetStreamAt(_)); EXPECT_CALL(visitor_, OnWindowUpdateFrame(_)); EXPECT_CALL(visitor_, OnBlockedFrame(_)); EXPECT_CALL(visitor_, OnHandshakeDoneReceived()); @@ -17557,6 +17564,36 @@ EXPECT_EQ(connection_.ecn_codepoint(), ECN_NOT_ECT); } +TEST_P(QuicConnectionTest, RejectResetStreamAtIfNotNegotiated) { + if (!version().HasIetfQuicFrames()) { + return; + } + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + QuicConfig config; + config.SetReliableStreamReset(false); + connection_.SetFromConfig(config); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + + EXPECT_CALL(visitor_, OnConnectionClosed(_, _)).Times(1); + connection_.OnResetStreamAtFrame(QuicResetStreamAtFrame()); +} + +TEST_P(QuicConnectionTest, ResetStreamAt) { + if (!version().HasIetfQuicFrames()) { + return; + } + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + QuicConfig config; + config.SetReliableStreamReset(true); + connection_.SetFromConfig(config); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + + EXPECT_CALL(visitor_, OnResetStreamAt(QuicResetStreamAtFrame( + 0, 0, QUIC_STREAM_NO_ERROR, 20, 10))) + .Times(1); + connection_.OnResetStreamAtFrame(QuicResetStreamAtFrame(0, 0, 0, 20, 10)); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quiche/quic/core/quic_session.cc b/quiche/quic/core/quic_session.cc index bba06b9..66d8935 100644 --- a/quiche/quic/core/quic_session.cc +++ b/quiche/quic/core/quic_session.cc
@@ -19,11 +19,13 @@ #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "quiche/quic/core/frames/quic_ack_frequency_frame.h" +#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/frames/quic_window_update_frame.h" #include "quiche/quic/core/quic_connection.h" #include "quiche/quic/core/quic_connection_context.h" #include "quiche/quic/core/quic_error_codes.h" #include "quiche/quic/core/quic_flow_controller.h" +#include "quiche/quic/core/quic_stream.h" #include "quiche/quic/core/quic_stream_priority.h" #include "quiche/quic/core/quic_types.h" #include "quiche/quic/core/quic_utils.h" @@ -439,6 +441,21 @@ ClosePendingStream(stream_id); } +void QuicSession::PendingStreamOnResetStreamAt( + const QuicResetStreamAtFrame& frame) { + QUICHE_DCHECK(VersionUsesHttp3(transport_version())); + QuicStreamId stream_id = frame.stream_id; + + PendingStream* pending = GetOrCreatePendingStream(stream_id); + + if (!pending) { + HandleRstOnValidNonexistentStream(frame.ToRstStream()); + return; + } + + pending->OnResetStreamAtFrame(frame); +} + void QuicSession::OnRstStream(const QuicRstStreamFrame& frame) { QuicStreamId stream_id = frame.stream_id; if (stream_id == QuicUtils::GetInvalidStreamId(transport_version())) { @@ -476,6 +493,40 @@ stream->OnStreamReset(frame); } +void QuicSession::OnResetStreamAt(const QuicResetStreamAtFrame& frame) { + QUICHE_DCHECK(VersionHasIetfQuicFrames(transport_version())); + QuicStreamId stream_id = frame.stream_id; + if (stream_id == QuicUtils::GetInvalidStreamId(transport_version())) { + connection()->CloseConnection( + QUIC_INVALID_STREAM_ID, "Received data for an invalid stream", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } + + if (VersionHasIetfQuicFrames(transport_version()) && + QuicUtils::GetStreamType(stream_id, perspective(), + IsIncomingStream(stream_id), + version()) == WRITE_UNIDIRECTIONAL) { + connection()->CloseConnection( + QUIC_INVALID_STREAM_ID, "Received RESET_STREAM for a write-only stream", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } + + if (ShouldProcessFrameByPendingStream(RESET_STREAM_AT_FRAME, stream_id)) { + PendingStreamOnResetStreamAt(frame); + return; + } + + QuicStream* stream = GetOrCreateStream(stream_id); + + if (!stream) { + HandleRstOnValidNonexistentStream(frame.ToRstStream()); + return; // Errors are handled by GetOrCreateStream. + } + stream->OnResetStreamAtFrame(frame); +} + void QuicSession::OnGoAway(const QuicGoAwayFrame& /*frame*/) { QUIC_BUG_IF(quic_bug_12435_1, version().UsesHttp3()) << "gQUIC GOAWAY received on version " << version();
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h index 1b94b70..8be519a 100644 --- a/quiche/quic/core/quic_session.h +++ b/quiche/quic/core/quic_session.h
@@ -20,6 +20,7 @@ #include "absl/types/span.h" #include "quiche/quic/core/crypto/tls_connection.h" #include "quiche/quic/core/frames/quic_ack_frequency_frame.h" +#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/frames/quic_stop_sending_frame.h" #include "quiche/quic/core/frames/quic_window_update_frame.h" #include "quiche/quic/core/handshaker_delegate_interface.h" @@ -136,6 +137,7 @@ void OnStreamFrame(const QuicStreamFrame& frame) override; void OnCryptoFrame(const QuicCryptoFrame& frame) override; void OnRstStream(const QuicRstStreamFrame& frame) override; + void OnResetStreamAt(const QuicResetStreamAtFrame& frame) override; void OnGoAway(const QuicGoAwayFrame& frame) override; void OnMessageReceived(absl::string_view message) override; void OnHandshakeDoneReceived() override; @@ -957,6 +959,7 @@ // Creates or gets pending strea, feed it with |frame|, and closes the pending // stream. void PendingStreamOnRstStream(const QuicRstStreamFrame& frame); + void PendingStreamOnResetStreamAt(const QuicResetStreamAtFrame& frame); // Creates or gets pending stream, feeds it with |frame|, and records the // max_data in the pending stream.
diff --git a/quiche/quic/core/quic_session_test.cc b/quiche/quic/core/quic_session_test.cc index 03c83e4..7f54074 100644 --- a/quiche/quic/core/quic_session_test.cc +++ b/quiche/quic/core/quic_session_test.cc
@@ -21,8 +21,11 @@ #include "quiche/quic/core/crypto/null_encrypter.h" #include "quiche/quic/core/crypto/transport_parameters.h" #include "quiche/quic/core/frames/quic_max_streams_frame.h" +#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" +#include "quiche/quic/core/quic_constants.h" #include "quiche/quic/core/quic_crypto_stream.h" #include "quiche/quic/core/quic_data_writer.h" +#include "quiche/quic/core/quic_error_codes.h" #include "quiche/quic/core/quic_packets.h" #include "quiche/quic/core/quic_stream.h" #include "quiche/quic/core/quic_types.h" @@ -1609,6 +1612,23 @@ session_.OnRstStream(rst1); } +TEST_P(QuicSessionTestServer, OnResetStreamAtInvalidStreamId) { + if (connection_->version().handshake_protocol != PROTOCOL_TLS1_3) { + // This test requires IETF QUIC. + return; + } + // Send two bytes of payload. + QuicResetStreamAtFrame rst1( + kInvalidControlFrameId, + QuicUtils::GetInvalidStreamId(connection_->transport_version()), + QUIC_ERROR_PROCESSING_STREAM, 10, 0); + EXPECT_CALL(*connection_, + CloseConnection( + QUIC_INVALID_STREAM_ID, "Received data for an invalid stream", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET)); + session_.OnResetStreamAt(rst1); +} + TEST_P(QuicSessionTestServer, HandshakeUnblocksFlowControlBlockedStream) { if (connection_->version().handshake_protocol == PROTOCOL_TLS1_3) { // This test requires Google QUIC crypto because it assumes streams start
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h index 0e9f020..0f26a3e 100644 --- a/quiche/quic/test_tools/quic_test_utils.h +++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -456,6 +456,8 @@ MOCK_METHOD(void, OnBlockedFrame, (const QuicBlockedFrame& frame), (override)); MOCK_METHOD(void, OnRstStream, (const QuicRstStreamFrame& frame), (override)); + MOCK_METHOD(void, OnResetStreamAt, (const QuicResetStreamAtFrame& frame), + (override)); MOCK_METHOD(void, OnGoAway, (const QuicGoAwayFrame& frame), (override)); MOCK_METHOD(void, OnMessageReceived, (absl::string_view message), (override)); MOCK_METHOD(void, OnHandshakeDoneReceived, (), (override));
diff --git a/quiche/quic/test_tools/simulator/quic_endpoint.h b/quiche/quic/test_tools/simulator/quic_endpoint.h index 11d3ee5..700bcd2 100644 --- a/quiche/quic/test_tools/simulator/quic_endpoint.h +++ b/quiche/quic/test_tools/simulator/quic_endpoint.h
@@ -8,6 +8,7 @@ #include "absl/strings/string_view.h" #include "quiche/quic/core/crypto/null_decrypter.h" #include "quiche/quic/core/crypto/null_encrypter.h" +#include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/quic_connection.h" #include "quiche/quic/core/quic_packet_writer.h" #include "quiche/quic/core/quic_packets.h" @@ -53,6 +54,7 @@ void OnWindowUpdateFrame(const QuicWindowUpdateFrame& /*frame*/) override {} void OnBlockedFrame(const QuicBlockedFrame& /*frame*/) override {} void OnRstStream(const QuicRstStreamFrame& /*frame*/) override {} + void OnResetStreamAt(const QuicResetStreamAtFrame& /*frame*/) override {} void OnGoAway(const QuicGoAwayFrame& /*frame*/) override {} void OnMessageReceived(absl::string_view /*message*/) override {} void OnHandshakeDoneReceived() override {}