diff --git a/quic/s2n-quic-core/src/event/generated.rs b/quic/s2n-quic-core/src/event/generated.rs index 28777eac91..7ebfb4c51a 100644 --- a/quic/s2n-quic-core/src/event/generated.rs +++ b/quic/s2n-quic-core/src/event/generated.rs @@ -203,6 +203,8 @@ pub mod api { HandshakeDone {}, #[non_exhaustive] Datagram { len: u16 }, + #[non_exhaustive] + DcStatelessResetTokens {}, } #[derive(Clone, Debug)] #[non_exhaustive] @@ -1565,6 +1567,12 @@ pub mod api { } } } + impl<'a> IntoEvent for &crate::frame::DcStatelessResetTokens<'a> { + #[inline] + fn into_event(self) -> builder::Frame { + builder::Frame::DcStatelessResetTokens {} + } + } impl IntoEvent for &crate::stream::StreamType { #[inline] fn into_event(self) -> builder::StreamType { @@ -2775,6 +2783,7 @@ pub mod builder { Datagram { len: u16, }, + DcStatelessResetTokens, } impl IntoEvent for Frame { #[inline] @@ -2869,6 +2878,7 @@ pub mod builder { Self::Datagram { len } => Datagram { len: len.into_event(), }, + Self::DcStatelessResetTokens => DcStatelessResetTokens {}, } } } diff --git a/quic/s2n-quic-core/src/frame/ack_elicitation.rs b/quic/s2n-quic-core/src/frame/ack_elicitation.rs index 3f5cc2ff91..2bce2cea30 100644 --- a/quic/s2n-quic-core/src/frame/ack_elicitation.rs +++ b/quic/s2n-quic-core/src/frame/ack_elicitation.rs @@ -79,6 +79,10 @@ impl AckElicitable for crate::frame::Crypto {} //# they are ack-eliciting ([RFC9002]). impl AckElicitable for crate::frame::Datagram {} impl AckElicitable for crate::frame::DataBlocked {} +//= https://www.rfc-editor.org/rfc/rfc9000#section-19.21 +//# Extension frames MUST be congestion controlled and MUST cause +//# an ACK frame to be sent. +impl AckElicitable for crate::frame::DcStatelessResetTokens<'_> {} impl AckElicitable for crate::frame::HandshakeDone {} impl AckElicitable for crate::frame::MaxData {} impl AckElicitable for crate::frame::MaxStreamData {} diff --git a/quic/s2n-quic-core/src/frame/congestion_controlled.rs b/quic/s2n-quic-core/src/frame/congestion_controlled.rs index a6e19ce91a..ce3d9b8b19 100644 --- a/quic/s2n-quic-core/src/frame/congestion_controlled.rs +++ b/quic/s2n-quic-core/src/frame/congestion_controlled.rs @@ -25,6 +25,10 @@ impl CongestionControlled for crate::frame::Crypto {} //# DATAGRAM frames employ the QUIC connection's congestion controller. impl CongestionControlled for crate::frame::Datagram {} impl CongestionControlled for crate::frame::DataBlocked {} +//= https://www.rfc-editor.org/rfc/rfc9000#section-19.21 +//# Extension frames MUST be congestion controlled and MUST cause +//# an ACK frame to be sent. +impl CongestionControlled for crate::frame::DcStatelessResetTokens<'_> {} impl CongestionControlled for crate::frame::HandshakeDone {} impl CongestionControlled for crate::frame::MaxData {} impl CongestionControlled for crate::frame::MaxStreamData {} diff --git a/quic/s2n-quic-core/src/frame/dc_stateless_reset_tokens.rs b/quic/s2n-quic-core/src/frame/dc_stateless_reset_tokens.rs new file mode 100644 index 0000000000..d86b514fde --- /dev/null +++ b/quic/s2n-quic-core/src/frame/dc_stateless_reset_tokens.rs @@ -0,0 +1,241 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{frame::ExtensionTag, stateless_reset, varint::VarInt}; +use s2n_codec::{ + decoder_invariant, + zerocopy::{AsBytes as _, FromBytes as _}, + DecoderError, Encoder, EncoderValue, +}; + +const TAG: VarInt = VarInt::from_u32(0xdc0000); + +// The maximum number of stateless reset tokens that could fit in a maximum sized QUIC packet +// based on the maximum permitted UDP payload of 65527 and the long packet header size. +const MAX_STATELESS_RESET_TOKEN_COUNT: usize = 4092; + +macro_rules! dc_stateless_reset_tokens_tag { + () => { + 0xdc0000u64 + }; +} + +//# DC_STATELESS_RESET_TOKENS Frame { +//# Type (i) = 0xdc0000, +//# Count (i), +//# Stateless Reset Tokens [(128)], +//# } + +//# DC_STATELESS_RESET_TOKENS frames contain the following fields: +//# +//# Count: A variable-length integer specifying the number of stateless +//# reset tokens in the frame. +//# Stateless Reset Tokens: 1 or more 128-bit values that will be used +//# for a stateless reset of dc path secrets. + +#[derive(Debug, PartialEq, Eq)] +pub struct DcStatelessResetTokens<'a> { + /// 1 or more 128-bit values + stateless_reset_tokens: &'a [stateless_reset::Token], +} + +impl<'a> DcStatelessResetTokens<'a> { + pub const fn tag(&self) -> ExtensionTag { + TAG + } + + /// Constructs a new `DcStatelessResetTokens` frame with the given `stateless_reset_tokens` + /// + /// `Err` if the given `stateless_reset_tokens` is empty + pub fn new(stateless_reset_tokens: &'a [stateless_reset::Token]) -> Result { + ensure!( + !stateless_reset_tokens.is_empty(), + Err("at least one stateless reset token is required") + ); + + ensure!( + stateless_reset_tokens.len() <= MAX_STATELESS_RESET_TOKEN_COUNT, + Err("too many stateless reset tokens") + ); + + Ok(Self { + stateless_reset_tokens, + }) + } +} + +impl<'a> IntoIterator for DcStatelessResetTokens<'a> { + type Item = &'a stateless_reset::Token; + type IntoIter = core::slice::Iter<'a, stateless_reset::Token>; + + fn into_iter(self) -> Self::IntoIter { + self.stateless_reset_tokens.iter() + } +} + +macro_rules! impl_decode_parameterized { + ($slice_from_prefix:ident, $buffer:ident) => {{ + let (count, buffer) = $buffer.decode::()?; + + decoder_invariant!( + count > VarInt::ZERO, + "at least one stateless token must be supplied" + ); + + decoder_invariant!( + count <= MAX_STATELESS_RESET_TOKEN_COUNT, + "too many stateless reset tokens" + ); + + let count: usize = count + .try_into() + .expect("MAX_STATELESS_RESET_TOKEN_COUNT fits in usize"); + + let buffer = buffer.into_less_safe_slice(); + let (stateless_reset_tokens, remaining) = + stateless_reset::Token::$slice_from_prefix(buffer, count) + .ok_or(DecoderError::InvariantViolation("invalid encoding"))?; + + let frame = DcStatelessResetTokens { + stateless_reset_tokens, + }; + + Ok((frame, remaining.into())) + }}; +} + +impl<'a> ::s2n_codec::DecoderParameterizedValue<'a> for DcStatelessResetTokens<'a> { + type Parameter = ExtensionTag; + + #[inline] + fn decode_parameterized( + _tag: Self::Parameter, + buffer: ::s2n_codec::DecoderBuffer<'a>, + ) -> ::s2n_codec::DecoderBufferResult<'a, Self> { + impl_decode_parameterized!(slice_from_prefix, buffer) + } +} + +impl<'a> ::s2n_codec::DecoderParameterizedValueMut<'a> for DcStatelessResetTokens<'a> { + type Parameter = ExtensionTag; + + #[inline] + fn decode_parameterized_mut( + _tag: Self::Parameter, + buffer: ::s2n_codec::DecoderBufferMut<'a>, + ) -> ::s2n_codec::DecoderBufferMutResult<'a, Self> { + impl_decode_parameterized!(mut_slice_from_prefix, buffer) + } +} + +impl<'a> EncoderValue for DcStatelessResetTokens<'a> { + fn encode(&self, buffer: &mut E) { + buffer.encode(&TAG); + let count = + self.stateless_reset_tokens.len().try_into().expect( + "count is limited to MAX_STATELESS_RESET_TOKEN_COUNT, which fits in VarInt", + ); + buffer.encode::(&count); + buffer.encode(&self.stateless_reset_tokens.as_bytes()); + } +} + +#[cfg(test)] +mod tests { + use crate::{ + frame::{ + dc_stateless_reset_tokens::{MAX_STATELESS_RESET_TOKEN_COUNT, TAG}, + DcStatelessResetTokens, ExtensionTag, + }, + stateless_reset, + }; + use s2n_codec::{DecoderBuffer, DecoderParameterizedValue, EncoderValue}; + + #[test] + fn round_trip() { + let tokens: Vec = vec![ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].into(), + [7, 7, 7, 3, 3, 3, 4, 4, 4, 8, 8, 8, 9, 9, 9, 9].into(), + ]; + let frame = DcStatelessResetTokens::new(tokens.as_slice()).unwrap(); + let encoded = frame.encode_to_vec(); + + let buffer = DecoderBuffer::new(encoded.as_slice()); + let (tag, buffer) = buffer.decode::().expect("decoding succeeds"); + assert_eq!(TAG, tag); + let (frame, remaining) = + DcStatelessResetTokens::decode_parameterized(TAG, buffer).expect("decoding succeeds"); + assert!(remaining.is_empty()); + assert_eq!(tokens.len(), frame.stateless_reset_tokens.len()); + for (index, &token) in frame.into_iter().enumerate() { + assert_eq!(tokens[index], token); + } + } + + #[test] + fn invalid_token_size() { + let frame = DcStatelessResetTokens { + stateless_reset_tokens: &[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].into()], + }; + let mut encoded = frame.encode_to_vec(); + encoded.truncate(encoded.len() - 1); + + let buffer = DecoderBuffer::new(encoded.as_slice()); + let (tag, buffer) = buffer.decode::().expect("decoding succeeds"); + assert_eq!(TAG, tag); + + assert!(DcStatelessResetTokens::decode_parameterized(TAG, buffer).is_err()); + } + + #[test] + fn zero_tokens() { + let frame = DcStatelessResetTokens { + stateless_reset_tokens: &[], + }; + let encoded = frame.encode_to_vec(); + + let buffer = DecoderBuffer::new(encoded.as_slice()); + let (tag, buffer) = buffer.decode::().expect("decoding succeeds"); + assert_eq!(TAG, tag); + + assert!(DcStatelessResetTokens::decode_parameterized(TAG, buffer).is_err()); + + assert!(DcStatelessResetTokens::new(&[]).is_err()); + } + + #[test] + fn maximum_sized_frame() { + let tokens: Vec = + vec![stateless_reset::token::testing::TEST_TOKEN_1; MAX_STATELESS_RESET_TOKEN_COUNT]; + let frame = DcStatelessResetTokens::new(tokens.as_slice()).unwrap(); + let encoded = frame.encode_to_vec(); + + let buffer = DecoderBuffer::new(encoded.as_slice()); + let (tag, buffer) = buffer.decode::().expect("decoding succeeds"); + assert_eq!(TAG, tag); + let (frame, remaining) = + DcStatelessResetTokens::decode_parameterized(TAG, buffer).expect("decoding succeeds"); + assert!(remaining.is_empty()); + assert_eq!(tokens.len(), frame.stateless_reset_tokens.len()); + } + + #[test] + fn too_many_tokens() { + let frame = DcStatelessResetTokens { + stateless_reset_tokens: &[stateless_reset::token::testing::TEST_TOKEN_1; + MAX_STATELESS_RESET_TOKEN_COUNT + 1], + }; + let encoded = frame.encode_to_vec(); + + let buffer = DecoderBuffer::new(encoded.as_slice()); + let (tag, buffer) = buffer.decode::().expect("decoding succeeds"); + assert_eq!(TAG, tag); + + assert!(DcStatelessResetTokens::decode_parameterized(TAG, buffer).is_err()); + + assert!(DcStatelessResetTokens::new( + &[stateless_reset::token::testing::TEST_TOKEN_1; MAX_STATELESS_RESET_TOKEN_COUNT + 1] + ) + .is_err()); + } +} diff --git a/quic/s2n-quic-core/src/frame/mod.rs b/quic/s2n-quic-core/src/frame/mod.rs index 8fafb1f1c5..e5e55f288c 100644 --- a/quic/s2n-quic-core/src/frame/mod.rs +++ b/quic/s2n-quic-core/src/frame/mod.rs @@ -6,6 +6,7 @@ use crate::{ event, frame::{ack_elicitation::AckElicitable, congestion_controlled::CongestionControlled}, + varint::VarInt, }; use core::fmt; use s2n_codec::{ @@ -26,6 +27,7 @@ mod tests; //# frame types. pub(crate) type Tag = u8; +pub(crate) type ExtensionTag = VarInt; pub type FrameRef<'a> = Frame<'a, ack::AckRangesDecoder<'a>, DecoderBuffer<'a>>; pub type FrameMut<'a> = Frame<'a, ack::AckRangesDecoder<'a>, DecoderBufferMut<'a>>; @@ -36,7 +38,7 @@ pub trait FrameTrait: AckElicitable + CongestionControlled + path_validation::Pr impl FrameTrait for T {} macro_rules! frames { - ($ack:ident, $data:ident | $($tag_macro:ident => $module:ident, $handler:ident, $ty:ident $([$($generics:tt)+])?;)*) => { + ($ack:ident, $data:ident | $($([$tag_macro:ident])? $(extension[$extension_tag_macro:ident])? => $module:ident, $handler:ident, $ty:ident $([$($generics:tt)+])?;)*) => { $( #[macro_use] pub mod $module; @@ -52,17 +54,6 @@ macro_rules! frames { )* } - impl<'a, $ack, $data> Frame<'a, $ack, $data> { - #[inline] - pub fn tag(&self) -> Tag { - match self { - $( - Frame::$ty(frame) => frame.tag(), - )* - } - } - } - impl<'a, $ack, $data> event::IntoEvent for &Frame<'a, $ack, $data> where $ack: crate::frame::ack::AckRanges, @@ -152,9 +143,23 @@ macro_rules! frames { #[inline] fn handle_extension_frame(&mut self, buffer: DecoderBufferMut<'a>) -> DecoderBufferMutResult<'a, Self::Output> { - let _ = buffer; + let (tag, buffer) = buffer.decode::()?; - Err(DecoderError::InvariantViolation("invalid frame")) + match tag.as_u64() { + $( + $( + $extension_tag_macro!() => { + let (frame, buffer) = buffer.decode_parameterized(tag)?; + let output = self.$handler(frame)?; + Ok((output, buffer)) + }, + )? + )* + _ => { + let _ = buffer; + Err(DecoderError::InvariantViolation("invalid frame")) + } + } } #[inline] @@ -168,12 +173,14 @@ macro_rules! frames { // otherwise fallback to extension selection 0b0100_0000..=0xff => self.handle_extension_frame(buffer), $( - $tag_macro!() => { - let buffer = buffer.skip(core::mem::size_of::())?; - let (frame, buffer) = buffer.decode_parameterized(tag)?; - let output = self.$handler(frame)?; - Ok((output, buffer)) - }, + $( + $tag_macro!() => { + let buffer = buffer.skip(core::mem::size_of::())?; + let (frame, buffer) = buffer.decode_parameterized(tag)?; + let output = self.$handler(frame)?; + Ok((output, buffer)) + }, + )? )* _ => self.handle_extension_frame(buffer), } @@ -235,27 +242,28 @@ macro_rules! simple_frame_codec { frames! { AckRanges, Data | - padding_tag => padding, handle_padding_frame, Padding; - ping_tag => ping, handle_ping_frame, Ping; - ack_tag => ack, handle_ack_frame, Ack[AckRanges]; - reset_stream_tag => reset_stream, handle_reset_stream_frame, ResetStream; - stop_sending_tag => stop_sending, handle_stop_sending_frame, StopSending; - crypto_tag => crypto, handle_crypto_frame, Crypto[Data]; - new_token_tag => new_token, handle_new_token_frame, NewToken['a]; - stream_tag => stream, handle_stream_frame, Stream[Data]; - max_data_tag => max_data, handle_max_data_frame, MaxData; - max_stream_data_tag => max_stream_data, handle_max_stream_data_frame, MaxStreamData; - max_streams_tag => max_streams, handle_max_streams_frame, MaxStreams; - data_blocked_tag => data_blocked, handle_data_blocked_frame, DataBlocked; - stream_data_blocked_tag => stream_data_blocked, handle_stream_data_blocked_frame, StreamDataBlocked; - streams_blocked_tag => streams_blocked, handle_streams_blocked_frame, StreamsBlocked; - new_connection_id_tag => new_connection_id, handle_new_connection_id_frame, NewConnectionId['a]; - retire_connection_id_tag => retire_connection_id, handle_retire_connection_id_frame, RetireConnectionId; - path_challenge_tag => path_challenge, handle_path_challenge_frame, PathChallenge['a]; - path_response_tag => path_response, handle_path_response_frame, PathResponse['a]; - connection_close_tag => connection_close, handle_connection_close_frame, ConnectionClose['a]; - handshake_done_tag => handshake_done, handle_handshake_done_frame, HandshakeDone; - datagram_tag => datagram, handle_datagram_frame, Datagram[Data]; + [padding_tag] => padding, handle_padding_frame, Padding; + [ping_tag] => ping, handle_ping_frame, Ping; + [ack_tag] => ack, handle_ack_frame, Ack[AckRanges]; + [reset_stream_tag] => reset_stream, handle_reset_stream_frame, ResetStream; + [stop_sending_tag] => stop_sending, handle_stop_sending_frame, StopSending; + [crypto_tag] => crypto, handle_crypto_frame, Crypto[Data]; + [new_token_tag] => new_token, handle_new_token_frame, NewToken['a]; + [stream_tag] => stream, handle_stream_frame, Stream[Data]; + [max_data_tag] => max_data, handle_max_data_frame, MaxData; + [max_stream_data_tag] => max_stream_data, handle_max_stream_data_frame, MaxStreamData; + [max_streams_tag] => max_streams, handle_max_streams_frame, MaxStreams; + [data_blocked_tag] => data_blocked, handle_data_blocked_frame, DataBlocked; + [stream_data_blocked_tag] => stream_data_blocked, handle_stream_data_blocked_frame, StreamDataBlocked; + [streams_blocked_tag] => streams_blocked, handle_streams_blocked_frame, StreamsBlocked; + [new_connection_id_tag] => new_connection_id, handle_new_connection_id_frame, NewConnectionId['a]; + [retire_connection_id_tag] => retire_connection_id, handle_retire_connection_id_frame, RetireConnectionId; + [path_challenge_tag] => path_challenge, handle_path_challenge_frame, PathChallenge['a]; + [path_response_tag] => path_response, handle_path_response_frame, PathResponse['a]; + [connection_close_tag] => connection_close, handle_connection_close_frame, ConnectionClose['a]; + [handshake_done_tag] => handshake_done, handle_handshake_done_frame, HandshakeDone; + [datagram_tag] => datagram, handle_datagram_frame, Datagram[Data]; + extension[dc_stateless_reset_tokens_tag] => dc_stateless_reset_tokens, handle_dc_stateless_reset_tokens_frame, DcStatelessResetTokens['a]; } #[derive(Clone, Copy, Debug, Default)] diff --git a/quic/s2n-quic-core/src/frame/path_validation.rs b/quic/s2n-quic-core/src/frame/path_validation.rs index 84f80fcb1c..1da33d2dcd 100644 --- a/quic/s2n-quic-core/src/frame/path_validation.rs +++ b/quic/s2n-quic-core/src/frame/path_validation.rs @@ -69,6 +69,7 @@ impl Probing for crate::frame::ConnectionClose<'_> {} impl Probing for crate::frame::Crypto {} impl Probing for crate::frame::Datagram {} impl Probing for crate::frame::DataBlocked {} +impl Probing for crate::frame::DcStatelessResetTokens<'_> {} impl Probing for crate::frame::HandshakeDone {} impl Probing for crate::frame::MaxData {} impl Probing for crate::frame::MaxStreamData {} diff --git a/quic/s2n-quic-core/src/frame/snapshots/s2n_quic_core__frame__snapshots__dc_stateless_reset_tokens.snap b/quic/s2n-quic-core/src/frame/snapshots/s2n_quic_core__frame__snapshots__dc_stateless_reset_tokens.snap new file mode 100644 index 0000000000..9cf1865939 --- /dev/null +++ b/quic/s2n-quic-core/src/frame/snapshots/s2n_quic_core__frame__snapshots__dc_stateless_reset_tokens.snap @@ -0,0 +1,52 @@ +--- +source: quic/s2n-quic-core/src/frame/mod.rs +expression: values +--- +[ + DcStatelessResetTokens( + DcStatelessResetTokens { + stateless_reset_tokens: [ + Token( + [ + 17, + 34, + 51, + 68, + 85, + 102, + 119, + 136, + 136, + 119, + 102, + 85, + 68, + 51, + 34, + 17, + ], + ), + Token( + [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + ], + ), + ], + }, + ), +] diff --git a/quic/s2n-quic-core/src/frame/test_samples/dc_stateless_reset_tokens.bin b/quic/s2n-quic-core/src/frame/test_samples/dc_stateless_reset_tokens.bin new file mode 100644 index 0000000000..bc243764da Binary files /dev/null and b/quic/s2n-quic-core/src/frame/test_samples/dc_stateless_reset_tokens.bin differ diff --git a/quic/s2n-quic-core/src/stateless_reset/token.rs b/quic/s2n-quic-core/src/stateless_reset/token.rs index e9cfc3fe7c..12ad89ddb6 100644 --- a/quic/s2n-quic-core/src/stateless_reset/token.rs +++ b/quic/s2n-quic-core/src/stateless_reset/token.rs @@ -3,7 +3,11 @@ //! Defines the Stateless Reset token -use s2n_codec::{decoder_value, Encoder, EncoderValue}; +use s2n_codec::{ + decoder_value, + zerocopy::{AsBytes, FromBytes, FromZeroes, Unaligned}, + Encoder, EncoderValue, +}; use subtle::ConstantTimeEq; //= https://www.rfc-editor.org/rfc/rfc9000#section-10.3 @@ -18,11 +22,12 @@ pub const LEN: usize = 128 / 8; // a derived version, except it is constant-time. Therefore // Hash can still be derived. #[allow(clippy::derived_hash_with_manual_eq)] -#[derive(Copy, Clone, Debug, Eq, Hash)] +#[derive(Copy, Clone, Debug, Eq, Hash, FromBytes, FromZeroes, AsBytes, Unaligned)] #[cfg_attr( any(test, feature = "generator"), derive(bolero_generator::TypeGenerator) )] +#[repr(C)] pub struct Token([u8; LEN]); impl Token { diff --git a/quic/s2n-quic-events/events/common.rs b/quic/s2n-quic-events/events/common.rs index 616a3a1771..fc5f0e0d60 100644 --- a/quic/s2n-quic-events/events/common.rs +++ b/quic/s2n-quic-events/events/common.rs @@ -370,6 +370,7 @@ enum Frame { Datagram { len: u16, }, + DcStatelessResetTokens, } impl IntoEvent for &crate::frame::Padding { @@ -573,6 +574,13 @@ where } } +impl<'a> IntoEvent for &crate::frame::DcStatelessResetTokens<'a> { + #[inline] + fn into_event(self) -> builder::Frame { + builder::Frame::DcStatelessResetTokens {} + } +} + enum StreamType { Bidirectional, Unidirectional, diff --git a/quic/s2n-quic-transport/src/space/mod.rs b/quic/s2n-quic-transport/src/space/mod.rs index ea1ac36e3f..bacbf90228 100644 --- a/quic/s2n-quic-transport/src/space/mod.rs +++ b/quic/s2n-quic-transport/src/space/mod.rs @@ -840,7 +840,7 @@ pub trait PacketSpace: Sized { ($frame:ident) => {{ let frame_type = $frame.tag(); processed_packet.on_processed_frame(&$frame); - move |err: transport::Error| err.with_frame_type(VarInt::from_u8(frame_type)) + move |err: transport::Error| err.with_frame_type(frame_type.into()) }}; } @@ -1042,6 +1042,13 @@ pub trait PacketSpace: Sized { ) .map_err(on_error)?; } + Frame::DcStatelessResetTokens(frame) => { + let on_error = on_frame_processed!(frame); + Err(on_error( + transport::Error::PROTOCOL_VIOLATION.with_reason("invalid frame"), + ))? + // TODO: Process DcStatelessResetTokens in dc provider + } } payload = remaining;