Skip to content

Commit

Permalink
feat(s2n-quic-dc): DC_STATELESS_RESET_TOKENS frame
Browse files Browse the repository at this point in the history
  • Loading branch information
WesleyRosenblum committed May 4, 2024
1 parent b085808 commit dc22641
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 44 deletions.
10 changes: 10 additions & 0 deletions quic/s2n-quic-core/src/event/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ pub mod api {
HandshakeDone {},
#[non_exhaustive]
Datagram { len: u16 },
#[non_exhaustive]
DcStatelessResetTokens {},
}
#[derive(Clone, Debug)]
#[non_exhaustive]
Expand Down Expand Up @@ -1565,6 +1567,12 @@ pub mod api {
}
}
}
impl<'a> IntoEvent<builder::Frame> for &crate::frame::DcStatelessResetTokens<'a> {
#[inline]
fn into_event(self) -> builder::Frame {
builder::Frame::DcStatelessResetTokens {}
}
}
impl IntoEvent<builder::StreamType> for &crate::stream::StreamType {
#[inline]
fn into_event(self) -> builder::StreamType {
Expand Down Expand Up @@ -2775,6 +2783,7 @@ pub mod builder {
Datagram {
len: u16,
},
DcStatelessResetTokens,
}
impl IntoEvent<api::Frame> for Frame {
#[inline]
Expand Down Expand Up @@ -2869,6 +2878,7 @@ pub mod builder {
Self::Datagram { len } => Datagram {
len: len.into_event(),
},
Self::DcStatelessResetTokens => DcStatelessResetTokens {},
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions quic/s2n-quic-core/src/frame/ack_elicitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ impl<Data> AckElicitable for crate::frame::Crypto<Data> {}
//# they are ack-eliciting ([RFC9002]).
impl<Data> AckElicitable for crate::frame::Datagram<Data> {}
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 {}
Expand Down
4 changes: 4 additions & 0 deletions quic/s2n-quic-core/src/frame/congestion_controlled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ impl<Data> CongestionControlled for crate::frame::Crypto<Data> {}
//# DATAGRAM frames employ the QUIC connection's congestion controller.
impl<Data> CongestionControlled for crate::frame::Datagram<Data> {}
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 {}
Expand Down
166 changes: 166 additions & 0 deletions quic/s2n-quic-core/src/frame/dc_stateless_reset_tokens.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// 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, decoder_parameterized_value, Encoder, EncoderValue};

const TAG: VarInt = VarInt::from_u32(0xdc0000);

macro_rules! dc_stateless_reset_tokens_tag {
() => {
0xdc0000u64
};
}

//# DC_STATELESS_RESET_TOKENS Frame {
//# Type (i) = 0xdc0000,
//# Stateless Reset Tokens [(128)],
//# }

//# DC_STATELESS_RESET_TOKENS frames contain the following field:
//#
//# 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 [u8],
}

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<Self, &'static str> {
ensure!(
!stateless_reset_tokens.is_empty(),
Err("at least one stateless reset token is required")
);

unsafe {
// Safety: `stateless_reset::Token` is a [u8; 16], so a slice of [u8; 16]s
// should be equivalent to &[u8] with length equal to the number
// of tokens multiplied by 16.
Ok(Self {
stateless_reset_tokens: core::slice::from_raw_parts(
stateless_reset_tokens.as_ptr() as _,
stateless_reset_tokens.len() * stateless_reset::token::LEN,
),
})
}
}
}

impl<'a> IntoIterator for DcStatelessResetTokens<'a> {
type Item = stateless_reset::Token;
type IntoIter =
core::iter::Map<core::slice::ChunksExact<'a, u8>, fn(&[u8]) -> stateless_reset::Token>;

fn into_iter(self) -> Self::IntoIter {
self.stateless_reset_tokens
.chunks_exact(stateless_reset::token::LEN)
.map(|item| {
stateless_reset::Token::try_from(item).expect(
"each chunk has exactly chunk_size (stateless_reset::token::LEN) elements",
)
})
}
}

decoder_parameterized_value!(
impl<'a> DcStatelessResetTokens<'a> {
fn decode(_tag: ExtensionTag, buffer: Buffer) -> Result<Self> {
let len = buffer.len();

decoder_invariant!(len > 0, "at least one stateless token must be supplied");
decoder_invariant!(
len % stateless_reset::token::LEN == 0,
"invalid dc stateless token length"
);

let (stateless_reset_tokens, buffer) = buffer.decode_slice(len)?;
let stateless_reset_tokens: &[u8] = stateless_reset_tokens.into_less_safe_slice();

let frame = DcStatelessResetTokens {
stateless_reset_tokens,
};

Ok((frame, buffer))
}
}
);

impl<'a> EncoderValue for DcStatelessResetTokens<'a> {
fn encode<E: Encoder>(&self, buffer: &mut E) {
buffer.encode(&TAG);
buffer.encode(&self.stateless_reset_tokens);
}
}

#[cfg(test)]
mod tests {
use crate::{
frame::{dc_stateless_reset_tokens::TAG, DcStatelessResetTokens, ExtensionTag},
stateless_reset,
};
use s2n_codec::{DecoderBuffer, DecoderParameterizedValue, EncoderValue};

#[test]
fn round_trip() {
let tokens: Vec<stateless_reset::Token> = 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::<ExtensionTag>().expect("decoding succeeds");
assert_eq!(TAG, tag);
let (frame, remaining) =
DcStatelessResetTokens::decode_parameterized(TAG, buffer).expect("decoding succeeds");
assert!(remaining.is_empty());
assert_eq!(
stateless_reset::token::LEN * 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: &[1, 2, 3],
};
let encoded = frame.encode_to_vec();

let buffer = DecoderBuffer::new(encoded.as_slice());
let (tag, buffer) = buffer.decode::<ExtensionTag>().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::<ExtensionTag>().expect("decoding succeeds");
assert_eq!(TAG, tag);

assert!(DcStatelessResetTokens::decode_parameterized(TAG, buffer).is_err());

assert!(DcStatelessResetTokens::new(&[]).is_err());
}
}
92 changes: 49 additions & 43 deletions quic/s2n-quic-core/src/frame/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

use crate::{
event,
frame::{ack_elicitation::AckElicitable, congestion_controlled::CongestionControlled},
varint::VarInt,
};
use core::fmt;
use s2n_codec::{
Expand All @@ -26,6 +25,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>>;
Expand All @@ -36,7 +36,7 @@ pub trait FrameTrait: AckElicitable + CongestionControlled + path_validation::Pr
impl<T: AckElicitable + CongestionControlled + path_validation::Probing> 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;
Expand All @@ -52,17 +52,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<event::builder::Frame> for &Frame<'a, $ack, $data>
where
$ack: crate::frame::ack::AckRanges,
Expand Down Expand Up @@ -152,9 +141,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::<ExtensionTag>()?;

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]
Expand All @@ -168,12 +171,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::<Tag>())?;
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::<Tag>())?;
let (frame, buffer) = buffer.decode_parameterized(tag)?;
let output = self.$handler(frame)?;
Ok((output, buffer))
},
)?
)*
_ => self.handle_extension_frame(buffer),
}
Expand Down Expand Up @@ -235,27 +240,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)]
Expand Down
1 change: 1 addition & 0 deletions quic/s2n-quic-core/src/frame/path_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ impl Probing for crate::frame::ConnectionClose<'_> {}
impl<Data> Probing for crate::frame::Crypto<Data> {}
impl<Data> Probing for crate::frame::Datagram<Data> {}
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 {}
Expand Down
Loading

0 comments on commit dc22641

Please sign in to comment.