use crate::frame::FrameType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FrameError {
ZeroStreamIdNotAllowed {
frame_type: FrameType,
},
NonZeroStreamIdNotAllowed {
frame_type: FrameType,
stream_id: u32,
},
ZeroWindowIncrement,
WindowIncrementOutOfRange {
value: u32,
},
InvalidWeight {
value: u16,
},
PaddingExceedsPayload {
padding: u8,
payload_len: usize,
},
LastStreamIdOutOfRange {
value: u32,
},
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ZeroStreamIdNotAllowed { frame_type } => {
write!(f, "{frame_type} frame must not use stream ID 0")
}
Self::NonZeroStreamIdNotAllowed {
frame_type,
stream_id,
} => write!(
f,
"{frame_type} frame requires stream ID 0 but got {stream_id}"
),
Self::ZeroWindowIncrement => write!(f, "WINDOW_UPDATE increment must not be 0"),
Self::WindowIncrementOutOfRange { value } => write!(
f,
"WINDOW_UPDATE increment {value} exceeds maximum 2147483647"
),
Self::InvalidWeight { value } => {
write!(f, "PRIORITY weight {value} out of range 0..=255")
}
Self::PaddingExceedsPayload {
padding,
payload_len,
} => write!(
f,
"padding length {padding} exceeds payload length {payload_len}"
),
Self::LastStreamIdOutOfRange { value } => {
write!(
f,
"GOAWAY last_stream_id {value} exceeds maximum 2147483647"
)
}
}
}
}
impl std::error::Error for FrameError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WindowIncrement(core::num::NonZeroU32);
impl WindowIncrement {
pub const MAX: u32 = (1u32 << 31) - 1;
pub fn new(increment: u32) -> Result<Self, FrameError> {
if increment == 0 {
return Err(FrameError::ZeroWindowIncrement);
}
if increment > Self::MAX {
return Err(FrameError::WindowIncrementOutOfRange { value: increment });
}
Ok(Self(
core::num::NonZeroU32::new(increment).expect("non-zero checked above"),
))
}
pub const fn from_static(increment: u32) -> Self {
assert!(
increment <= Self::MAX,
"WindowIncrement::from_static: increment must be <= 2^31-1 (RFC 9113 §6.9)"
);
match core::num::NonZeroU32::new(increment) {
Some(v) => Self(v),
None => panic!("WindowIncrement::from_static: increment must not be 0 (RFC 9113 §6.9)"),
}
}
pub(crate) fn from_validated_parts(increment: core::num::NonZeroU32) -> Self {
debug_assert!(
increment.get() <= Self::MAX,
"WindowIncrement::from_validated_parts: increment must be <= 2^31-1"
);
Self(increment)
}
pub const fn get(self) -> core::num::NonZeroU32 {
self.0
}
pub const fn as_u32(self) -> u32 {
self.0.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Weight(u8);
impl Weight {
pub fn new(wire_value: u16) -> Result<Self, FrameError> {
if wire_value > 255 {
return Err(FrameError::InvalidWeight { value: wire_value });
}
Ok(Self(wire_value as u8))
}
pub const fn from_static(wire_value: u16) -> Self {
assert!(
wire_value <= 255,
"Weight::from_static: wire value must be 0..=255 (RFC 9113 §6.3)"
);
Self(wire_value as u8)
}
pub(crate) fn from_validated_parts(wire_value: u8) -> Self {
Self(wire_value)
}
pub const fn as_wire(self) -> u8 {
self.0
}
pub const fn weight_value(self) -> u16 {
self.0 as u16 + 1
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct LastStreamId(u32);
impl LastStreamId {
pub const MAX: u32 = (1u32 << 31) - 1;
pub fn new(id: u32) -> Result<Self, FrameError> {
if id > Self::MAX {
return Err(FrameError::LastStreamIdOutOfRange { value: id });
}
Ok(Self(id))
}
pub const fn from_static(id: u32) -> Self {
assert!(
id <= Self::MAX,
"LastStreamId::from_static: id must be <= 2^31-1 (RFC 9113 §6.8)"
);
Self(id)
}
pub(crate) fn from_validated_parts(id: u32) -> Self {
debug_assert!(
id <= Self::MAX,
"LastStreamId::from_validated_parts: id must be <= 2^31-1"
);
Self(id)
}
pub const fn get(self) -> u32 {
self.0
}
}
#[cfg(test)]
mod validated_parts {
use super::{LastStreamId, Weight, WindowIncrement};
#[test]
fn window_increment_validated_matches_new() -> noprop::TestResult {
let seed = noprop::seed_from_env_or_time("HTTP2_PBT_SEED")?;
let mut runner = noprop::Runner::new(seed);
runner.run(256, |ctx| {
let v = noprop::sample_with_boundaries(
ctx,
&[1u32, WindowIncrement::MAX],
noprop::Ratio::one_nth(5),
|ctx| noprop::sample_u64_in(ctx, 1..=WindowIncrement::MAX as u64) as u32,
);
let via_new = WindowIncrement::new(v).expect("valid window increment");
let nz = core::num::NonZeroU32::new(v).expect("non-zero increment");
let via_validated = WindowIncrement::from_validated_parts(nz);
assert_eq!(via_new, via_validated);
Ok(())
})?;
Ok(())
}
#[test]
fn weight_validated_matches_new() -> noprop::TestResult {
let seed = noprop::seed_from_env_or_time("HTTP2_PBT_SEED")?;
let mut runner = noprop::Runner::new(seed);
runner.run(256, |ctx| {
let w = noprop::sample_with_boundaries(
ctx,
&[0u16, 255],
noprop::Ratio::one_nth(5),
|ctx| noprop::sample_u64_in(ctx, 0..=255) as u16,
);
let via_new = Weight::new(w).expect("valid weight");
let via_validated = Weight::from_validated_parts(w as u8);
assert_eq!(via_new, via_validated);
Ok(())
})?;
Ok(())
}
#[test]
fn last_stream_id_validated_matches_new() -> noprop::TestResult {
let seed = noprop::seed_from_env_or_time("HTTP2_PBT_SEED")?;
let mut runner = noprop::Runner::new(seed);
runner.run(256, |ctx| {
let id = noprop::sample_with_boundaries(
ctx,
&[0u32, LastStreamId::MAX],
noprop::Ratio::one_nth(5),
|ctx| noprop::sample_u64_in(ctx, 0..=LastStreamId::MAX as u64) as u32,
);
let via_new = LastStreamId::new(id).expect("valid last stream id");
let via_validated = LastStreamId::from_validated_parts(id);
assert_eq!(via_new, via_validated);
Ok(())
})?;
Ok(())
}
}