mod frame;
mod params;
mod version;
use crate::StreamId;
#[cfg(test)]
mod wire_format_tests;
pub use frame::*;
pub(crate) use params::*;
pub use version::*;
pub use params::DEFAULT_MAX_RECORD_SIZE;
pub const MAX_FRAME_SIZE: usize = 16384;
pub const MAX_FRAME_PAYLOAD: usize = MAX_FRAME_SIZE - 32;
pub fn max_stream_payload(version: Version, budget: u64, id: StreamId, offset: u64) -> u64 {
let header = 1 + varint_size(id.into_inner());
if !version.is_qmux() {
return budget.saturating_sub(header);
}
max_length_prefixed_payload(budget.saturating_sub(header + varint_size(offset)))
}
fn max_length_prefixed_payload(available: u64) -> u64 {
const VARINT_MAX: [u64; 4] = [63, 16383, (1 << 30) - 1, (1 << 62) - 1];
let mut best = 0;
for max in VARINT_MAX {
let width = varint_size(max);
if available < width {
break;
}
let candidate = (available - width).min(max);
if varint_size(candidate) == width && candidate > best {
best = candidate;
}
}
best
}
pub(crate) const fn varint_size(v: u64) -> u64 {
if v < (1 << 6) {
1
} else if v < (1 << 14) {
2
} else if v < (1 << 30) {
4
} else {
8
}
}
#[cfg(test)]
mod max_stream_payload_tests {
use super::*;
use crate::{StreamDir, StreamId};
use web_transport_proto::VarInt;
fn assert_tight(budget: u64, id: StreamId, offset: u64) {
let payload = max_stream_payload(Version::QMux01, budget, id, offset);
let framed =
|n: u64| 1 + varint_size(id.into_inner()) + varint_size(offset) + varint_size(n) + n;
assert!(
framed(payload) <= budget,
"budget {budget}: payload {payload} encodes to {}",
framed(payload)
);
assert!(
framed(payload + 1) > budget,
"budget {budget}: payload {} also fits, so {payload} under-fills",
payload + 1
);
}
#[test]
fn fills_the_budget_at_varint_boundaries() {
let id = StreamId::new(0, StreamDir::Uni, true);
for budget in (60..=70).chain(16_380..=16_392) {
assert_tight(budget, id, 0);
}
assert_eq!(max_stream_payload(Version::QMux01, 16_387, id, 0), 16_382);
}
#[test]
fn accounts_for_wider_header_fields() {
let id = StreamId::new(0, StreamDir::Uni, true);
let wide = StreamId::new(1 << 20, StreamDir::Uni, true);
assert_tight(DEFAULT_MAX_RECORD_SIZE, wide, 1 << 20);
assert!(
max_stream_payload(Version::QMux01, DEFAULT_MAX_RECORD_SIZE, wide, 1 << 20)
< max_stream_payload(Version::QMux01, DEFAULT_MAX_RECORD_SIZE, id, 0)
);
}
#[test]
fn handles_the_largest_advertised_record_size() {
let id = StreamId::new(0, StreamDir::Uni, true);
assert_tight(VarInt::MAX.into_inner(), id, 0);
}
#[test]
fn legacy_binding_reserves_no_length() {
let id = StreamId::new(0, StreamDir::Uni, true);
assert_eq!(
max_stream_payload(Version::WebTransport, MAX_FRAME_SIZE as u64, id, 0),
MAX_FRAME_SIZE as u64 - 2
);
}
}