use crate::error::{Error, Result};
use broadcast_common::len::{need_in, need_out};
pub const SIZE_INDICATOR_MASK: u8 = 0x80;
pub const MAX_LENGTH_OCTETS: u8 = 4;
pub const MAX_LENGTH: usize = 0xFFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum LengthForm {
#[default]
Minimal,
Long {
octets: u8,
},
}
impl LengthForm {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Minimal => "minimal",
Self::Long { .. } => "long",
}
}
}
broadcast_common::impl_spec_display!(LengthForm);
pub fn decode(bytes: &[u8]) -> Result<(usize, usize)> {
let (value, hdr, _) = decode_form(bytes)?;
Ok((value, hdr))
}
pub fn decode_form(bytes: &[u8]) -> Result<(usize, usize, LengthForm)> {
let first = *bytes
.first()
.ok_or(Error::InvalidLength("empty length_field"))?;
if first & SIZE_INDICATOR_MASK == 0 {
return Ok(((first & 0x7F) as usize, 1, LengthForm::Minimal));
}
let n = (first & 0x7F) as usize;
if n == 0 {
return Err(Error::InvalidLength("indefinite length form not allowed"));
}
if n > MAX_LENGTH_OCTETS as usize {
return Err(Error::InvalidLength(
"length_field_size exceeds 4 length octets",
));
}
need_in(bytes, 1 + n, "length_field (long form)")?;
let mut value = 0usize;
for &b in &bytes[1..1 + n] {
value = (value << 8) | b as usize;
}
if value > MAX_LENGTH {
return Err(Error::LengthTooLarge(value));
}
let form = if n == minimal_octets(value) && value >= 0x80 {
LengthForm::Minimal
} else {
LengthForm::Long { octets: n as u8 }
};
Ok((value, 1 + n, form))
}
const fn minimal_octets(value: usize) -> usize {
if value <= 0xFF { 1 } else { 2 }
}
#[must_use]
pub fn encoded_len(value: usize) -> usize {
if value < 0x80 {
1
} else if value <= 0xFF {
2
} else if value <= 0xFFFF {
3
} else {
4
}
}
#[must_use]
pub fn encoded_len_with(value: usize, form: LengthForm) -> usize {
match form {
LengthForm::Minimal => encoded_len(value),
LengthForm::Long { octets } => 1 + octets as usize,
}
}
pub fn encode_with(value: usize, form: LengthForm, buf: &mut [u8]) -> Result<usize> {
let LengthForm::Long { octets } = form else {
return encode_into(value, buf);
};
if value > MAX_LENGTH {
return Err(Error::LengthTooLarge(value));
}
if octets == 0 || octets > MAX_LENGTH_OCTETS {
return Err(Error::InvalidLength("length_field_size must be 1..=4"));
}
let n = octets as usize;
if n < 4 && value >> (8 * n) != 0 {
return Err(Error::InvalidLength(
"length value does not fit the recorded length_field_size",
));
}
if value >= 0x80 && n == minimal_octets(value) {
return Err(Error::InvalidLength(
"Long form with the minimal number of octets is LengthForm::Minimal",
));
}
let need = 1 + n;
need_out(buf, need)?;
buf[0] = SIZE_INDICATOR_MASK | octets;
for (i, slot) in buf[1..need].iter_mut().enumerate() {
let shift = 8 * (n - 1 - i);
*slot = (value >> shift) as u8;
}
Ok(need)
}
pub fn encode_into(value: usize, buf: &mut [u8]) -> Result<usize> {
let need = encoded_len(value);
if value > 0xFFFF {
return Err(Error::LengthTooLarge(value));
}
need_out(buf, need)?;
if value < 0x80 {
buf[0] = value as u8;
} else if value <= 0xFF {
buf[0] = SIZE_INDICATOR_MASK | 1;
buf[1] = value as u8;
} else {
buf[0] = SIZE_INDICATOR_MASK | 2;
buf[1] = (value >> 8) as u8;
buf[2] = value as u8;
}
Ok(need)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn short_form_round_trip() {
for v in [0usize, 1, 0x7F] {
let mut buf = [0u8; 4];
let n = encode_into(v, &mut buf).unwrap();
assert_eq!(n, 1);
assert_eq!(encoded_len(v), 1);
let (decoded, hdr) = decode(&buf).unwrap();
assert_eq!((decoded, hdr), (v, 1));
}
}
#[test]
fn two_byte_form_round_trip() {
for v in [0x80usize, 0xFF] {
let mut buf = [0u8; 4];
let n = encode_into(v, &mut buf).unwrap();
assert_eq!(n, 2);
assert_eq!(buf[0], SIZE_INDICATOR_MASK | 1);
let (decoded, hdr) = decode(&buf).unwrap();
assert_eq!((decoded, hdr), (v, 2));
}
}
#[test]
fn three_byte_form_round_trip() {
for v in [0x100usize, 0x1234, 0xFFFF] {
let mut buf = [0u8; 4];
let n = encode_into(v, &mut buf).unwrap();
assert_eq!(n, 3);
assert_eq!(buf[0], SIZE_INDICATOR_MASK | 2);
let (decoded, hdr) = decode(&buf).unwrap();
assert_eq!((decoded, hdr), (v, 3));
}
}
#[test]
fn rejects_oversize() {
let mut buf = [0u8; 4];
assert!(matches!(
encode_into(0x1_0000, &mut buf),
Err(Error::LengthTooLarge(0x1_0000))
));
}
#[test]
fn rejects_indefinite_and_wide() {
assert_eq!(
decode(&[0x80]),
Err(Error::InvalidLength("indefinite length form not allowed"))
);
assert_eq!(
decode(&[0x85, 0, 0, 0, 0, 0]),
Err(Error::InvalidLength(
"length_field_size exceeds 4 length octets"
))
);
assert_eq!(decode(&[]), Err(Error::InvalidLength("empty length_field")));
assert_eq!(
decode(&[0x82, 0x12]),
Err(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: 3,
have: 2,
what: "length_field (long form)"
}
))
);
}
#[test]
fn every_form_round_trips_byte_exactly() {
let cases: &[(&[u8], usize, LengthForm)] = &[
(&[0x05], 5, LengthForm::Minimal),
(&[0x7F], 127, LengthForm::Minimal),
(&[0x81, 0x80], 0x80, LengthForm::Minimal),
(&[0x81, 0xFF], 0xFF, LengthForm::Minimal),
(&[0x82, 0x01, 0x00], 0x100, LengthForm::Minimal),
(&[0x82, 0xFF, 0xFF], 0xFFFF, LengthForm::Minimal),
(&[0x81, 0x05], 5, LengthForm::Long { octets: 1 }),
(&[0x81, 0x00], 0, LengthForm::Long { octets: 1 }),
(&[0x82, 0x00, 0x09], 9, LengthForm::Long { octets: 2 }),
(&[0x82, 0x00, 0x80], 0x80, LengthForm::Long { octets: 2 }),
(
&[0x83, 0x00, 0x01, 0x00],
0x100,
LengthForm::Long { octets: 3 },
),
(&[0x83, 0x00, 0x00, 0x09], 9, LengthForm::Long { octets: 3 }),
(
&[0x84, 0x00, 0x00, 0x00, 0x09],
9,
LengthForm::Long { octets: 4 },
),
];
for &(wire, value, form) in cases {
let (v, hdr, f) = decode_form(wire).unwrap();
assert_eq!((v, hdr, f), (value, wire.len(), form), "{wire:02X?}");
let mut buf = [0u8; 8];
let n = encode_with(value, form, &mut buf).unwrap();
assert_eq!(&buf[..n], wire, "re-encode {wire:02X?}");
assert_eq!(encoded_len_with(value, form), wire.len());
}
}
#[test]
fn minimal_form_matches_encode_into() {
for v in [0usize, 1, 0x7F, 0x80, 0xFF, 0x100, 0x1234, 0xFFFF] {
let (mut a, mut b) = ([0u8; 4], [0u8; 4]);
let na = encode_into(v, &mut a).unwrap();
let nb = encode_with(v, LengthForm::default(), &mut b).unwrap();
assert_eq!(a[..na], b[..nb]);
}
}
#[test]
fn encode_with_rejects_a_form_that_cannot_hold_the_value() {
let mut buf = [0u8; 8];
assert_eq!(
encode_with(0x100, LengthForm::Long { octets: 1 }, &mut buf),
Err(Error::InvalidLength(
"length value does not fit the recorded length_field_size"
))
);
assert_eq!(
encode_with(0x80, LengthForm::Long { octets: 1 }, &mut buf),
Err(Error::InvalidLength(
"Long form with the minimal number of octets is LengthForm::Minimal"
))
);
assert_eq!(
encode_with(0xFF, LengthForm::Long { octets: 1 }, &mut buf),
Err(Error::InvalidLength(
"Long form with the minimal number of octets is LengthForm::Minimal"
))
);
assert_eq!(
encode_with(0x100, LengthForm::Long { octets: 2 }, &mut buf),
Err(Error::InvalidLength(
"Long form with the minimal number of octets is LengthForm::Minimal"
))
);
assert_eq!(
encode_with(0xFFFF, LengthForm::Long { octets: 2 }, &mut buf),
Err(Error::InvalidLength(
"Long form with the minimal number of octets is LengthForm::Minimal"
))
);
assert!(encode_with(0x7F, LengthForm::Long { octets: 1 }, &mut buf).is_ok());
assert!(encode_with(0x80, LengthForm::Long { octets: 2 }, &mut buf).is_ok());
assert!(encode_with(0x100, LengthForm::Long { octets: 3 }, &mut buf).is_ok());
assert_eq!(
encode_with(5, LengthForm::Long { octets: 0 }, &mut buf),
Err(Error::InvalidLength("length_field_size must be 1..=4"))
);
assert_eq!(
encode_with(5, LengthForm::Long { octets: 5 }, &mut buf),
Err(Error::InvalidLength("length_field_size must be 1..=4"))
);
assert_eq!(
encode_with(0x1_0000, LengthForm::Long { octets: 4 }, &mut buf),
Err(Error::LengthTooLarge(0x1_0000))
);
}
#[test]
fn rejects_a_value_above_the_spec_cap() {
assert!(matches!(
decode_form(&[0x83, 0x01, 0x00, 0x00]),
Err(Error::LengthTooLarge(0x1_0000))
));
}
#[test]
fn mutating_a_byte_changes_decode() {
let mut buf = [0u8; 4];
encode_into(0x1234, &mut buf).unwrap();
let (a, _) = decode(&buf).unwrap();
buf[2] ^= 0xFF;
let (b, _) = decode(&buf).unwrap();
assert_ne!(a, b);
}
}