use std::ops::RangeInclusive;
use crate::constants;
use crate::varint::{self, VarInt};
use super::stream_id::StreamId;
pub(super) const STREAM_FILL_QUANTUM: usize = 1024;
const _: () =
assert!(STREAM_FILL_QUANTUM as u64 >= crate::constants::REASSEMBLY_MIN_CONFORMING_FRAME);
pub(super) const PATH_CHALLENGE_LEN: usize = 8;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Frame {
Padding,
Ping,
Ack(Ack),
ResetStream(ResetStream),
Stream(Stream),
MaxData(u64),
MaxStreamData(MaxStreamData),
MaxStreamsBidi(u64),
MaxStreamsUni(u64),
PathChallenge([u8; 8]),
PathResponse([u8; 8]),
Close(Close),
Datagram(Datagram),
}
impl Frame {
pub(crate) fn type_code(&self) -> u64 {
match self {
Frame::Padding => constants::FRAME_PADDING,
Frame::Ping => constants::FRAME_PING,
Frame::Ack(_) => constants::FRAME_ACK,
Frame::ResetStream(_) => constants::FRAME_RESET_STREAM,
Frame::Stream(stream) => stream.type_code(),
Frame::MaxData(_) => constants::FRAME_MAX_DATA,
Frame::MaxStreamData(_) => constants::FRAME_MAX_STREAM_DATA,
Frame::MaxStreamsBidi(_) => constants::FRAME_MAX_STREAMS_BIDI,
Frame::MaxStreamsUni(_) => constants::FRAME_MAX_STREAMS_UNI,
Frame::PathChallenge(_) => constants::FRAME_PATH_CHALLENGE,
Frame::PathResponse(_) => constants::FRAME_PATH_RESPONSE,
Frame::Close(_) => constants::FRAME_CLOSE,
Frame::Datagram(datagram) => datagram.type_code(),
}
}
pub(crate) fn is_ack_eliciting(&self) -> bool {
is_ack_eliciting(self.type_code())
}
pub(crate) fn encoded_len(&self) -> usize {
match self {
Frame::Padding | Frame::Ping => 1,
Frame::Ack(ack) => 1 + ack.body_len(),
Frame::ResetStream(reset) => 1 + reset.body_len(),
Frame::Stream(stream) => 1 + stream.body_len(),
Frame::MaxData(max) | Frame::MaxStreamsBidi(max) | Frame::MaxStreamsUni(max) => {
1 + varint_len(*max)
}
Frame::MaxStreamData(grant) => 1 + grant.body_len(),
Frame::PathChallenge(_) | Frame::PathResponse(_) => 1 + PATH_CHALLENGE_LEN,
Frame::Close(close) => 1 + close.body_len(),
Frame::Datagram(datagram) => 1 + datagram.body_len(),
}
}
pub(crate) fn extends_to_end(&self) -> bool {
match self {
Frame::Stream(s) => !s.len_present,
Frame::Datagram(d) => !d.len_present,
_ => false,
}
}
pub(crate) fn encode(&self, out: &mut Vec<u8>) {
varint::encode(
VarInt::new(self.type_code()).expect("§8.3's type codes are all far below 2⁶² − 1"),
out,
);
match self {
Frame::Padding | Frame::Ping => {}
Frame::Ack(ack) => ack.encode_body(out),
Frame::ResetStream(reset) => reset.encode_body(out),
Frame::Stream(stream) => stream.encode_body(out),
Frame::MaxData(max) | Frame::MaxStreamsBidi(max) | Frame::MaxStreamsUni(max) => {
put_varint(*max, out)
}
Frame::MaxStreamData(grant) => grant.encode_body(out),
Frame::PathChallenge(value) | Frame::PathResponse(value) => {
out.extend_from_slice(value)
}
Frame::Close(close) => close.encode_body(out),
Frame::Datagram(datagram) => datagram.encode_body(out),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Datagram {
pub(crate) data: Vec<u8>,
pub(crate) len_present: bool,
}
impl Datagram {
pub(crate) fn type_code(&self) -> u64 {
if self.len_present {
constants::FRAME_DATAGRAM_LEN
} else {
constants::FRAME_DATAGRAM
}
}
fn body_len(&self) -> usize {
if self.len_present {
varint_len(self.data.len() as u64) + self.data.len()
} else {
self.data.len()
}
}
fn encode_body(&self, out: &mut Vec<u8>) {
if self.len_present {
put_varint(self.data.len() as u64, out);
}
out.extend_from_slice(&self.data);
}
fn parse_body(ty: u64, buf: &[u8]) -> Result<(Datagram, usize), Structural> {
let len_present = ty == constants::FRAME_DATAGRAM_LEN;
let mut cursor = Cursor::new(buf);
let data = if len_present {
let len = cursor.varint()?;
let len = usize::try_from(len).map_err(|_| Structural::LengthOverrun)?;
cursor.bytes(len)?.to_vec()
} else {
let rest = cursor.rest().to_vec();
cursor.advance(rest.len());
rest
};
Ok((Datagram { data, len_present }, cursor.consumed()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ResetStream {
pub(crate) id: StreamId,
pub(crate) error_code: u64,
pub(crate) final_size: u64,
}
impl ResetStream {
fn body_len(&self) -> usize {
varint_len(self.id.as_u64()) + varint_len(self.error_code) + varint_len(self.final_size)
}
fn encode_body(&self, out: &mut Vec<u8>) {
put_varint(self.id.as_u64(), out);
put_varint(self.error_code, out);
put_varint(self.final_size, out);
}
fn parse_body(buf: &[u8]) -> Result<(ResetStream, usize), Structural> {
let mut cursor = Cursor::new(buf);
let id = StreamId::from_u64(cursor.varint()?);
let error_code = cursor.varint()?;
let final_size = cursor.varint()?;
Ok((
ResetStream {
id,
error_code,
final_size,
},
cursor.consumed(),
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Stream {
pub(crate) id: StreamId,
pub(crate) offset: u64,
pub(crate) off_present: bool,
pub(crate) len_present: bool,
pub(crate) fin: bool,
pub(crate) data: Vec<u8>,
}
impl Stream {
pub(crate) fn new(id: StreamId, offset: u64, data: Vec<u8>, fin: bool) -> Self {
Self {
id,
offset,
off_present: offset != 0,
len_present: true,
fin,
data,
}
}
pub(crate) fn type_code(&self) -> u64 {
let mut ty = constants::FRAME_STREAM_BASE;
if self.off_present {
ty |= constants::STREAM_OFF;
}
if self.len_present {
ty |= constants::STREAM_LEN;
}
if self.fin {
ty |= constants::STREAM_FIN;
}
ty
}
fn body_len(&self) -> usize {
varint_len(self.id.as_u64())
+ if self.off_present {
varint_len(self.offset)
} else {
0
}
+ if self.len_present {
varint_len(self.data.len() as u64)
} else {
0
}
+ self.data.len()
}
fn encode_body(&self, out: &mut Vec<u8>) {
put_varint(self.id.as_u64(), out);
if self.off_present {
put_varint(self.offset, out);
}
if self.len_present {
put_varint(self.data.len() as u64, out);
}
out.extend_from_slice(&self.data);
}
fn parse_body(ty: u64, buf: &[u8]) -> Result<(Stream, usize), Structural> {
let off_present = ty & constants::STREAM_OFF != 0;
let len_present = ty & constants::STREAM_LEN != 0;
let fin = ty & constants::STREAM_FIN != 0;
let mut cursor = Cursor::new(buf);
let id = StreamId::from_u64(cursor.varint()?);
let offset = if off_present { cursor.varint()? } else { 0 };
let data = if len_present {
let len = cursor.varint()?;
let len = usize::try_from(len).map_err(|_| Structural::LengthOverrun)?;
cursor.bytes(len)?.to_vec()
} else {
let rest = cursor.rest().to_vec();
cursor.advance(rest.len());
rest
};
let end = offset
.checked_add(data.len() as u64)
.filter(|end| *end <= VarInt::MAX_VALUE)
.ok_or(Structural::StreamOffsetOverflow)?;
let _ = end;
Ok((
Stream {
id,
offset,
off_present,
len_present,
fin,
data,
},
cursor.consumed(),
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct MaxStreamData {
pub(crate) id: StreamId,
pub(crate) max: u64,
}
impl MaxStreamData {
fn body_len(&self) -> usize {
varint_len(self.id.as_u64()) + varint_len(self.max)
}
fn encode_body(&self, out: &mut Vec<u8>) {
put_varint(self.id.as_u64(), out);
put_varint(self.max, out);
}
fn parse_body(buf: &[u8]) -> Result<(MaxStreamData, usize), Structural> {
let mut cursor = Cursor::new(buf);
let id = StreamId::from_u64(cursor.varint()?);
let max = cursor.varint()?;
Ok((MaxStreamData { id, max }, cursor.consumed()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Ack {
pub(crate) largest: u64,
pub(crate) ack_delay: u64,
pub(crate) first_range: u64,
pub(crate) ranges: Vec<(u64, u64)>,
}
impl Ack {
pub(crate) fn ranges_desc(&self) -> Vec<RangeInclusive<u64>> {
let mut out = Vec::with_capacity(1 + self.ranges.len());
let mut smallest = self.largest.saturating_sub(self.first_range);
out.push(smallest..=self.largest);
for (gap, range) in &self.ranges {
let largest = smallest.saturating_sub(*gap).saturating_sub(2);
smallest = largest.saturating_sub(*range);
out.push(smallest..=largest);
}
out
}
fn validate(&self) -> Result<(), Structural> {
if self.ranges.len() > constants::MAX_ACK_RANGES {
return Err(Structural::AckRangeCount(self.ranges.len() as u64));
}
let mut smallest = self
.largest
.checked_sub(self.first_range)
.ok_or(Structural::AckRangeUnderflow)?;
for (gap, range) in &self.ranges {
let largest = smallest
.checked_sub(*gap)
.and_then(|v| v.checked_sub(2))
.ok_or(Structural::AckRangeUnderflow)?;
smallest = largest
.checked_sub(*range)
.ok_or(Structural::AckRangeUnderflow)?;
}
Ok(())
}
pub(crate) fn encoded_len(&self) -> usize {
varint_len(constants::FRAME_ACK) + self.body_len()
}
fn body_len(&self) -> usize {
varint_len(self.largest)
+ varint_len(self.ack_delay)
+ varint_len(self.ranges.len() as u64)
+ varint_len(self.first_range)
+ self
.ranges
.iter()
.map(|(gap, range)| varint_len(*gap) + varint_len(*range))
.sum::<usize>()
}
fn encode_body(&self, out: &mut Vec<u8>) {
put_varint(self.largest, out);
put_varint(self.ack_delay, out);
put_varint(self.ranges.len() as u64, out);
put_varint(self.first_range, out);
for (gap, range) in &self.ranges {
put_varint(*gap, out);
put_varint(*range, out);
}
}
fn parse_body(buf: &[u8]) -> Result<(Ack, usize), Structural> {
let mut cursor = Cursor::new(buf);
let largest = cursor.varint()?;
let ack_delay = cursor.varint()?;
let range_count = cursor.varint()?;
if range_count > constants::MAX_ACK_RANGES as u64 {
return Err(Structural::AckRangeCount(range_count));
}
let first_range = cursor.varint()?;
let mut ranges = Vec::with_capacity(range_count as usize);
for _ in 0..range_count {
let gap = cursor.varint()?;
let range = cursor.varint()?;
ranges.push((gap, range));
}
let ack = Ack {
largest,
ack_delay,
first_range,
ranges,
};
ack.validate()?;
Ok((ack, cursor.consumed()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Close {
pub(crate) code: u64,
pub(crate) reason: Vec<u8>,
}
impl Close {
pub(crate) fn new(code: u64, reason: &[u8]) -> Self {
let n = reason.len().min(constants::CLOSE_REASON_MAX);
Self {
code: code.min(VarInt::MAX_VALUE),
reason: reason[..n].to_vec(),
}
}
fn body_len(&self) -> usize {
varint_len(self.code) + varint_len(self.reason.len() as u64) + self.reason.len()
}
fn encode_body(&self, out: &mut Vec<u8>) {
debug_assert!(
self.reason.len() <= constants::CLOSE_REASON_MAX,
"§8.4: a CLOSE this implementation produced must never exceed CLOSE_REASON_MAX"
);
put_varint(self.code, out);
put_varint(self.reason.len() as u64, out);
out.extend_from_slice(&self.reason);
}
fn parse_body(buf: &[u8]) -> Result<(Close, usize), Structural> {
let mut cursor = Cursor::new(buf);
let code = cursor.varint()?;
let reason_len = cursor.varint()?;
if reason_len > constants::CLOSE_REASON_MAX as u64 {
return Err(Structural::CloseReasonTooLong(reason_len));
}
let reason = cursor.bytes(reason_len as usize)?.to_vec();
Ok((Close { code, reason }, cursor.consumed()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub(crate) enum Structural {
#[error("unknown frame type {0:#x}")]
UnknownType(u64),
#[error("a varint overruns the plaintext")]
VarintOverrun,
#[error("a length field overruns the plaintext")]
LengthOverrun,
#[error("ACK range_count {0} exceeds MAX_ACK_RANGES")]
AckRangeCount(u64),
#[error("an ACK range descends below counter zero")]
AckRangeUnderflow,
#[error("CLOSE reason_len {0} exceeds CLOSE_REASON_MAX")]
CloseReasonTooLong(u64),
#[error("a STREAM frame's offset + length exceeds 2⁶² − 1")]
StreamOffsetOverflow,
#[error("MAX_STREAMS max {0} exceeds 2⁶⁰")]
MaxStreamsTooLarge(u64),
#[error("a frame follows one that extends to the end of the plaintext")]
TrailingFrame,
}
pub(crate) fn parse(plaintext: &[u8]) -> Result<Vec<Frame>, Structural> {
let mut frames = Vec::new();
let mut cursor = Cursor::new(plaintext);
while !cursor.is_empty() {
if frames.last().is_some_and(Frame::extends_to_end) {
return Err(Structural::TrailingFrame);
}
let ty = cursor.varint()?;
let frame = match ty {
constants::FRAME_PADDING => Frame::Padding,
constants::FRAME_PING => Frame::Ping,
constants::FRAME_ACK => {
let (ack, used) = Ack::parse_body(cursor.rest())?;
cursor.advance(used);
Frame::Ack(ack)
}
constants::FRAME_RESET_STREAM => {
let (reset, used) = ResetStream::parse_body(cursor.rest())?;
cursor.advance(used);
Frame::ResetStream(reset)
}
ty @ constants::FRAME_STREAM_BASE..=constants::FRAME_STREAM_MAX => {
let (stream, used) = Stream::parse_body(ty, cursor.rest())?;
cursor.advance(used);
Frame::Stream(stream)
}
constants::FRAME_MAX_DATA => Frame::MaxData(cursor.varint()?),
constants::FRAME_MAX_STREAM_DATA => {
let (grant, used) = MaxStreamData::parse_body(cursor.rest())?;
cursor.advance(used);
Frame::MaxStreamData(grant)
}
constants::FRAME_MAX_STREAMS_BIDI | constants::FRAME_MAX_STREAMS_UNI => {
let max = cursor.varint()?;
if max > super::stream_id::MAX_STREAMS_CEILING {
return Err(Structural::MaxStreamsTooLarge(max));
}
if ty == constants::FRAME_MAX_STREAMS_BIDI {
Frame::MaxStreamsBidi(max)
} else {
Frame::MaxStreamsUni(max)
}
}
ty @ (constants::FRAME_PATH_CHALLENGE | constants::FRAME_PATH_RESPONSE) => {
let mut value = [0u8; PATH_CHALLENGE_LEN];
value.copy_from_slice(cursor.bytes(PATH_CHALLENGE_LEN)?);
if ty == constants::FRAME_PATH_CHALLENGE {
Frame::PathChallenge(value)
} else {
Frame::PathResponse(value)
}
}
constants::FRAME_CLOSE => {
let (close, used) = Close::parse_body(cursor.rest())?;
cursor.advance(used);
Frame::Close(close)
}
constants::FRAME_DATAGRAM | constants::FRAME_DATAGRAM_LEN => {
let (datagram, used) = Datagram::parse_body(ty, cursor.rest())?;
cursor.advance(used);
Frame::Datagram(datagram)
}
other => return Err(Structural::UnknownType(other)),
};
frames.push(frame);
}
Ok(frames)
}
pub(crate) fn packet_is_ack_eliciting(frames: &[Frame]) -> bool {
frames.iter().any(Frame::is_ack_eliciting)
}
pub(crate) fn is_ack_eliciting(ty: u64) -> bool {
match ty {
constants::FRAME_PADDING => false,
constants::FRAME_PING => true,
constants::FRAME_ACK => false,
constants::FRAME_RESET_STREAM => true,
constants::FRAME_STREAM_BASE..=constants::FRAME_STREAM_MAX => true,
constants::FRAME_MAX_DATA
| constants::FRAME_MAX_STREAM_DATA
| constants::FRAME_MAX_STREAMS_BIDI
| constants::FRAME_MAX_STREAMS_UNI => true,
constants::FRAME_PATH_CHALLENGE | constants::FRAME_PATH_RESPONSE => true,
constants::FRAME_CLOSE => false,
constants::FRAME_DATAGRAM | constants::FRAME_DATAGRAM_LEN => true,
_ => false,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Retransmission {
Ranges,
Regenerate,
Never,
}
pub(crate) fn retransmission(ty: u64) -> Option<Retransmission> {
match ty {
constants::FRAME_PADDING | constants::FRAME_PING | constants::FRAME_ACK => {
Some(Retransmission::Never)
}
constants::FRAME_RESET_STREAM => Some(Retransmission::Regenerate),
constants::FRAME_STREAM_BASE..=constants::FRAME_STREAM_MAX => Some(Retransmission::Ranges),
constants::FRAME_MAX_DATA
| constants::FRAME_MAX_STREAM_DATA
| constants::FRAME_MAX_STREAMS_BIDI
| constants::FRAME_MAX_STREAMS_UNI => Some(Retransmission::Regenerate),
constants::FRAME_PATH_CHALLENGE | constants::FRAME_PATH_RESPONSE => {
Some(Retransmission::Never)
}
constants::FRAME_CLOSE => Some(Retransmission::Never),
constants::FRAME_DATAGRAM | constants::FRAME_DATAGRAM_LEN => Some(Retransmission::Never),
_ => None,
}
}
pub(crate) struct Packing {
frames: Vec<Frame>,
used: usize,
budget: usize,
stage: Stage,
extends_to_end: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Stage {
Ack,
Control,
Fill,
Ping,
}
impl Packing {
pub(crate) fn new() -> Self {
Self::bounded(constants::MAX_PLAINTEXT)
}
pub(crate) fn bounded(budget: usize) -> Self {
Self {
frames: Vec::new(),
used: 0,
budget: budget.min(constants::MAX_PLAINTEXT),
stage: Stage::Ack,
extends_to_end: false,
}
}
pub(crate) fn ack(&mut self, ack: Ack) -> bool {
self.push(Stage::Ack, Frame::Ack(ack))
}
pub(crate) fn control(&mut self, frame: Frame) -> bool {
self.push(Stage::Control, frame)
}
pub(crate) fn path_challenge(&mut self, value: [u8; PATH_CHALLENGE_LEN]) -> bool {
self.push(Stage::Control, Frame::PathChallenge(value))
}
pub(crate) fn path_response(&mut self, value: [u8; PATH_CHALLENGE_LEN]) -> bool {
self.push(Stage::Control, Frame::PathResponse(value))
}
pub(crate) fn fill(&mut self, frame: Frame) -> bool {
self.push(Stage::Fill, frame)
}
pub(crate) fn datagram(&mut self, data: &[u8]) -> bool {
let n = data.len();
let Some(body) = self.room().checked_sub(1) else {
return false;
};
let len_present = if varint_len(n as u64) + n <= body {
true
} else if n <= body {
false
} else {
return false;
};
self.push(
Stage::Fill,
Frame::Datagram(Datagram {
data: data.to_vec(),
len_present,
}),
)
}
pub(crate) fn ping(&mut self) -> bool {
self.push(Stage::Ping, Frame::Ping)
}
pub(crate) fn room(&self) -> usize {
self.budget.saturating_sub(self.used)
}
pub(crate) fn stream_payload_room(&self, id: StreamId, offset: u64) -> Option<usize> {
let fixed = 1 + varint_len(id.as_u64()) + if offset != 0 { varint_len(offset) } else { 0 };
let avail = self.room().checked_sub(fixed)?;
for width in [1usize, 2, 4, 8] {
if avail
.checked_sub(width)
.is_some_and(|p| varint_len(p as u64) <= width)
{
return Some(avail - width);
}
}
None
}
pub(crate) fn frames(&self) -> &[Frame] {
&self.frames
}
pub(crate) fn into_plaintext(self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.used);
for frame in &self.frames {
frame.encode(&mut out);
}
debug_assert_eq!(out.len(), self.used, "encoded_len disagrees with encode");
debug_assert!(
out.len() <= constants::MAX_PLAINTEXT,
"§8.6's per-seal bound"
);
out
}
fn push(&mut self, stage: Stage, frame: Frame) -> bool {
debug_assert!(
stage >= self.stage,
"§8.5's packing order runs forwards only: {stage:?} after {:?}",
self.stage
);
self.stage = stage;
if self.extends_to_end {
return false;
}
let len = frame.encoded_len();
if self.used + len > self.budget {
return false;
}
self.extends_to_end = frame.extends_to_end();
self.used += len;
self.frames.push(frame);
true
}
}
struct Cursor<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
fn is_empty(&self) -> bool {
self.pos >= self.buf.len()
}
fn rest(&self) -> &'a [u8] {
&self.buf[self.pos..]
}
fn consumed(&self) -> usize {
self.pos
}
fn advance(&mut self, n: usize) {
self.pos += n;
}
fn varint(&mut self) -> Result<u64, Structural> {
let (v, n) = varint::decode(self.rest()).ok_or(Structural::VarintOverrun)?;
self.pos += n;
Ok(v.into_inner())
}
fn bytes(&mut self, n: usize) -> Result<&'a [u8], Structural> {
let rest = self.rest();
if rest.len() < n {
return Err(Structural::LengthOverrun);
}
self.pos += n;
Ok(&rest[..n])
}
}
fn to_varint(v: u64) -> VarInt {
VarInt::new(v).unwrap_or(VarInt::MAX)
}
fn varint_len(v: u64) -> usize {
to_varint(v).encoded_len()
}
fn put_varint(v: u64, out: &mut Vec<u8>) {
varint::encode(to_varint(v), out);
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(frame: &Frame) -> Vec<Frame> {
let mut bytes = Vec::new();
frame.encode(&mut bytes);
assert_eq!(bytes.len(), frame.encoded_len(), "encoded_len is wrong");
parse(&bytes).expect("a frame this codec produced must parse")
}
#[test]
fn padding_and_ping_are_one_byte_each() {
assert_eq!(round_trip(&Frame::Padding), vec![Frame::Padding]);
assert_eq!(round_trip(&Frame::Ping), vec![Frame::Ping]);
let mut bytes = Vec::new();
Frame::Padding.encode(&mut bytes);
assert_eq!(bytes, vec![0x00]);
bytes.clear();
Frame::Ping.encode(&mut bytes);
assert_eq!(bytes, vec![0x01]);
}
#[test]
fn the_path_frames_round_trip_as_nine_fixed_bytes() {
let value = [1u8, 2, 3, 4, 5, 6, 7, 8];
for frame in [Frame::PathChallenge(value), Frame::PathResponse(value)] {
assert_eq!(frame.encoded_len(), 9, "1 type byte + 8 opaque bytes");
assert_eq!(round_trip(&frame), vec![frame.clone()]);
let mut bytes = Vec::new();
frame.encode(&mut bytes);
assert_eq!(bytes[0], frame.type_code() as u8);
assert_eq!(&bytes[1..], &value, "the body is the eight bytes, raw");
}
}
#[test]
fn padding_may_appear_anywhere() {
let plaintext = vec![0x00, 0x00, 0x01, 0x00, 0x01, 0x00];
assert_eq!(
parse(&plaintext).expect("PADDING is legal anywhere"),
vec![
Frame::Padding,
Frame::Padding,
Frame::Ping,
Frame::Padding,
Frame::Ping,
Frame::Padding
]
);
}
#[test]
fn close_round_trips_with_code_and_reason() {
let frame = Frame::Close(Close::new(0x42, b"because"));
assert_eq!(
round_trip(&frame),
vec![Frame::Close(Close {
code: 0x42,
reason: b"because".to_vec()
})]
);
}
#[test]
fn close_with_an_empty_reason_round_trips() {
let mut bytes = Vec::new();
Frame::Close(Close::new(constants::NO_ERROR, b"")).encode(&mut bytes);
assert_eq!(bytes, vec![0x1c, 0x00, 0x00]);
assert_eq!(
parse(&bytes).unwrap(),
vec![Frame::Close(Close {
code: 0,
reason: Vec::new()
})]
);
}
#[test]
fn close_new_truncates_at_close_reason_max() {
let long = vec![b'x'; constants::CLOSE_REASON_MAX + 64];
let close = Close::new(1, &long);
assert_eq!(close.reason.len(), constants::CLOSE_REASON_MAX);
}
#[test]
fn close_reason_len_boundary_is_two_sided() {
for (len, ok) in [
(constants::CLOSE_REASON_MAX - 1, true),
(constants::CLOSE_REASON_MAX, true),
(constants::CLOSE_REASON_MAX + 1, false),
] {
let mut bytes = vec![0x1c];
put_varint(7, &mut bytes);
put_varint(len as u64, &mut bytes);
bytes.extend(std::iter::repeat_n(b'z', len));
let parsed = parse(&bytes);
assert_eq!(
parsed.is_ok(),
ok,
"reason_len {len} should {} parse",
if ok { "" } else { "not" }
);
if !ok {
assert_eq!(
parsed.unwrap_err(),
Structural::CloseReasonTooLong(len as u64)
);
}
}
}
#[test]
fn close_reason_running_past_the_plaintext_is_structural() {
let mut bytes = vec![0x1c];
put_varint(1, &mut bytes);
put_varint(8, &mut bytes);
bytes.extend_from_slice(b"only4");
assert_eq!(parse(&bytes).unwrap_err(), Structural::LengthOverrun);
}
#[test]
fn ack_round_trips_with_extra_ranges() {
let ack = Ack {
largest: 100,
ack_delay: 1234,
first_range: 3,
ranges: vec![(0, 1), (4, 2)],
};
assert_eq!(round_trip(&Frame::Ack(ack.clone())), vec![Frame::Ack(ack)]);
}
#[test]
fn ack_ranges_are_descending_and_newest_first() {
let ack = Ack {
largest: 100,
ack_delay: 0,
first_range: 3,
ranges: vec![(0, 1), (4, 2)],
};
assert_eq!(ack.ranges_desc(), vec![97..=100, 94..=95, 86..=88]);
}
#[test]
fn ack_range_count_boundary_is_two_sided() {
for (count, ok) in [
(constants::MAX_ACK_RANGES - 1, true),
(constants::MAX_ACK_RANGES, true),
(constants::MAX_ACK_RANGES + 1, false),
] {
let ack = Ack {
largest: 1_000_000,
ack_delay: 0,
first_range: 0,
ranges: vec![(0, 0); count],
};
let mut bytes = Vec::new();
Frame::Ack(ack).encode(&mut bytes);
let parsed = parse(&bytes);
assert_eq!(parsed.is_ok(), ok, "range_count {count}");
if !ok {
assert_eq!(parsed.unwrap_err(), Structural::AckRangeCount(count as u64));
}
}
}
#[test]
fn an_ack_range_below_counter_zero_is_structural() {
let mut bytes = Vec::new();
Frame::Ack(Ack {
largest: 2,
ack_delay: 0,
first_range: 5,
ranges: Vec::new(),
})
.encode(&mut bytes);
assert_eq!(parse(&bytes).unwrap_err(), Structural::AckRangeUnderflow);
let mut bytes = Vec::new();
Frame::Ack(Ack {
largest: 10,
ack_delay: 0,
first_range: 4,
ranges: vec![(9, 0)],
})
.encode(&mut bytes);
assert_eq!(parse(&bytes).unwrap_err(), Structural::AckRangeUnderflow);
}
#[test]
fn a_truncated_ack_is_structural() {
let bytes = vec![0x02, 0x05]; assert_eq!(parse(&bytes).unwrap_err(), Structural::VarintOverrun);
}
#[test]
fn the_reserved_type_is_an_unknown_type() {
assert_eq!(
parse(&[constants::FRAME_STOP_SENDING_RESERVED as u8]).unwrap_err(),
Structural::UnknownType(0x05)
);
}
#[test]
fn an_unknown_type_is_structural() {
assert_eq!(parse(&[0x3f]).unwrap_err(), Structural::UnknownType(0x3f));
assert_eq!(parse(&[0x7f]).unwrap_err(), Structural::VarintOverrun);
assert_eq!(
parse(&[0x40, 0x7f]).unwrap_err(),
Structural::UnknownType(0x7f)
);
}
#[test]
fn a_valid_frame_before_an_unknown_type_is_not_applied() {
let mut bytes = Vec::new();
Frame::Close(Close::new(9, b"bye")).encode(&mut bytes);
bytes.push(0x3f);
assert_eq!(parse(&bytes).unwrap_err(), Structural::UnknownType(0x3f));
}
#[test]
fn a_non_minimally_encoded_type_code_still_parses() {
assert_eq!(parse(&[0x40, 0x01]).unwrap(), vec![Frame::Ping]);
}
#[test]
fn ack_eliciting_matches_the_whole_of_table_8_3() {
let expected: &[(u64, bool)] = &[
(constants::FRAME_PADDING, false),
(constants::FRAME_PING, true),
(constants::FRAME_ACK, false),
(constants::FRAME_RESET_STREAM, true),
(0x08, true),
(0x09, true),
(0x0a, true),
(0x0b, true),
(0x0c, true),
(0x0d, true),
(0x0e, true),
(0x0f, true),
(constants::FRAME_MAX_DATA, true),
(constants::FRAME_MAX_STREAM_DATA, true),
(constants::FRAME_MAX_STREAMS_BIDI, true),
(constants::FRAME_MAX_STREAMS_UNI, true),
(constants::FRAME_PATH_CHALLENGE, true),
(constants::FRAME_PATH_RESPONSE, true),
(constants::FRAME_CLOSE, false),
(constants::FRAME_DATAGRAM, true),
(constants::FRAME_DATAGRAM_LEN, true),
];
assert_eq!(
expected.len(),
21,
"§8.3's fourteen rows, less the reserved `0x05`, with the STREAM \
and DATAGRAM rows expanded to their eight and two codes"
);
for (ty, want) in expected {
assert_eq!(is_ack_eliciting(*ty), *want, "type {ty:#x}");
}
}
#[test]
fn retransmission_classes_match_the_whole_of_table_8_3() {
use Retransmission::*;
let expected: &[(u64, Option<Retransmission>)] = &[
(constants::FRAME_PADDING, Some(Never)),
(constants::FRAME_PING, Some(Never)),
(constants::FRAME_ACK, Some(Never)),
(constants::FRAME_RESET_STREAM, Some(Regenerate)),
(constants::FRAME_STOP_SENDING_RESERVED, None),
(0x08, Some(Ranges)),
(0x0f, Some(Ranges)),
(constants::FRAME_MAX_DATA, Some(Regenerate)),
(constants::FRAME_MAX_STREAM_DATA, Some(Regenerate)),
(constants::FRAME_MAX_STREAMS_BIDI, Some(Regenerate)),
(constants::FRAME_MAX_STREAMS_UNI, Some(Regenerate)),
(constants::FRAME_PATH_CHALLENGE, Some(Never)),
(constants::FRAME_PATH_RESPONSE, Some(Never)),
(constants::FRAME_CLOSE, Some(Never)),
(constants::FRAME_DATAGRAM, Some(Never)),
(constants::FRAME_DATAGRAM_LEN, Some(Never)),
(0x77, None),
];
for (ty, want) in expected {
assert_eq!(retransmission(*ty), *want, "type {ty:#x}");
}
}
#[test]
fn packet_ack_eliciting_is_an_any_over_frames() {
let quiet = vec![Frame::Close(Close::new(0, b"")), Frame::Padding];
assert!(!packet_is_ack_eliciting(&quiet));
let mut loud = quiet.clone();
loud.push(Frame::Ping);
assert!(packet_is_ack_eliciting(&loud));
}
#[test]
fn packing_emits_ack_then_control_then_ping() {
let mut packing = Packing::new();
assert!(packing.ack(Ack {
largest: 5,
ack_delay: 0,
first_range: 0,
ranges: Vec::new()
}));
assert!(packing.control(Frame::Close(Close::new(0, b""))));
assert!(packing.ping());
let types: Vec<u64> = packing.frames().iter().map(Frame::type_code).collect();
assert_eq!(
types,
vec![
constants::FRAME_ACK,
constants::FRAME_CLOSE,
constants::FRAME_PING
]
);
let plaintext = packing.into_plaintext();
let parsed = parse(&plaintext).unwrap();
assert_eq!(
parsed.iter().map(Frame::type_code).collect::<Vec<_>>(),
types
);
}
#[test]
fn packing_refuses_a_frame_that_would_overrun_max_plaintext() {
let mut packing = Packing::new();
let mut accepted = 0;
while packing.control(Frame::Close(Close::new(
1,
&[b'x'; constants::CLOSE_REASON_MAX],
))) {
accepted += 1;
assert!(accepted < 64, "the budget must bind");
}
assert!(accepted > 0);
assert!(packing.into_plaintext().len() <= constants::MAX_PLAINTEXT);
}
}