Skip to content

Commit

Permalink
feat(s2n-quic-dc): DC_STATELESS_RESET_TOKENS frame (#2198)
Browse files Browse the repository at this point in the history
* feat(s2n-quic-dc): DC_STATELESS_RESET_TOKENS frame

* add length

* use zero copy traits

* refactor into a macro

* use expect

* use as_bytes
  • Loading branch information
WesleyRosenblum authored May 7, 2024
1 parent 6dd41e0 commit f05dc62
Show file tree
Hide file tree
Showing 11 changed files with 384 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
241 changes: 241 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,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<Self, &'static str> {
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::<VarInt>()?;

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<E: Encoder>(&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::<VarInt>(&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<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!(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::<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());
}

#[test]
fn maximum_sized_frame() {
let tokens: Vec<stateless_reset::Token> =
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::<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!(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::<ExtensionTag>().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());
}
}
Loading

0 comments on commit f05dc62

Please sign in to comment.