use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
use crate::error::{Error, Result};
use crate::header::HeaderExtension;
pub const ONE_BYTE_PROFILE_ID: u16 = 0xBEDE;
const TWO_BYTE_PROFILE_ID_MASK: u16 = 0xFFF0;
const TWO_BYTE_PROFILE_ID_PREFIX: u16 = 0x1000;
const ONE_BYTE_STOP_ID: u8 = 15;
const PADDING_BYTE: u8 = 0x00;
const ONE_BYTE_ID_MIN: u8 = 1;
const ONE_BYTE_ID_MAX: u8 = 14;
const ONE_BYTE_DATA_MIN: usize = 1;
const ONE_BYTE_DATA_MAX: usize = 16;
const TWO_BYTE_ID_MIN: u8 = 1;
const TWO_BYTE_DATA_MAX: usize = u8::MAX as usize;
const EXT_ALIGN: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct OneByteId(u8);
impl OneByteId {
pub fn new(id: u8) -> Result<Self> {
if (ONE_BYTE_ID_MIN..=ONE_BYTE_ID_MAX).contains(&id) {
Ok(Self(id))
} else {
Err(Error::InvalidOneByteExtensionId(id))
}
}
#[must_use]
pub fn get(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct TwoByteId(u8);
impl TwoByteId {
pub fn new(id: u8) -> Result<Self> {
if id >= TWO_BYTE_ID_MIN {
Ok(Self(id))
} else {
Err(Error::InvalidTwoByteExtensionId)
}
}
#[must_use]
pub fn get(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct OneByteElement<'a> {
pub id: OneByteId,
pub data: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct OneByteElements<'a>(pub Vec<OneByteElement<'a>>);
impl<'a> OneByteElements<'a> {
#[must_use]
pub fn elements(&self) -> &[OneByteElement<'a>] {
&self.0
}
}
impl<'a> IntoIterator for OneByteElements<'a> {
type Item = OneByteElement<'a>;
type IntoIter = alloc::vec::IntoIter<OneByteElement<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> Parse<'a> for OneByteElements<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let mut elements = Vec::new();
let mut pos = 0;
while pos < bytes.len() {
let b = bytes[pos];
if b == PADDING_BYTE {
pos += 1;
continue;
}
let id_nibble = b >> 4;
if id_nibble == ONE_BYTE_STOP_ID || id_nibble == 0 {
break;
}
let len = usize::from(b & 0x0F) + 1; let data_start = pos + 1;
let data_end = data_start + len;
if bytes.len() < data_end {
return Err(Error::BufferTooShort {
need: data_end,
have: bytes.len(),
what: "RFC 8285 one-byte extension element data",
});
}
let id = OneByteId(id_nibble);
elements.push(OneByteElement {
id,
data: &bytes[data_start..data_end],
});
pos = data_end;
}
Ok(Self(elements))
}
}
impl Serialize for OneByteElements<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
let raw: usize = self.0.iter().map(|e| 1 + e.data.len()).sum();
raw.div_ceil(EXT_ALIGN) * EXT_ALIGN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::BufferTooShort {
need: len,
have: buf.len(),
what: "RFC 8285 one-byte extension elements serialize output",
});
}
let mut pos = 0;
for e in &self.0 {
if !(ONE_BYTE_DATA_MIN..=ONE_BYTE_DATA_MAX).contains(&e.data.len()) {
return Err(Error::InvalidValue {
field: "OneByteElement::data.len()",
value: e.data.len() as u64,
reason: "must be 1..=16 bytes (RFC 8285 §4.2: len nibble = data.len() - 1)",
});
}
let len_nibble = (e.data.len() - 1) as u8;
buf[pos] = (e.id.get() << 4) | len_nibble;
pos += 1;
buf[pos..pos + e.data.len()].copy_from_slice(e.data);
pos += e.data.len();
}
for b in &mut buf[pos..len] {
*b = PADDING_BYTE;
}
Ok(len)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TwoByteElement<'a> {
pub id: TwoByteId,
pub data: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TwoByteElements<'a>(pub Vec<TwoByteElement<'a>>);
impl<'a> TwoByteElements<'a> {
#[must_use]
pub fn elements(&self) -> &[TwoByteElement<'a>] {
&self.0
}
}
impl<'a> IntoIterator for TwoByteElements<'a> {
type Item = TwoByteElement<'a>;
type IntoIter = alloc::vec::IntoIter<TwoByteElement<'a>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> Parse<'a> for TwoByteElements<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let mut elements = Vec::new();
let mut pos = 0;
while pos < bytes.len() {
let id_byte = bytes[pos];
if id_byte == PADDING_BYTE {
pos += 1;
continue;
}
let len_pos = pos + 1;
if bytes.len() <= len_pos {
return Err(Error::BufferTooShort {
need: len_pos + 1,
have: bytes.len(),
what: "RFC 8285 two-byte extension element length byte",
});
}
let len = usize::from(bytes[len_pos]);
let data_start = len_pos + 1;
let data_end = data_start + len;
if bytes.len() < data_end {
return Err(Error::BufferTooShort {
need: data_end,
have: bytes.len(),
what: "RFC 8285 two-byte extension element data",
});
}
let id = TwoByteId(id_byte);
elements.push(TwoByteElement {
id,
data: &bytes[data_start..data_end],
});
pos = data_end;
}
Ok(Self(elements))
}
}
impl Serialize for TwoByteElements<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
let raw: usize = self.0.iter().map(|e| 2 + e.data.len()).sum();
raw.div_ceil(EXT_ALIGN) * EXT_ALIGN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::BufferTooShort {
need: len,
have: buf.len(),
what: "RFC 8285 two-byte extension elements serialize output",
});
}
let mut pos = 0;
for e in &self.0 {
if e.data.len() > TWO_BYTE_DATA_MAX {
return Err(Error::InvalidValue {
field: "TwoByteElement::data.len()",
value: e.data.len() as u64,
reason: "exceeds the 8-bit length field maximum (255)",
});
}
buf[pos] = e.id.get();
buf[pos + 1] = e.data.len() as u8;
pos += 2;
buf[pos..pos + e.data.len()].copy_from_slice(e.data);
pos += e.data.len();
}
for b in &mut buf[pos..len] {
*b = PADDING_BYTE;
}
Ok(len)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ExtensionElements<'a> {
OneByte(OneByteElements<'a>),
TwoByte(TwoByteElements<'a>),
}
impl Serialize for ExtensionElements<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::OneByte(e) => e.serialized_len(),
Self::TwoByte(e) => e.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::OneByte(e) => e.serialize_into(buf),
Self::TwoByte(e) => e.serialize_into(buf),
}
}
}
pub fn parse_extensions<'a>(ext: &HeaderExtension<'a>) -> Result<ExtensionElements<'a>> {
if ext.profile_id == ONE_BYTE_PROFILE_ID {
Ok(ExtensionElements::OneByte(OneByteElements::parse(
ext.data,
)?))
} else if ext.profile_id & TWO_BYTE_PROFILE_ID_MASK == TWO_BYTE_PROFILE_ID_PREFIX {
Ok(ExtensionElements::TwoByte(TwoByteElements::parse(
ext.data,
)?))
} else {
Err(Error::NotRfc8285Extension {
profile_id: ext.profile_id,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn one_byte_id_rejects_padding_and_stop() {
assert!(matches!(
OneByteId::new(0),
Err(Error::InvalidOneByteExtensionId(0))
));
assert!(matches!(
OneByteId::new(15),
Err(Error::InvalidOneByteExtensionId(15))
));
assert!(OneByteId::new(1).is_ok());
assert!(OneByteId::new(14).is_ok());
}
#[test]
fn two_byte_id_rejects_zero() {
assert!(matches!(
TwoByteId::new(0),
Err(Error::InvalidTwoByteExtensionId)
));
assert!(TwoByteId::new(1).is_ok());
assert!(TwoByteId::new(255).is_ok());
}
#[test]
fn one_byte_round_trip_single_element() {
let elements = OneByteElements(vec![OneByteElement {
id: OneByteId::new(3).unwrap(),
data: &[0xAA, 0xBB],
}]);
let mut out = vec![0u8; elements.serialized_len()];
elements.serialize_into(&mut out).unwrap();
assert_eq!(out[0], (3 << 4) | 1);
assert_eq!(&out[1..3], &[0xAA, 0xBB]);
assert_eq!(out.len(), 4);
assert_eq!(out[3], 0x00);
let reparsed = OneByteElements::parse(&out).unwrap();
assert_eq!(reparsed, elements);
}
#[test]
fn one_byte_spec_worked_example_structure() {
let elements = OneByteElements(vec![
OneByteElement {
id: OneByteId::new(1).unwrap(),
data: &[0x11],
},
OneByteElement {
id: OneByteId::new(2).unwrap(),
data: &[0x22, 0x33],
},
OneByteElement {
id: OneByteId::new(3).unwrap(),
data: &[0x44, 0x55, 0x66, 0x77],
},
]);
let mut out = vec![0u8; elements.serialized_len()];
elements.serialize_into(&mut out).unwrap();
let expected = [
1 << 4, 0x11,
(2 << 4) | 1, 0x22,
0x33,
(3 << 4) | 3, 0x44,
0x55,
0x66,
0x77,
0x00,
0x00, ];
assert_eq!(out, expected);
assert_eq!(
out.len(),
12,
"3 words, matching length=3 in the RFC diagram"
);
let reparsed = OneByteElements::parse(&out).unwrap();
assert_eq!(reparsed, elements);
}
#[test]
fn one_byte_reparses_interspersed_padding_to_the_same_elements() {
#[rustfmt::skip]
let interspersed: [u8; 12] = [
1 << 4, 0x11,
(2 << 4) | 1, 0x22, 0x33,
0x00, 0x00, (3 << 4) | 3, 0x44, 0x55, 0x66, 0x77,
];
let parsed = OneByteElements::parse(&interspersed).unwrap();
let expected_elements = OneByteElements(vec![
OneByteElement {
id: OneByteId::new(1).unwrap(),
data: &[0x11],
},
OneByteElement {
id: OneByteId::new(2).unwrap(),
data: &[0x22, 0x33],
},
OneByteElement {
id: OneByteId::new(3).unwrap(),
data: &[0x44, 0x55, 0x66, 0x77],
},
]);
assert_eq!(parsed, expected_elements, "decoded elements are identical");
let mut out = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut out).unwrap();
assert_ne!(
out, interspersed,
"padding position is canonicalized, not preserved verbatim"
);
assert_eq!(OneByteElements::parse(&out).unwrap(), expected_elements);
}
#[test]
fn one_byte_stop_marker_halts_parsing() {
let bytes = [1 << 4, 0xAA, (15 << 4) | 5, 0xFF];
let parsed = OneByteElements::parse(&bytes).unwrap();
assert_eq!(parsed.elements().len(), 1);
assert_eq!(parsed.elements()[0].id.get(), 1);
assert_eq!(parsed.elements()[0].data, &[0xAA]);
}
#[test]
fn one_byte_malformed_id_zero_with_length_halts_parsing() {
let bytes = [1 << 4, 0xAA, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
let parsed = OneByteElements::parse(&bytes).unwrap();
assert_eq!(parsed.elements().len(), 1);
assert_eq!(parsed.elements()[0].data, &[0xAA]);
}
#[test]
fn one_byte_pure_padding_byte_is_skipped_not_terminal() {
let bytes = [
1 << 4,
0xAA,
0x00, 2 << 4,
0xBB,
];
let parsed = OneByteElements::parse(&bytes).unwrap();
assert_eq!(parsed.elements().len(), 2);
assert_eq!(parsed.elements()[1].id.get(), 2);
assert_eq!(parsed.elements()[1].data, &[0xBB]);
}
#[test]
fn one_byte_rejects_data_len_out_of_range() {
let elements = OneByteElements(vec![OneByteElement {
id: OneByteId::new(1).unwrap(),
data: &[],
}]);
let mut out = vec![0u8; 4];
assert!(matches!(
elements.serialize_into(&mut out),
Err(Error::InvalidValue {
field: "OneByteElement::data.len()",
..
})
));
}
#[test]
fn one_byte_truncated_element_data_is_buffer_too_short() {
let bytes = [(1 << 4) | 3, 0xAA, 0xBB];
assert!(matches!(
OneByteElements::parse(&bytes),
Err(Error::BufferTooShort { .. })
));
}
#[test]
fn one_byte_empty_is_valid() {
let elements = OneByteElements::default();
assert_eq!(elements.serialized_len(), 0);
let mut out: [u8; 0] = [];
elements.serialize_into(&mut out).unwrap();
let reparsed = OneByteElements::parse(&[]).unwrap();
assert_eq!(reparsed, elements);
}
#[test]
fn two_byte_round_trip_single_element() {
let elements = TwoByteElements(vec![TwoByteElement {
id: TwoByteId::new(200).unwrap(),
data: &[0x01, 0x02, 0x03],
}]);
let mut out = vec![0u8; elements.serialized_len()];
elements.serialize_into(&mut out).unwrap();
assert_eq!(out[0], 200);
assert_eq!(out[1], 3);
assert_eq!(&out[2..5], &[0x01, 0x02, 0x03]);
assert_eq!(out.len(), 8, "5 bytes of content padded to 8");
let reparsed = TwoByteElements::parse(&out).unwrap();
assert_eq!(reparsed, elements);
}
#[test]
fn two_byte_spec_worked_example_structure() {
let elements = TwoByteElements(vec![
TwoByteElement {
id: TwoByteId::new(10).unwrap(),
data: &[],
},
TwoByteElement {
id: TwoByteId::new(20).unwrap(),
data: &[0x99],
},
TwoByteElement {
id: TwoByteId::new(30).unwrap(),
data: &[0x01, 0x02, 0x03, 0x04],
},
]);
let mut out = vec![0u8; elements.serialized_len()];
elements.serialize_into(&mut out).unwrap();
let expected = [
10, 0, 20, 1, 0x99, 30, 4, 0x01, 0x02, 0x03, 0x04, 0x00, ];
assert_eq!(out, expected);
assert_eq!(
out.len(),
12,
"3 words, matching length=3 in the RFC diagram"
);
let reparsed = TwoByteElements::parse(&out).unwrap();
assert_eq!(reparsed, elements);
}
#[test]
fn two_byte_reparses_interspersed_padding_to_the_same_elements() {
#[rustfmt::skip]
let interspersed: [u8; 12] = [
10, 0,
20, 1, 0x99,
0x00, 30, 4, 0x01, 0x02, 0x03, 0x04,
];
let parsed = TwoByteElements::parse(&interspersed).unwrap();
let expected_elements = TwoByteElements(vec![
TwoByteElement {
id: TwoByteId::new(10).unwrap(),
data: &[],
},
TwoByteElement {
id: TwoByteId::new(20).unwrap(),
data: &[0x99],
},
TwoByteElement {
id: TwoByteId::new(30).unwrap(),
data: &[0x01, 0x02, 0x03, 0x04],
},
]);
assert_eq!(parsed, expected_elements, "decoded elements are identical");
let mut out = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut out).unwrap();
assert_ne!(
out, interspersed,
"padding position is canonicalized, not preserved verbatim"
);
assert_eq!(TwoByteElements::parse(&out).unwrap(), expected_elements);
}
#[test]
fn two_byte_zero_id_byte_is_padding_not_an_element() {
let bytes = [10u8, 0, 0x00, 20, 1, 0x77];
let parsed = TwoByteElements::parse(&bytes).unwrap();
assert_eq!(parsed.elements().len(), 2);
assert_eq!(parsed.elements()[0].id.get(), 10);
assert_eq!(parsed.elements()[0].data, &[] as &[u8]);
assert_eq!(parsed.elements()[1].id.get(), 20);
assert_eq!(parsed.elements()[1].data, &[0x77]);
}
#[test]
fn two_byte_truncated_length_byte_is_buffer_too_short() {
let bytes = [10u8]; assert!(matches!(
TwoByteElements::parse(&bytes),
Err(Error::BufferTooShort { .. })
));
}
#[test]
fn two_byte_truncated_element_data_is_buffer_too_short() {
let bytes = [10u8, 4, 0x01, 0x02]; assert!(matches!(
TwoByteElements::parse(&bytes),
Err(Error::BufferTooShort { .. })
));
}
#[test]
fn two_byte_empty_is_valid() {
let elements = TwoByteElements::default();
assert_eq!(elements.serialized_len(), 0);
let reparsed = TwoByteElements::parse(&[]).unwrap();
assert_eq!(reparsed, elements);
}
#[test]
fn parse_extensions_dispatches_one_byte() {
let data = [1 << 4, 0xAA, 0x00, 0x00];
let ext = HeaderExtension {
profile_id: ONE_BYTE_PROFILE_ID,
data: &data,
};
let parsed = parse_extensions(&ext).unwrap();
assert!(matches!(parsed, ExtensionElements::OneByte(_)));
}
#[test]
fn parse_extensions_dispatches_two_byte() {
let data = [10u8, 1, 0xAA, 0x00];
let ext = HeaderExtension {
profile_id: 0x1005, data: &data,
};
let parsed = parse_extensions(&ext).unwrap();
assert!(matches!(parsed, ExtensionElements::TwoByte(_)));
}
#[test]
fn parse_extensions_rejects_unknown_profile() {
let data: [u8; 0] = [];
let ext = HeaderExtension {
profile_id: 0x1234,
data: &data,
};
assert!(matches!(
parse_extensions(&ext),
Err(Error::NotRfc8285Extension { profile_id: 0x1234 })
));
}
#[test]
fn extension_elements_serialize_round_trip_via_dispatch() {
let data = [(5 << 4) | 1, 0x01, 0x02, 0x00];
let ext = HeaderExtension {
profile_id: ONE_BYTE_PROFILE_ID,
data: &data,
};
let elements = parse_extensions(&ext).unwrap();
let mut out = vec![0u8; elements.serialized_len()];
elements.serialize_into(&mut out).unwrap();
assert_eq!(out, data);
}
}