use crate::error::{Error, Result};
use crate::scr::{self, Scr};
use broadcast_common::len::{need_in, need_out};
use broadcast_common::{Parse, Serialize};
pub const PACK_START_CODE: u32 = 0x0000_01BA;
const FIXED_LEN: usize = 14;
const PROGRAM_MUX_RATE_BITS: u32 = 22;
const RESERVED_BITS: u32 = 5;
const STUFFING_LENGTH_MAX: u8 = 7;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PackHeader<'a> {
pub scr: Scr,
pub program_mux_rate: u32,
pub stuffing_length: u8,
#[cfg_attr(feature = "serde", serde(skip))]
pub stuffing: &'a [u8],
pub reserved: u8,
}
impl PackHeader<'_> {
#[must_use]
pub fn header_len(&self) -> usize {
self.serialized_len()
}
}
impl<'a> Parse<'a> for PackHeader<'a> {
type Error = Error;
fn parse(b: &'a [u8]) -> Result<Self> {
need_in(b, FIXED_LEN, "pack_header")?;
if u32::from_be_bytes([b[0], b[1], b[2], b[3]]) != PACK_START_CODE {
return Err(Error::BadPackStartCode(u32::from_be_bytes([
b[0], b[1], b[2], b[3],
])));
}
let scr = scr::read_scr_field(&b[4..10], "SCR")?;
if b[12] & 0x03 != 0x03 {
return Err(Error::BadMarker("program_mux_rate markers"));
}
let program_mux_rate =
(u32::from(b[10]) << 14) | (u32::from(b[11]) << 6) | (u32::from(b[12]) >> 2);
if program_mux_rate == 0 {
return Err(Error::ZeroMuxRate);
}
let reserved = (b[13] >> 3) & 0x1F;
let stuffing_length = b[13] & 0x07;
let total = FIXED_LEN + stuffing_length as usize;
need_in(b, total, "pack_header stuffing bytes")?;
Ok(PackHeader {
scr,
program_mux_rate,
stuffing_length,
stuffing: &b[FIXED_LEN..total],
reserved,
})
}
}
impl Serialize for PackHeader<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
FIXED_LEN + self.stuffing_length as usize
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
if self.stuffing_length > STUFFING_LENGTH_MAX {
return Err(Error::StuffingLengthTooLarge(self.stuffing_length));
}
let mux = broadcast_common::len::fit_bits(
u64::from(self.program_mux_rate),
PROGRAM_MUX_RATE_BITS,
"pack_header.program_mux_rate",
)? as u32;
let reserved = broadcast_common::len::fit_bits(
u64::from(self.reserved),
RESERVED_BITS,
"pack_header.reserved",
)? as u8;
let len = self.serialized_len();
need_out(buf, len)?;
buf[0..4].copy_from_slice(&PACK_START_CODE.to_be_bytes());
buf[4..10].copy_from_slice(&scr::encode_scr_field(self.scr)?);
buf[10] = (mux >> 14) as u8;
buf[11] = (mux >> 6) as u8;
buf[12] = (((mux & 0x3F) as u8) << 2) | 0x03;
buf[13] = reserved << 3 | self.stuffing_length;
if self.stuffing.len() != self.stuffing_length as usize {
return Err(Error::StuffingLengthMismatch {
declared: self.stuffing_length,
actual: self.stuffing.len(),
});
}
buf[FIXED_LEN..len].copy_from_slice(self.stuffing);
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn pack_header_round_trip_fixture_pattern() {
let bytes = vec![
0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x43, 0x36, 0x3B, 0xF8, ];
let h = PackHeader::parse(&bytes).unwrap();
assert_eq!(
h.scr,
Scr {
base: 0,
extension: 0,
}
);
assert_eq!(h.program_mux_rate, 0x10CD8E);
assert_eq!(h.stuffing_length, 0);
assert_eq!(h.reserved, 0x1F);
assert!(h.stuffing.is_empty());
let mut out = vec![0u8; h.serialized_len()];
h.serialize_into(&mut out).unwrap();
assert_eq!(&out[..], &bytes[..], "round-trip mismatch");
let h_mut = PackHeader {
program_mux_rate: 0x12345,
..h.clone()
};
let mut out2 = vec![0u8; h_mut.serialized_len()];
h_mut.serialize_into(&mut out2).unwrap();
assert_ne!(&out[..], &out2[..]);
}
#[test]
fn serialize_rejects_out_of_range_fields() {
let ok = PackHeader {
scr: Scr {
base: 0,
extension: 0,
},
program_mux_rate: 1,
stuffing_length: 0,
stuffing: &[],
reserved: 0x1F,
};
let mut out = vec![0u8; 64];
assert!(ok.serialize_into(&mut out).is_ok());
let long = [0xFFu8; 9];
let bad = PackHeader {
stuffing_length: 9,
stuffing: &long,
..ok.clone()
};
assert_eq!(
bad.serialize_into(&mut out),
Err(Error::StuffingLengthTooLarge(9))
);
let bad = PackHeader {
program_mux_rate: 0x40_0000,
..ok.clone()
};
assert!(matches!(
bad.serialize_into(&mut out),
Err(Error::FieldOverflow(_))
));
let bad = PackHeader {
reserved: 0x20,
..ok.clone()
};
assert!(matches!(
bad.serialize_into(&mut out),
Err(Error::FieldOverflow(_))
));
}
#[test]
fn pack_header_round_trip_with_stuffing() {
let bytes = vec![
0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x00, 0x01,
0x0F, 0x03, 0xFF, 0xFF, 0xFF,
];
let h = PackHeader::parse(&bytes).unwrap();
assert_eq!(h.program_mux_rate, 0x43);
assert_eq!(h.stuffing_length, 3);
assert_eq!(h.stuffing, &[0xFF, 0xFF, 0xFF]);
assert_eq!(h.reserved, 0);
let mut out = vec![0u8; h.serialized_len()];
h.serialize_into(&mut out).unwrap();
assert_eq!(&out[..], &bytes[..]);
let h2 = PackHeader::parse(&out).unwrap();
assert_eq!(h, h2);
let h_mut = PackHeader {
reserved: 0x0A,
..h.clone()
};
let mut out2 = vec![0u8; h_mut.serialized_len()];
h_mut.serialize_into(&mut out2).unwrap();
assert_ne!(&out[..], &out2[..]);
}
#[test]
fn pack_header_nonzero_scr() {
let scr = Scr {
base: 0x12345678,
extension: 0x0AA,
};
let scr_enc = scr::write_scr_field(scr);
let mut b = vec![0u8; 14];
b[0..4].copy_from_slice(&PACK_START_CODE.to_be_bytes());
b[4..10].copy_from_slice(&scr_enc);
b[10..14].copy_from_slice(&[0x00, 0x01, 0x0F, 0x00]);
let h = PackHeader::parse(&b).unwrap();
assert_eq!(h.scr, scr);
assert_eq!(h.program_mux_rate, 0x43);
let mut out = vec![0u8; h.serialized_len()];
h.serialize_into(&mut out).unwrap();
assert_eq!(&out[..], &b[..]);
}
#[test]
fn non_0xff_stuffing_round_trips_byte_identically() {
let bytes = vec![
0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x00, 0x01, 0x0F,
0x03, 0xAB, 0xCD, 0xEF, ];
let h = PackHeader::parse(&bytes).unwrap();
assert_eq!(h.stuffing, &[0xAB, 0xCD, 0xEF]);
let mut out = vec![0u8; h.serialized_len()];
h.serialize_into(&mut out).unwrap();
assert_eq!(&out[..], &bytes[..]);
}
#[test]
fn serialize_rejects_stuffing_length_mismatch() {
let bytes = vec![
0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x00, 0x01, 0x0F, 0x03,
0xFF, 0xFF, 0xFF,
];
let mut h = PackHeader::parse(&bytes).unwrap();
h.stuffing = &[0xFF, 0xFF];
let mut out = vec![0u8; h.serialized_len()];
let err = h.serialize_into(&mut out).unwrap_err();
assert!(matches!(
err,
Error::StuffingLengthMismatch {
declared: 3,
actual: 2
}
));
}
}