// Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. // Directly relating to QUIC frames. use std::ops::RangeInclusive; use neqo_common::{Buffer, Decoder, Encoder, MAX_VARINT, qtrace}; use strum::FromRepr; use crate::{ AppError, ConnectionId, Error, Res, TransportError, ecn, packet, stateless_reset::Token as Srt, stream_id::{StreamId, StreamType}, }; #[repr(u64)] #[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)] pub enum FrameType { Padding = 0x0, Ping = 0x1, Ack = 0x2, AckEcn = 0x3, ResetStream = 0x4, StopSending = 0x5, Crypto = 0x6, NewToken = 0x7, Stream = 0x08, // + 0b000 StreamWithFin = 0x08 + 0b001, StreamWithLen = 0x08 + 0b010, StreamWithLenFin = 0x08 + 0b011, StreamWithOff = 0x08 + 0b100, StreamWithOffFin = 0x08 + 0b101, StreamWithOffLen = 0x08 + 0b110, StreamWithOffLenFin = 0x08 + 0b111, MaxData = 0x10, MaxStreamData = 0x11, MaxStreamsBiDi = 0x12, MaxStreamsUniDi = 0x13, DataBlocked = 0x14, StreamDataBlocked = 0x15, StreamsBlockedBiDi = 0x16, StreamsBlockedUniDi = 0x17, NewConnectionId = 0x18, RetireConnectionId = 0x19, PathChallenge = 0x1a, PathResponse = 0x1b, ConnectionCloseTransport = 0x1c, ConnectionCloseApplication = 0x1d, HandshakeDone = 0x1e, // draft-ietf-quic-ack-delay AckFrequency = 0xaf, // draft-ietf-quic-datagram Datagram = 0x30, DatagramWithLen = 0x31, } impl From for u64 { fn from(val: FrameType) -> Self { val as Self } } impl From for u8 { fn from(val: FrameType) -> Self { val as Self } } impl TryFrom for FrameType { type Error = Error; fn try_from(value: u64) -> Result { Self::from_repr(value).ok_or(Error::UnknownFrameType) } } impl FrameType { const fn is_stream_with_length(self) -> bool { matches!( self, Self::StreamWithLen | Self::StreamWithLenFin | Self::StreamWithOffLen | Self::StreamWithOffLenFin ) } const fn is_stream_with_offset(self) -> bool { matches!( self, Self::StreamWithOff | Self::StreamWithOffFin | Self::StreamWithOffLen | Self::StreamWithOffLenFin ) } const fn is_stream_with_fin(self) -> bool { matches!( self, Self::StreamWithFin | Self::StreamWithLenFin | Self::StreamWithOffFin | Self::StreamWithOffLenFin ) } } impl TryFrom for StreamType { type Error = Error; fn try_from(value: FrameType) -> Result { match value { FrameType::MaxStreamsBiDi | FrameType::StreamsBlockedBiDi => Ok(Self::BiDi), FrameType::MaxStreamsUniDi | FrameType::StreamsBlockedUniDi => Ok(Self::UniDi), _ => Err(Error::FrameEncoding), } } } #[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Clone, Copy)] pub enum CloseError { Transport(TransportError), Application(AppError), } impl CloseError { #[must_use] pub const fn code(&self) -> u64 { match self { Self::Transport(c) | Self::Application(c) => *c, } } } impl From for Error { fn from(_err: std::array::TryFromSliceError) -> Self { Self::FrameEncoding } } #[derive(PartialEq, Eq, Debug, Default, Clone)] pub struct AckRange { gap: u64, range: u64, } #[derive(PartialEq, Eq, Debug, Clone)] pub enum Frame<'a> { Padding(u16), Ping, Ack { largest_acknowledged: u64, ack_delay: u64, first_ack_range: u64, ack_ranges: Vec, ecn_count: Option, }, ResetStream { stream_id: StreamId, application_error_code: AppError, final_size: u64, }, StopSending { stream_id: StreamId, application_error_code: AppError, }, Crypto { offset: u64, data: &'a [u8], }, NewToken { token: &'a [u8], }, Stream { stream_id: StreamId, offset: u64, data: &'a [u8], fin: bool, fill: bool, }, MaxData { maximum_data: u64, }, MaxStreamData { stream_id: StreamId, maximum_stream_data: u64, }, MaxStreams { stream_type: StreamType, maximum_streams: u64, }, DataBlocked { data_limit: u64, }, StreamDataBlocked { stream_id: StreamId, stream_data_limit: u64, }, StreamsBlocked { stream_type: StreamType, stream_limit: u64, }, NewConnectionId { sequence_number: u64, retire_prior: u64, connection_id: &'a [u8], stateless_reset_token: Srt, }, RetireConnectionId { sequence_number: u64, }, PathChallenge { data: [u8; 8], }, PathResponse { data: [u8; 8], }, ConnectionClose { error_code: CloseError, frame_type: u64, // Not a reference as we use this to hold the value. // This is not used in optimized builds anyway. reason_phrase: String, }, HandshakeDone, AckFrequency { /// The current ACK frequency sequence number. seqno: u64, /// The number of contiguous packets that can be received without /// acknowledging immediately. tolerance: u64, /// The time to delay after receiving the first packet that is /// not immediately acknowledged. delay: u64, /// Ignore reordering when deciding to immediately acknowledge. ignore_order: bool, }, Datagram { data: &'a [u8], fill: bool, }, } impl<'a> Frame<'a> { #[must_use] pub const fn get_type(&self) -> FrameType { match self { Self::Padding { .. } => FrameType::Padding, Self::Ping => FrameType::Ping, Self::Ack { .. } => FrameType::Ack, Self::ResetStream { .. } => FrameType::ResetStream, Self::StopSending { .. } => FrameType::StopSending, Self::Crypto { .. } => FrameType::Crypto, Self::NewToken { .. } => FrameType::NewToken, Self::Stream { fin, offset, fill, .. } => Self::stream_type(*fin, *offset > 0, *fill), Self::MaxData { .. } => FrameType::MaxData, Self::MaxStreamData { .. } => FrameType::MaxStreamData, Self::MaxStreams { stream_type, .. } => match stream_type { StreamType::BiDi => FrameType::MaxStreamsBiDi, StreamType::UniDi => FrameType::MaxStreamsUniDi, }, Self::DataBlocked { .. } => FrameType::DataBlocked, Self::StreamDataBlocked { .. } => FrameType::StreamDataBlocked, Self::StreamsBlocked { stream_type, .. } => match stream_type { StreamType::BiDi => FrameType::StreamsBlockedBiDi, StreamType::UniDi => FrameType::StreamsBlockedUniDi, }, Self::NewConnectionId { .. } => FrameType::NewConnectionId, Self::RetireConnectionId { .. } => FrameType::RetireConnectionId, Self::PathChallenge { .. } => FrameType::PathChallenge, Self::PathResponse { .. } => FrameType::PathResponse, Self::ConnectionClose { error_code, .. } => match error_code { CloseError::Transport(_) => FrameType::ConnectionCloseTransport, CloseError::Application(_) => FrameType::ConnectionCloseApplication, }, Self::HandshakeDone => FrameType::HandshakeDone, Self::AckFrequency { .. } => FrameType::AckFrequency, Self::Datagram { fill, .. } => match fill { false => FrameType::Datagram, true => FrameType::DatagramWithLen, }, } } #[must_use] pub const fn is_stream(&self) -> bool { matches!( self, Self::ResetStream { .. } | Self::StopSending { .. } | Self::Stream { .. } | Self::MaxData { .. } | Self::MaxStreamData { .. } | Self::MaxStreams { .. } | Self::DataBlocked { .. } | Self::StreamDataBlocked { .. } | Self::StreamsBlocked { .. } ) } #[must_use] pub const fn stream_type(fin: bool, nonzero_offset: bool, fill: bool) -> FrameType { match (nonzero_offset, fill, fin) { (false, true, false) => FrameType::Stream, (false, true, true) => FrameType::StreamWithFin, (false, false, false) => FrameType::StreamWithLen, (false, false, true) => FrameType::StreamWithLenFin, (true, true, false) => FrameType::StreamWithOff, (true, true, true) => FrameType::StreamWithOffFin, (true, false, false) => FrameType::StreamWithOffLen, (true, false, true) => FrameType::StreamWithOffLenFin, } } /// If the frame causes a recipient to generate an ACK within its /// advertised maximum acknowledgement delay. #[must_use] pub const fn ack_eliciting(&self) -> bool { !matches!( self, Self::Ack { .. } | Self::Padding { .. } | Self::ConnectionClose { .. } ) } /// If the frame can be sent in a path probe /// without initiating migration to that path. #[must_use] pub const fn path_probing(&self) -> bool { matches!( self, Self::Padding { .. } | Self::NewConnectionId { .. } | Self::PathChallenge { .. } | Self::PathResponse { .. } ) } /// Converts `AckRanges` as encoded in a ACK frame (see -transport /// 19.3.1) into ranges of acked packets (end, start), inclusive of /// start and end values. /// /// # Errors /// /// Returns an error if the ranges are invalid. pub fn decode_ack_frame( largest_acked: u64, first_ack_range: u64, ack_ranges: &[AckRange], ) -> Res>> { let mut acked_ranges = Vec::with_capacity(ack_ranges.len() + 1); if largest_acked < first_ack_range { return Err(Error::FrameEncoding); } acked_ranges.push((largest_acked - first_ack_range)..=largest_acked); if !ack_ranges.is_empty() && largest_acked < first_ack_range + 1 { return Err(Error::FrameEncoding); } let mut cur = if ack_ranges.is_empty() { 0 } else { largest_acked - first_ack_range - 1 }; for r in ack_ranges { if cur < r.gap + 1 { return Err(Error::FrameEncoding); } cur = cur - r.gap - 1; if cur < r.range { return Err(Error::FrameEncoding); } acked_ranges.push((cur - r.range)..=cur); if cur > r.range + 1 { cur -= r.range + 1; } else { cur -= r.range; } } Ok(acked_ranges) } #[must_use] pub fn dump(&self) -> String { match self { Self::Crypto { offset, data } => { format!("Crypto {{ offset: {offset}, len: {} }}", data.len()) } Self::Stream { stream_id, offset, fill, data, fin, } => format!( "Stream {{ stream_id: {}, offset: {offset}, len: {}{}, fin: {fin} }}", stream_id.as_u64(), if *fill { ">>" } else { "" }, data.len(), ), Self::Padding(length) => format!("Padding {{ len: {length} }}"), Self::Datagram { data, .. } => format!("Datagram {{ len: {} }}", data.len()), _ => format!("{self:?}"), } } #[must_use] pub fn is_allowed(&self, pt: packet::Type) -> bool { match self { Self::Padding { .. } | Self::Ping => true, Self::Crypto { .. } | Self::Ack { .. } | Self::ConnectionClose { error_code: CloseError::Transport(_), .. } => pt != packet::Type::ZeroRtt, Self::NewToken { .. } | Self::ConnectionClose { .. } => pt == packet::Type::Short, _ => pt == packet::Type::ZeroRtt || pt == packet::Type::Short, } } /// # Errors /// /// Returns an error if the frame cannot be decoded. #[expect( clippy::too_many_lines, reason = "Yeah, but it's a nice match statement." )] pub fn decode(dec: &mut Decoder<'a>) -> Res { /// Maximum ACK Range Count in ACK Frame /// /// Given a max UDP datagram size of 64k bytes and a minimum ACK Range size of 2 /// bytes (2 QUIC varints), a single datagram can at most contain 32k ACK /// Ranges. /// /// Note that the maximum (jumbogram) Ethernet MTU of 9216 or on the /// Internet the regular Ethernet MTU of 1518 are more realistically to /// be the limiting factor. Though for simplicity the higher limit is chosen. const MAX_ACK_RANGE_COUNT: u64 = 32 * 1024; fn d(v: Option) -> Res { v.ok_or(Error::NoMoreData) } fn dv(dec: &mut Decoder) -> Res { d(dec.decode_varint()) } fn decode_ack<'a>(dec: &mut Decoder<'a>, ecn: bool) -> Res> { let la = dv(dec)?; let ad = dv(dec)?; let nr = dv(dec).and_then(|nr| { if nr < MAX_ACK_RANGE_COUNT { Ok(nr) } else { Err(Error::TooMuchData) } })?; let fa = dv(dec)?; let mut arr: Vec = Vec::with_capacity(usize::try_from(nr)?); for _ in 0..nr { let ar = AckRange { gap: dv(dec)?, range: dv(dec)?, }; arr.push(ar); } // Now check for the values for ACK_ECN. let ecn_count = ecn .then(|| -> Res { Ok(ecn::Count::new(0, dv(dec)?, dv(dec)?, dv(dec)?)) }) .transpose()?; Ok(Frame::Ack { largest_acknowledged: la, ack_delay: ad, first_ack_range: fa, ack_ranges: arr, ecn_count, }) } // Check for minimal encoding of frame type. let pos = dec.offset(); let t = dv(dec)?; // RFC 9000, Section 12.4: // // The Frame Type field uses a variable-length integer encoding [...], // with one exception. To ensure simple and efficient implementations of // frame parsing, a frame type MUST use the shortest possible encoding. if Encoder::varint_len(t) != dec.offset() - pos { return Err(Error::ProtocolViolation); } let t = t.try_into()?; match t { FrameType::Padding => { // t itself + any additional `Frame::Padding` (1 + dec.skip_while(u8::from(FrameType::Padding))) .try_into() .map(Self::Padding) .map_err(|_| Error::TooMuchData) } FrameType::Ping => Ok(Self::Ping), FrameType::ResetStream => Ok(Self::ResetStream { stream_id: StreamId::from(dv(dec)?), application_error_code: dv(dec)?, final_size: match dec.decode_varint() { Some(v) => v, None => return Err(Error::NoMoreData), }, }), FrameType::Ack => decode_ack(dec, false), FrameType::AckEcn => decode_ack(dec, true), FrameType::StopSending => Ok(Self::StopSending { stream_id: StreamId::from(dv(dec)?), application_error_code: dv(dec)?, }), FrameType::Crypto => { let offset = dv(dec)?; let data = d(dec.decode_vvec())?; if offset + u64::try_from(data.len())? > MAX_VARINT { return Err(Error::FrameEncoding); } Ok(Self::Crypto { offset, data }) } FrameType::NewToken => { let token = d(dec.decode_vvec())?; if token.is_empty() { return Err(Error::FrameEncoding); } Ok(Self::NewToken { token }) } FrameType::Stream | FrameType::StreamWithFin | FrameType::StreamWithLen | FrameType::StreamWithLenFin | FrameType::StreamWithOff | FrameType::StreamWithOffFin | FrameType::StreamWithOffLen | FrameType::StreamWithOffLenFin => { let s = dv(dec)?; let o = if t.is_stream_with_offset() { dv(dec)? } else { 0 }; let fill = !t.is_stream_with_length(); let data = if fill { qtrace!("STREAM frame, extends to the end of the packet"); dec.decode_remainder() } else { qtrace!("STREAM frame, with length"); d(dec.decode_vvec())? }; if o + u64::try_from(data.len())? > MAX_VARINT { return Err(Error::FrameEncoding); } Ok(Self::Stream { fin: t.is_stream_with_fin(), stream_id: StreamId::from(s), offset: o, data, fill, }) } FrameType::MaxData => Ok(Self::MaxData { maximum_data: dv(dec)?, }), FrameType::MaxStreamData => Ok(Self::MaxStreamData { stream_id: StreamId::from(dv(dec)?), maximum_stream_data: dv(dec)?, }), FrameType::MaxStreamsBiDi | FrameType::MaxStreamsUniDi => { let m = dv(dec)?; if m > (1 << 60) { return Err(Error::StreamLimit); } Ok(Self::MaxStreams { stream_type: t.try_into()?, maximum_streams: m, }) } FrameType::DataBlocked => Ok(Self::DataBlocked { data_limit: dv(dec)?, }), FrameType::StreamDataBlocked => Ok(Self::StreamDataBlocked { stream_id: dv(dec)?.into(), stream_data_limit: dv(dec)?, }), FrameType::StreamsBlockedBiDi | FrameType::StreamsBlockedUniDi => { Ok(Self::StreamsBlocked { stream_type: t.try_into()?, stream_limit: dv(dec)?, }) } FrameType::NewConnectionId => { let sequence_number = dv(dec)?; let retire_prior = dv(dec)?; let connection_id = d(dec.decode_vec(1))?; if connection_id.len() > ConnectionId::MAX_LEN { return Err(Error::FrameEncoding); } let stateless_reset_token = Srt::try_from(dec)?; Ok(Self::NewConnectionId { sequence_number, retire_prior, connection_id, stateless_reset_token, }) } FrameType::RetireConnectionId => Ok(Self::RetireConnectionId { sequence_number: dv(dec)?, }), FrameType::PathChallenge => { let data = d(dec.decode(8))?; let mut datav: [u8; 8] = [0; 8]; datav.copy_from_slice(data); Ok(Self::PathChallenge { data: datav }) } FrameType::PathResponse => { let data = d(dec.decode(8))?; let mut datav: [u8; 8] = [0; 8]; datav.copy_from_slice(data); Ok(Self::PathResponse { data: datav }) } FrameType::ConnectionCloseTransport | FrameType::ConnectionCloseApplication => { let (error_code, frame_type) = if t == FrameType::ConnectionCloseTransport { (CloseError::Transport(dv(dec)?), dv(dec)?) } else { (CloseError::Application(dv(dec)?), 0) }; // We can tolerate this copy for now. let reason_phrase = String::from_utf8_lossy(d(dec.decode_vvec())?).to_string(); Ok(Self::ConnectionClose { error_code, frame_type, reason_phrase, }) } FrameType::HandshakeDone => Ok(Self::HandshakeDone), FrameType::AckFrequency => { let seqno = dv(dec)?; let tolerance = dv(dec)?; if tolerance == 0 { return Err(Error::FrameEncoding); } let delay = dv(dec)?; let ignore_order = match d(dec.decode_uint::())? { 0 => false, 1 => true, _ => return Err(Error::FrameEncoding), }; Ok(Self::AckFrequency { seqno, tolerance, delay, ignore_order, }) } FrameType::Datagram | FrameType::DatagramWithLen => { let fill = t == FrameType::Datagram; let data = if fill { qtrace!("DATAGRAM frame, extends to the end of the packet"); dec.decode_remainder() } else { qtrace!("DATAGRAM frame, with length"); d(dec.decode_vvec())? }; Ok(Self::Datagram { data, fill }) } } } } /// Extension trait for [`Encoder`] that automates writing to fuzzing corpus. pub trait FrameEncoder { /// Encode a frame with the given type and encoding closure. /// /// This method: /// 1. Encodes the frame type as a varint /// 2. Calls the provided closure to encode the frame-specific data /// 3. When fuzzing corpus collection is enabled, saves the frame to the corpus /// /// # Example /// ```ignore /// builder.encode_frame(FrameType::NewToken, |b| { /// b.encode_vvec(&token); /// }); /// ``` fn encode_frame(&mut self, frame_type: T, encode_fn: F) -> &mut Self where T: Into, F: FnOnce(&mut Self); } impl FrameEncoder for Encoder { fn encode_frame(&mut self, frame_type: T, encode_fn: F) -> &mut Self where T: Into, F: FnOnce(&mut Self), { #[cfg(feature = "build-fuzzing-corpus")] let frame_start = self.len(); self.encode_varint(frame_type.into()); encode_fn(self); #[cfg(feature = "build-fuzzing-corpus")] neqo_common::write_item_to_fuzzing_corpus("frame", &self.as_ref()[frame_start..]); self } } #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { use neqo_common::{Decoder, Encoder}; use crate::{ CloseError, ConnectionId, Error, StreamId, StreamType, Token as Srt, ecn::Count, frame::{AckRange, Frame, FrameType}, }; fn just_dec(f: &Frame, s: &str) { let encoded = Encoder::from_hex(s); let decoded = Frame::decode(&mut encoded.as_decoder()).expect("Failed to decode frame"); assert_eq!(*f, decoded); } #[test] fn padding() { let f = Frame::Padding(1); just_dec(&f, "00"); let f = Frame::Padding(2); just_dec(&f, "0000"); } #[test] fn ping() { let f = Frame::Ping; just_dec(&f, "01"); } #[test] fn ack() { let ar = vec![AckRange { gap: 1, range: 2 }, AckRange { gap: 3, range: 4 }]; let f = Frame::Ack { largest_acknowledged: 0x1234, ack_delay: 0x1235, first_ack_range: 0x1236, ack_ranges: ar.clone(), ecn_count: None, }; just_dec(&f, "025234523502523601020304"); // Try to parse ACK_ECN without ECN values let enc = Encoder::from_hex("035234523502523601020304"); let mut dec = enc.as_decoder(); assert_eq!(Frame::decode(&mut dec).unwrap_err(), Error::NoMoreData); // Try to parse ACK_ECN with ECN values let ecn_count = Some(Count::new(0, 1, 2, 3)); let fe = Frame::Ack { largest_acknowledged: 0x1234, ack_delay: 0x1235, first_ack_range: 0x1236, ack_ranges: ar, ecn_count, }; let enc = Encoder::from_hex("035234523502523601020304010203"); let mut dec = enc.as_decoder(); assert_eq!(Frame::decode(&mut dec).unwrap(), fe); } #[test] fn reset_stream() { let f = Frame::ResetStream { stream_id: StreamId::from(0x1234), application_error_code: 0x77, final_size: 0x3456, }; just_dec(&f, "04523440777456"); } #[test] fn stop_sending() { let f = Frame::StopSending { stream_id: StreamId::from(63), application_error_code: 0x77, }; just_dec(&f, "053F4077"); } #[test] fn crypto() { let f = Frame::Crypto { offset: 1, data: &[1, 2, 3], }; just_dec(&f, "060103010203"); } #[test] fn new_token() { let f = Frame::NewToken { token: &[0x12, 0x34, 0x56], }; just_dec(&f, "0703123456"); } #[test] fn empty_new_token() { let mut dec = Decoder::from(&[0x07, 0x00][..]); assert_eq!(Frame::decode(&mut dec).unwrap_err(), Error::FrameEncoding); } #[test] fn stream() { // First, just set the length bit. let f = Frame::Stream { fin: false, stream_id: StreamId::from(5), offset: 0, data: &[1, 2, 3], fill: false, }; just_dec(&f, "0a0503010203"); // Now with offset != 0 and FIN let f = Frame::Stream { fin: true, stream_id: StreamId::from(5), offset: 1, data: &[1, 2, 3], fill: false, }; just_dec(&f, "0f050103010203"); // Now to fill the packet. let f = Frame::Stream { fin: true, stream_id: StreamId::from(5), offset: 0, data: &[1, 2, 3], fill: true, }; just_dec(&f, "0905010203"); } #[test] fn max_data() { let f = Frame::MaxData { maximum_data: 0x1234, }; just_dec(&f, "105234"); } #[test] fn max_stream_data() { let f = Frame::MaxStreamData { stream_id: StreamId::from(5), maximum_stream_data: 0x1234, }; just_dec(&f, "11055234"); } #[test] fn max_streams() { let mut f = Frame::MaxStreams { stream_type: StreamType::BiDi, maximum_streams: 0x1234, }; just_dec(&f, "125234"); f = Frame::MaxStreams { stream_type: StreamType::UniDi, maximum_streams: 0x1234, }; just_dec(&f, "135234"); } #[test] fn data_blocked() { let f = Frame::DataBlocked { data_limit: 0x1234 }; just_dec(&f, "145234"); } #[test] fn stream_data_blocked() { let f = Frame::StreamDataBlocked { stream_id: StreamId::from(5), stream_data_limit: 0x1234, }; just_dec(&f, "15055234"); } #[test] fn streams_blocked() { let mut f = Frame::StreamsBlocked { stream_type: StreamType::BiDi, stream_limit: 0x1234, }; just_dec(&f, "165234"); f = Frame::StreamsBlocked { stream_type: StreamType::UniDi, stream_limit: 0x1234, }; just_dec(&f, "175234"); } #[test] fn new_connection_id() { let f = Frame::NewConnectionId { sequence_number: 0x1234, retire_prior: 0, connection_id: &[0x01, 0x02], stateless_reset_token: Srt::new([9; Srt::LEN]), }; just_dec(&f, "1852340002010209090909090909090909090909090909"); } #[test] fn too_large_new_connection_id() { let mut enc = Encoder::from_hex("18523400"); // up to the CID enc.encode_vvec(&[0x0c; ConnectionId::MAX_LEN + 10]); enc.encode(&[0x11; 16][..]); assert_eq!( Frame::decode(&mut enc.as_decoder()).unwrap_err(), Error::FrameEncoding ); } #[test] fn retire_connection_id() { let f = Frame::RetireConnectionId { sequence_number: 0x1234, }; just_dec(&f, "195234"); } #[test] fn path_challenge() { let f = Frame::PathChallenge { data: [9; 8] }; just_dec(&f, "1a0909090909090909"); } #[test] fn path_response() { let f = Frame::PathResponse { data: [9; 8] }; just_dec(&f, "1b0909090909090909"); } #[test] fn connection_close_transport() { let f = Frame::ConnectionClose { error_code: CloseError::Transport(0x5678), frame_type: 0x1234, reason_phrase: String::from("\x01\x02\x03"), }; just_dec(&f, "1c80005678523403010203"); } #[test] fn connection_close_application() { let f = Frame::ConnectionClose { error_code: CloseError::Application(0x5678), frame_type: 0, reason_phrase: String::from("\x01\x02\x03"), }; just_dec(&f, "1d8000567803010203"); } #[test] fn compare() { let f1 = Frame::Padding(1); let f2 = Frame::Padding(1); let f3 = Frame::Crypto { offset: 0, data: &[1, 2, 3], }; let f4 = Frame::Crypto { offset: 0, data: &[1, 2, 3], }; let f5 = Frame::Crypto { offset: 1, data: &[1, 2, 3], }; let f6 = Frame::Crypto { offset: 0, data: &[1, 2, 4], }; assert_eq!(f1, f2); assert_ne!(f1, f3); assert_eq!(f3, f4); assert_ne!(f3, f5); assert_ne!(f3, f6); } #[test] fn decode_ack_frame() { let res = Frame::decode_ack_frame(7, 2, &[AckRange { gap: 0, range: 3 }]); assert!(res.is_ok()); assert_eq!(res.unwrap(), vec![5..=7, 0..=3]); } #[test] fn ack_frequency() { let f = Frame::AckFrequency { seqno: 10, tolerance: 5, delay: 2000, ignore_order: true, }; just_dec(&f, "40af0a0547d001"); } #[test] fn ack_frequency_ignore_error_error() { let enc = Encoder::from_hex("40af0a0547d003"); // ignore_order of 3 assert_eq!( Frame::decode(&mut enc.as_decoder()).unwrap_err(), Error::FrameEncoding ); } /// Hopefully this test is eventually redundant. #[test] fn ack_frequency_zero_packets() { let enc = Encoder::from_hex("40af0a000101"); // packets of 0 assert_eq!( Frame::decode(&mut enc.as_decoder()).unwrap_err(), Error::FrameEncoding ); } #[test] fn datagram() { // Without the length bit. let f = Frame::Datagram { data: &[1, 2, 3], fill: true, }; just_dec(&f, "30010203"); // With the length bit. let f = Frame::Datagram { data: &[1, 2, 3], fill: false, }; just_dec(&f, "3103010203"); } #[test] fn frame_decode_enforces_bound_on_ack_range() { let mut e = Encoder::default(); e.encode_varint(FrameType::Ack); e.encode_varint(0u64); // largest acknowledged e.encode_varint(0u64); // ACK delay e.encode_varint(u32::MAX); // ACK range count = huge, but maybe available for allocation assert_eq!(Err(Error::TooMuchData), Frame::decode(&mut e.as_decoder())); } #[test] #[should_panic(expected = "Failed to decode frame")] fn invalid_frame_type_len() { let f = Frame::Datagram { data: &[1, 2, 3], fill: true, }; just_dec(&f, "4030010203"); } /// See bug in . #[test] fn padding_frame_u16_overflow() { let mut e = Encoder::default(); e.encode_varint(FrameType::Padding); // `Frame::Padding` uses u16 to store length. Try to overflow length. e.pad_to(u16::MAX as usize + 1, 0); assert_eq!(Frame::decode(&mut e.as_decoder()), Err(Error::TooMuchData)); } #[test] fn frame_type_to_u8() { assert_eq!(u8::from(FrameType::Padding), 0); assert_eq!(u8::from(FrameType::Ping), 1); } #[test] #[expect(clippy::too_many_lines, reason = "OK in tests.")] fn dump() { let s = |id| StreamId::from(id); assert_eq!(Frame::Padding(5).dump(), "Padding { len: 5 }"); assert_eq!( Frame::Crypto { offset: 1, data: &[2, 3] } .dump(), "Crypto { offset: 1, len: 2 }" ); assert_eq!( Frame::Stream { stream_id: s(4), offset: 10, data: &[1], fin: true, fill: false } .dump(), "Stream { stream_id: 4, offset: 10, len: 1, fin: true }" ); assert_eq!( Frame::Stream { stream_id: s(4), offset: 0, data: &[1, 2], fin: false, fill: true } .dump(), "Stream { stream_id: 4, offset: 0, len: >>2, fin: false }" ); assert_eq!( Frame::Datagram { data: &[1, 2, 3], fill: false } .dump(), "Datagram { len: 3 }" ); // Remaining frames use Debug format assert_eq!(Frame::Ping.dump(), "Ping"); assert_eq!( Frame::Ack { largest_acknowledged: 1, ack_delay: 2, first_ack_range: 0, ack_ranges: vec![], ecn_count: None } .dump(), "Ack { largest_acknowledged: 1, ack_delay: 2, first_ack_range: 0, ack_ranges: [], ecn_count: None }" ); assert_eq!( Frame::ResetStream { stream_id: s(1), application_error_code: 2, final_size: 3 } .dump(), "ResetStream { stream_id: StreamId(1), application_error_code: 2, final_size: 3 }" ); assert_eq!( Frame::StopSending { stream_id: s(1), application_error_code: 2 } .dump(), "StopSending { stream_id: StreamId(1), application_error_code: 2 }" ); assert_eq!( Frame::NewToken { token: &[1] }.dump(), "NewToken { token: [1] }" ); assert_eq!( Frame::MaxData { maximum_data: 100 }.dump(), "MaxData { maximum_data: 100 }" ); assert_eq!( Frame::MaxStreamData { stream_id: s(1), maximum_stream_data: 100 } .dump(), "MaxStreamData { stream_id: StreamId(1), maximum_stream_data: 100 }" ); assert_eq!( Frame::MaxStreams { stream_type: StreamType::BiDi, maximum_streams: 10 } .dump(), "MaxStreams { stream_type: BiDi, maximum_streams: 10 }" ); assert_eq!( Frame::DataBlocked { data_limit: 50 }.dump(), "DataBlocked { data_limit: 50 }" ); assert_eq!( Frame::StreamDataBlocked { stream_id: s(1), stream_data_limit: 50 } .dump(), "StreamDataBlocked { stream_id: StreamId(1), stream_data_limit: 50 }" ); assert_eq!( Frame::StreamsBlocked { stream_type: StreamType::UniDi, stream_limit: 5 } .dump(), "StreamsBlocked { stream_type: UniDi, stream_limit: 5 }" ); assert_eq!( Frame::NewConnectionId { sequence_number: 1, retire_prior: 0, connection_id: &[1, 2], stateless_reset_token: Srt::new([0; 16]) } .dump(), "NewConnectionId { sequence_number: 1, retire_prior: 0, connection_id: [1, 2], stateless_reset_token: Token([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }" ); assert_eq!( Frame::RetireConnectionId { sequence_number: 1 }.dump(), "RetireConnectionId { sequence_number: 1 }" ); assert_eq!( Frame::PathChallenge { data: [1; 8] }.dump(), "PathChallenge { data: [1, 1, 1, 1, 1, 1, 1, 1] }" ); assert_eq!( Frame::PathResponse { data: [2; 8] }.dump(), "PathResponse { data: [2, 2, 2, 2, 2, 2, 2, 2] }" ); assert_eq!( Frame::ConnectionClose { error_code: CloseError::Transport(0), frame_type: 0, reason_phrase: String::new() } .dump(), "ConnectionClose { error_code: Transport(0), frame_type: 0, reason_phrase: \"\" }" ); assert_eq!(Frame::HandshakeDone.dump(), "HandshakeDone"); assert_eq!( Frame::AckFrequency { seqno: 1, tolerance: 2, delay: 3, ignore_order: false } .dump(), "AckFrequency { seqno: 1, tolerance: 2, delay: 3, ignore_order: false }" ); } }