use broadcast_common::{Parse, Serialize};
use crate::error::{Error, Result};
pub const SYNC_WORD_PA: u16 = 0xF872;
pub const SYNC_WORD_PB: u16 = 0x4E1F;
pub const EXTENDED_DATA_TYPE_MARKER: u8 = 31;
pub const MAX_LENGTH_CODE_BITS: u32 = 0xFFFF;
pub const MAX_5_BIT_FIELD: u8 = 0x1F;
pub const MAX_DATA_STREAM_NUMBER: u8 = 0x07;
const PREAMBLE_WORD_LEN: usize = 2;
const FOUR_WORD_PREAMBLE_LEN: usize = 4 * PREAMBLE_WORD_LEN;
const SIX_WORD_PREAMBLE_LEN: usize = 6 * PREAMBLE_WORD_LEN;
const EXTENDED_PREAMBLE_LENGTH_CODE_BITS: u32 =
(SIX_WORD_PREAMBLE_LEN - FOUR_WORD_PREAMBLE_LEN) as u32 * 8;
const PC_DATA_TYPE_MASK: u16 = 0x001F;
const PC_DATA_MODE_SHIFT: u32 = 5;
const PC_DATA_MODE_MASK: u16 = 0x0003;
const PC_ERROR_FLAG_BIT: u16 = 0x0080;
const PC_DATA_TYPE_DEPENDENT_SHIFT: u32 = 8;
const PC_DATA_TYPE_DEPENDENT_MASK: u16 = 0x001F;
const PC_DATA_STREAM_NUMBER_SHIFT: u32 = 13;
const PC_DATA_STREAM_NUMBER_MASK: u16 = 0x0007;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum DataMode {
Mode16,
Mode20,
Mode24,
Reserved,
}
impl DataMode {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Mode16 => "16-bit mode",
Self::Mode20 => "20-bit mode",
Self::Mode24 => "24-bit mode",
Self::Reserved => "reserved",
}
}
fn from_bits(bits: u16) -> Self {
match bits {
0 => Self::Mode16,
1 => Self::Mode20,
2 => Self::Mode24,
_ => Self::Reserved,
}
}
fn to_bits(self) -> u16 {
match self {
Self::Mode16 => 0,
Self::Mode20 => 1,
Self::Mode24 => 2,
Self::Reserved => 3,
}
}
}
broadcast_common::impl_spec_display!(DataMode);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExtendedPreamble {
pub extended_data_type: u16,
pub reserved_pf: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BurstPreamble {
pub data_type: u8,
pub data_mode: DataMode,
pub error_flag: bool,
pub data_type_dependent: u8,
pub data_stream_number: u8,
pub length_code: u16,
pub extended: Option<ExtendedPreamble>,
}
impl BurstPreamble {
fn pack_pc(&self) -> u16 {
(u16::from(self.data_type) & PC_DATA_TYPE_MASK)
| ((self.data_mode.to_bits() & PC_DATA_MODE_MASK) << PC_DATA_MODE_SHIFT)
| (if self.error_flag {
PC_ERROR_FLAG_BIT
} else {
0
})
| ((u16::from(self.data_type_dependent) & PC_DATA_TYPE_DEPENDENT_MASK)
<< PC_DATA_TYPE_DEPENDENT_SHIFT)
| ((u16::from(self.data_stream_number) & PC_DATA_STREAM_NUMBER_MASK)
<< PC_DATA_STREAM_NUMBER_SHIFT)
}
fn unpack_pc(pc: u16) -> (u8, DataMode, bool, u8, u8) {
let data_type = (pc & PC_DATA_TYPE_MASK) as u8;
let data_mode = DataMode::from_bits((pc >> PC_DATA_MODE_SHIFT) & PC_DATA_MODE_MASK);
let error_flag = pc & PC_ERROR_FLAG_BIT != 0;
let data_type_dependent =
((pc >> PC_DATA_TYPE_DEPENDENT_SHIFT) & PC_DATA_TYPE_DEPENDENT_MASK) as u8;
let data_stream_number =
((pc >> PC_DATA_STREAM_NUMBER_SHIFT) & PC_DATA_STREAM_NUMBER_MASK) as u8;
(
data_type,
data_mode,
error_flag,
data_type_dependent,
data_stream_number,
)
}
#[must_use]
pub fn wire_len(&self) -> usize {
if self.extended.is_some() {
SIX_WORD_PREAMBLE_LEN
} else {
FOUR_WORD_PREAMBLE_LEN
}
}
fn validate(&self) -> Result<()> {
if self.data_mode != DataMode::Mode16 {
return Err(Error::UnsupportedDataMode(self.data_mode));
}
if self.data_type > MAX_5_BIT_FIELD {
return Err(Error::InvalidValue {
field: "data_type",
value: u64::from(self.data_type),
reason: "must be a 5-bit value (0..=31)",
});
}
if self.data_type_dependent > MAX_5_BIT_FIELD {
return Err(Error::InvalidValue {
field: "data_type_dependent",
value: u64::from(self.data_type_dependent),
reason: "must be a 5-bit value (0..=31)",
});
}
if self.data_stream_number > MAX_DATA_STREAM_NUMBER {
return Err(Error::InvalidValue {
field: "data_stream_number",
value: u64::from(self.data_stream_number),
reason: "must be a 3-bit value (0..=7)",
});
}
let requires_extended = self.data_type == EXTENDED_DATA_TYPE_MARKER;
if requires_extended != self.extended.is_some() {
return Err(Error::ExtendedPreambleMismatch {
data_type: self.data_type,
expected_extended: requires_extended,
found_extended: self.extended.is_some(),
});
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Burst<'a> {
pub preamble: BurstPreamble,
pub payload: &'a [u8],
}
impl<'a> Burst<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
data_type: u8,
data_mode: DataMode,
error_flag: bool,
data_type_dependent: u8,
data_stream_number: u8,
extended: Option<ExtendedPreamble>,
payload: &'a [u8],
) -> Result<Self> {
let extended_offset_bits = if extended.is_some() {
EXTENDED_PREAMBLE_LENGTH_CODE_BITS
} else {
0
};
let payload_bits = (payload.len() as u64) * 8 + u64::from(extended_offset_bits);
if payload_bits > u64::from(MAX_LENGTH_CODE_BITS) {
return Err(Error::PayloadTooLarge {
payload_bits: payload_bits as u32,
extended_offset_bits,
});
}
#[allow(clippy::cast_possible_truncation)]
let length_code = payload_bits as u16;
let preamble = BurstPreamble {
data_type,
data_mode,
error_flag,
data_type_dependent,
data_stream_number,
length_code,
extended,
};
preamble.validate()?;
Ok(Self { preamble, payload })
}
}
impl<'a> Parse<'a> for Burst<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
if bytes.len() < FOUR_WORD_PREAMBLE_LEN {
return Err(Error::BufferTooShort {
need: FOUR_WORD_PREAMBLE_LEN,
have: bytes.len(),
what: "burst_preamble (Pa-Pd)",
});
}
let pa = u16::from_le_bytes([bytes[0], bytes[1]]);
if pa != SYNC_WORD_PA {
return Err(Error::InvalidSync {
word: "Pa",
expected: SYNC_WORD_PA,
found: pa,
});
}
let pb = u16::from_le_bytes([bytes[2], bytes[3]]);
if pb != SYNC_WORD_PB {
return Err(Error::InvalidSync {
word: "Pb",
expected: SYNC_WORD_PB,
found: pb,
});
}
let pc = u16::from_le_bytes([bytes[4], bytes[5]]);
let (data_type, data_mode, error_flag, data_type_dependent, data_stream_number) =
BurstPreamble::unpack_pc(pc);
if data_mode != DataMode::Mode16 {
return Err(Error::UnsupportedDataMode(data_mode));
}
let length_code = u16::from_le_bytes([bytes[6], bytes[7]]);
let is_extended = data_type == EXTENDED_DATA_TYPE_MARKER;
let (extended, preamble_len) = if is_extended {
if bytes.len() < SIX_WORD_PREAMBLE_LEN {
return Err(Error::BufferTooShort {
need: SIX_WORD_PREAMBLE_LEN,
have: bytes.len(),
what: "burst_preamble (Pa-Pf, six-word form)",
});
}
let pe = u16::from_le_bytes([bytes[8], bytes[9]]);
let pf = u16::from_le_bytes([bytes[10], bytes[11]]);
(
Some(ExtendedPreamble {
extended_data_type: pe,
reserved_pf: pf,
}),
SIX_WORD_PREAMBLE_LEN,
)
} else {
(None, FOUR_WORD_PREAMBLE_LEN)
};
let extended_offset_bits = if is_extended {
EXTENDED_PREAMBLE_LENGTH_CODE_BITS
} else {
0
};
let payload_bits = u32::from(length_code)
.checked_sub(extended_offset_bits)
.ok_or(Error::InvalidValue {
field: "length_code",
value: u64::from(length_code),
reason: "shorter than the six-word preamble's own 32 extended-preamble bits",
})?;
let payload_len = payload_bits.div_ceil(8) as usize;
let total_len = preamble_len + payload_len;
if bytes.len() < total_len {
return Err(Error::BufferTooShort {
need: total_len,
have: bytes.len(),
what: "burst_payload",
});
}
Ok(Self {
preamble: BurstPreamble {
data_type,
data_mode,
error_flag,
data_type_dependent,
data_stream_number,
length_code,
extended,
},
payload: &bytes[preamble_len..total_len],
})
}
}
impl Serialize for Burst<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
self.preamble.wire_len() + self.payload.len()
}
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: "burst serialize output",
});
}
self.preamble.validate()?;
let extended_offset_bits = if self.preamble.extended.is_some() {
EXTENDED_PREAMBLE_LENGTH_CODE_BITS
} else {
0
};
let payload_bits = u32::from(self.preamble.length_code)
.checked_sub(extended_offset_bits)
.ok_or(Error::InvalidValue {
field: "length_code",
value: u64::from(self.preamble.length_code),
reason: "shorter than the six-word preamble's own 32 extended-preamble bits",
})?;
let expected_payload_len = payload_bits.div_ceil(8) as usize;
if self.payload.len() != expected_payload_len {
return Err(Error::InvalidValue {
field: "length_code",
value: u64::from(self.preamble.length_code),
reason: "does not match payload.len() (see BurstPreamble::length_code docs)",
});
}
buf[0..2].copy_from_slice(&SYNC_WORD_PA.to_le_bytes());
buf[2..4].copy_from_slice(&SYNC_WORD_PB.to_le_bytes());
buf[4..6].copy_from_slice(&self.preamble.pack_pc().to_le_bytes());
buf[6..8].copy_from_slice(&self.preamble.length_code.to_le_bytes());
let preamble_len = if let Some(ext) = self.preamble.extended {
buf[8..10].copy_from_slice(&ext.extended_data_type.to_le_bytes());
buf[10..12].copy_from_slice(&ext.reserved_pf.to_le_bytes());
SIX_WORD_PREAMBLE_LEN
} else {
FOUR_WORD_PREAMBLE_LEN
};
buf[preamble_len..preamble_len + self.payload.len()].copy_from_slice(self.payload);
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_a_minimal_four_word_burst() {
let payload = [0xAAu8, 0xBB, 0xCC];
let burst = Burst::new(1, DataMode::Mode16, false, 0, 0, None, &payload).unwrap();
assert_eq!(burst.preamble.length_code, 24);
let bytes = burst.to_bytes();
assert_eq!(bytes.len(), 8 + 3);
let reparsed = Burst::parse(&bytes).unwrap();
assert_eq!(reparsed, burst);
}
#[test]
fn round_trips_a_six_word_extended_burst() {
let payload = [0x11u8, 0x22, 0x33, 0x44];
let ext = ExtendedPreamble {
extended_data_type: 0x1234,
reserved_pf: 0x0000,
};
let burst = Burst::new(
EXTENDED_DATA_TYPE_MARKER,
DataMode::Mode16,
true,
5,
7,
Some(ext),
&payload,
)
.unwrap();
assert_eq!(burst.preamble.length_code, 64);
let bytes = burst.to_bytes();
assert_eq!(bytes.len(), 12 + 4);
let reparsed = Burst::parse(&bytes).unwrap();
assert_eq!(reparsed, burst);
assert_eq!(reparsed.preamble.extended, Some(ext));
}
#[test]
fn pc_bit_layout_matches_the_real_ffmpeg_oracle() {
let preamble = BurstPreamble {
data_type: 21,
data_mode: DataMode::Mode16,
error_flag: false,
data_type_dependent: 0,
data_stream_number: 0,
length_code: 0,
extended: None,
};
assert_eq!(preamble.pack_pc(), 0x0015);
let (dt, dm, ef, dtd, dsn) = BurstPreamble::unpack_pc(0x0015);
assert_eq!(dt, 21);
assert_eq!(dm, DataMode::Mode16);
assert!(!ef);
assert_eq!(dtd, 0);
assert_eq!(dsn, 0);
}
#[test]
fn rejects_bad_pa_sync() {
let mut bytes = [0u8; 8];
bytes[0..2].copy_from_slice(&0x0000u16.to_le_bytes());
let err = Burst::parse(&bytes).unwrap_err();
assert!(matches!(err, Error::InvalidSync { word: "Pa", .. }));
}
#[test]
fn rejects_bad_pb_sync() {
let mut bytes = [0u8; 8];
bytes[0..2].copy_from_slice(&SYNC_WORD_PA.to_le_bytes());
bytes[2..4].copy_from_slice(&0x0000u16.to_le_bytes());
let err = Burst::parse(&bytes).unwrap_err();
assert!(matches!(err, Error::InvalidSync { word: "Pb", .. }));
}
#[test]
fn rejects_unsupported_data_mode() {
let payload = [0u8; 1];
let err = Burst::new(0, DataMode::Mode20, false, 0, 0, None, &payload).unwrap_err();
assert!(matches!(err, Error::UnsupportedDataMode(DataMode::Mode20)));
}
#[test]
fn data_type_31_requires_extended_preamble() {
let payload = [0u8; 1];
let err = Burst::new(
EXTENDED_DATA_TYPE_MARKER,
DataMode::Mode16,
false,
0,
0,
None,
&payload,
)
.unwrap_err();
assert!(matches!(
err,
Error::ExtendedPreambleMismatch {
data_type: EXTENDED_DATA_TYPE_MARKER,
expected_extended: true,
found_extended: false,
}
));
}
#[test]
fn non_31_data_type_rejects_extended_preamble() {
let payload = [0u8; 1];
let ext = ExtendedPreamble {
extended_data_type: 0,
reserved_pf: 0,
};
let err = Burst::new(1, DataMode::Mode16, false, 0, 0, Some(ext), &payload).unwrap_err();
assert!(matches!(
err,
Error::ExtendedPreambleMismatch {
data_type: 1,
expected_extended: false,
found_extended: true,
}
));
}
#[test]
fn max_16_bit_mode_payload_is_accepted() {
let payload = vec![0xFFu8; 8191];
let burst = Burst::new(0, DataMode::Mode16, false, 0, 0, None, &payload).unwrap();
assert_eq!(burst.preamble.length_code, 65528); let bytes = burst.to_bytes();
let reparsed = Burst::parse(&bytes).unwrap();
assert_eq!(reparsed, burst);
}
#[test]
fn oversized_payload_is_rejected() {
let payload = vec![0u8; 8192]; let err = Burst::new(0, DataMode::Mode16, false, 0, 0, None, &payload).unwrap_err();
assert!(matches!(err, Error::PayloadTooLarge { .. }));
}
#[test]
fn buffer_too_short_for_preamble() {
let err = Burst::parse(&[0u8; 4]).unwrap_err();
assert!(matches!(
err,
Error::BufferTooShort {
need: 8,
have: 4,
..
}
));
}
#[test]
fn buffer_too_short_for_payload() {
let burst = Burst::new(0, DataMode::Mode16, false, 0, 0, None, &[1, 2, 3]).unwrap();
let bytes = burst.to_bytes();
let err = Burst::parse(&bytes[..bytes.len() - 1]).unwrap_err();
assert!(matches!(
err,
Error::BufferTooShort {
what: "burst_payload",
..
}
));
}
#[test]
fn data_mode_name_and_display() {
assert_eq!(DataMode::Mode16.name(), "16-bit mode");
assert_eq!(DataMode::Reserved.to_string(), "reserved");
}
}