use alloc::vec::Vec;
use crate::data_unit_id::DataUnitId;
use crate::error::{Error, Result};
use crate::line_header::{LINE_HEADER_LEN, LineHeader};
pub const TXT_DATA_BLOCK_LEN: usize = 42;
pub const TELETEXT_FIELD_LEN: usize = LINE_HEADER_LEN + 1 + TXT_DATA_BLOCK_LEN;
pub const TELETEXT_DATA_UNIT_LENGTH: u8 = 0x2C;
pub const FRAMING_CODE_EBU: u8 = 0b1110_0100;
pub const FRAMING_CODE_INVERTED: u8 = 0b0001_1011;
pub const VPS_DATA_BLOCK_LEN: usize = 13;
pub const VPS_FIELD_LEN: usize = LINE_HEADER_LEN + VPS_DATA_BLOCK_LEN;
pub const WSS_FIELD_LEN: usize = LINE_HEADER_LEN + 2;
pub const WSS_DATA_BLOCK_MASK: u16 = 0x3FFF;
const WSS_BYTE2_DATA_MASK: u8 = 0x3F;
pub const WSS_RESERVED_TAIL: u8 = 0b11;
pub const CC_FIELD_LEN: usize = LINE_HEADER_LEN + 2;
pub const MONO_HEADER_LEN: usize = 4;
const MONO_FIRST_SEGMENT: u8 = 0b1000_0000;
const MONO_LAST_SEGMENT: u8 = 0b0100_0000;
const MONO_FIELD_PARITY: u8 = 0b0010_0000;
const MONO_LINE_OFFSET: u8 = 0b0001_1111;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TeletextDataField {
pub header: LineHeader,
pub framing_code: u8,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_txt_block"))]
pub txt_data_block: [u8; TXT_DATA_BLOCK_LEN],
}
#[cfg(feature = "serde")]
fn serialize_txt_block<S>(
block: &[u8; TXT_DATA_BLOCK_LEN],
s: S,
) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
s.serialize_bytes(block)
}
impl TeletextDataField {
pub fn serialized_len(&self) -> usize {
TELETEXT_FIELD_LEN
}
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < TELETEXT_FIELD_LEN {
return Err(Error::BufferTooShort {
need: TELETEXT_FIELD_LEN,
have: data.len(),
what: "txt_data_field",
});
}
let header = LineHeader::from_byte(data[0]);
let framing_code = data[1];
let mut txt_data_block = [0u8; TXT_DATA_BLOCK_LEN];
txt_data_block.copy_from_slice(&data[2..2 + TXT_DATA_BLOCK_LEN]);
Ok(TeletextDataField {
header,
framing_code,
txt_data_block,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
if out.len() < TELETEXT_FIELD_LEN {
return Err(Error::OutputBufferTooSmall {
need: TELETEXT_FIELD_LEN,
have: out.len(),
});
}
out[0] = self.header.to_byte()?;
out[1] = self.framing_code;
out[2..2 + TXT_DATA_BLOCK_LEN].copy_from_slice(&self.txt_data_block);
Ok(TELETEXT_FIELD_LEN)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct VpsDataField {
pub header: LineHeader,
pub vps_data_block: [u8; VPS_DATA_BLOCK_LEN],
}
impl VpsDataField {
pub fn serialized_len(&self) -> usize {
VPS_FIELD_LEN
}
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < VPS_FIELD_LEN {
return Err(Error::BufferTooShort {
need: VPS_FIELD_LEN,
have: data.len(),
what: "vps_data_field",
});
}
let header = LineHeader::from_byte(data[0]);
let mut vps_data_block = [0u8; VPS_DATA_BLOCK_LEN];
vps_data_block.copy_from_slice(&data[1..1 + VPS_DATA_BLOCK_LEN]);
Ok(VpsDataField {
header,
vps_data_block,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
if out.len() < VPS_FIELD_LEN {
return Err(Error::OutputBufferTooSmall {
need: VPS_FIELD_LEN,
have: out.len(),
});
}
out[0] = self.header.to_byte()?;
out[1..1 + VPS_DATA_BLOCK_LEN].copy_from_slice(&self.vps_data_block);
Ok(VPS_FIELD_LEN)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct WssDataField {
pub header: LineHeader,
pub wss_data_block: u16,
}
impl WssDataField {
pub fn serialized_len(&self) -> usize {
WSS_FIELD_LEN
}
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < WSS_FIELD_LEN {
return Err(Error::BufferTooShort {
need: WSS_FIELD_LEN,
have: data.len(),
what: "wss_data_field",
});
}
let header = LineHeader::from_byte(data[0]);
let wss_data_block =
(((data[1] as u16) << 6) | ((data[2] as u16) >> 2)) & WSS_DATA_BLOCK_MASK;
Ok(WssDataField {
header,
wss_data_block,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
if out.len() < WSS_FIELD_LEN {
return Err(Error::OutputBufferTooSmall {
need: WSS_FIELD_LEN,
have: out.len(),
});
}
if self.wss_data_block > WSS_DATA_BLOCK_MASK {
return Err(Error::FieldTooWide {
what: "wss_data_block",
value: self.wss_data_block as u32,
bits: 14,
});
}
out[0] = self.header.to_byte()?;
out[1] = (self.wss_data_block >> 6) as u8;
out[2] = (((self.wss_data_block as u8) & WSS_BYTE2_DATA_MASK) << 2) | WSS_RESERVED_TAIL;
Ok(WSS_FIELD_LEN)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ClosedCaptioningDataField {
pub header: LineHeader,
pub closed_captioning_data_block: u16,
}
impl ClosedCaptioningDataField {
pub fn serialized_len(&self) -> usize {
CC_FIELD_LEN
}
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < CC_FIELD_LEN {
return Err(Error::BufferTooShort {
need: CC_FIELD_LEN,
have: data.len(),
what: "closed_captioning_data_field",
});
}
let header = LineHeader::from_byte(data[0]);
let closed_captioning_data_block = u16::from_be_bytes([data[1], data[2]]);
Ok(ClosedCaptioningDataField {
header,
closed_captioning_data_block,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
if out.len() < CC_FIELD_LEN {
return Err(Error::OutputBufferTooSmall {
need: CC_FIELD_LEN,
have: out.len(),
});
}
out[0] = self.header.to_byte()?;
out[1..3].copy_from_slice(&self.closed_captioning_data_block.to_be_bytes());
Ok(CC_FIELD_LEN)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MonochromeDataField<'a> {
pub first_segment: bool,
pub last_segment: bool,
pub field_parity: bool,
pub line_offset: u8,
pub first_pixel_position: u16,
#[cfg_attr(feature = "serde", serde(borrow))]
pub samples: &'a [u8],
}
impl<'a> MonochromeDataField<'a> {
pub fn serialized_len(&self) -> usize {
MONO_HEADER_LEN + self.samples.len()
}
pub fn parse(data: &'a [u8]) -> Result<Self> {
if data.len() < MONO_HEADER_LEN {
return Err(Error::BufferTooShort {
need: MONO_HEADER_LEN,
have: data.len(),
what: "monochrome_data_field header",
});
}
let b0 = data[0];
let first_segment = (b0 & MONO_FIRST_SEGMENT) != 0;
let last_segment = (b0 & MONO_LAST_SEGMENT) != 0;
let field_parity = (b0 & MONO_FIELD_PARITY) != 0;
let line_offset = b0 & MONO_LINE_OFFSET;
let first_pixel_position = u16::from_be_bytes([data[1], data[2]]);
let n_pixels = data[3] as usize;
if n_pixels == 0 {
return Err(Error::InvalidField {
what: "n_pixels",
reason: "n_pixels shall be > 0 (ETSI EN 301 775 §4.9.2)",
});
}
if data.len() < MONO_HEADER_LEN + n_pixels {
return Err(Error::BufferTooShort {
need: MONO_HEADER_LEN + n_pixels,
have: data.len(),
what: "monochrome Y_value samples",
});
}
let samples = &data[MONO_HEADER_LEN..MONO_HEADER_LEN + n_pixels];
Ok(MonochromeDataField {
first_segment,
last_segment,
field_parity,
line_offset,
first_pixel_position,
samples,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if out.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: out.len(),
});
}
if self.line_offset > MONO_LINE_OFFSET {
return Err(Error::FieldTooWide {
what: "line_offset",
value: self.line_offset as u32,
bits: 5,
});
}
if self.samples.len() > u8::MAX as usize {
return Err(Error::FieldTooWide {
what: "n_pixels",
value: self.samples.len() as u32,
bits: 8,
});
}
let mut b0 = self.line_offset;
if self.first_segment {
b0 |= MONO_FIRST_SEGMENT;
}
if self.last_segment {
b0 |= MONO_LAST_SEGMENT;
}
if self.field_parity {
b0 |= MONO_FIELD_PARITY;
}
out[0] = b0;
out[1..3].copy_from_slice(&self.first_pixel_position.to_be_bytes());
out[3] = self.samples.len() as u8;
out[MONO_HEADER_LEN..total].copy_from_slice(self.samples);
Ok(total)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum DataUnitPayload<'a> {
Teletext(TeletextDataField),
Vps(VpsDataField),
Wss(WssDataField),
ClosedCaptioning(ClosedCaptioningDataField),
Monochrome(#[cfg_attr(feature = "serde", serde(borrow))] MonochromeDataField<'a>),
Stuffing {
length: u8,
},
Opaque(#[cfg_attr(feature = "serde", serde(borrow))] &'a [u8]),
}
impl<'a> DataUnitPayload<'a> {
pub fn serialized_len(&self) -> usize {
match self {
DataUnitPayload::Teletext(f) => f.serialized_len(),
DataUnitPayload::Vps(f) => f.serialized_len(),
DataUnitPayload::Wss(f) => f.serialized_len(),
DataUnitPayload::ClosedCaptioning(f) => f.serialized_len(),
DataUnitPayload::Monochrome(f) => f.serialized_len(),
DataUnitPayload::Stuffing { length } => *length as usize,
DataUnitPayload::Opaque(b) => b.len(),
}
}
pub fn parse(id: DataUnitId, body: &'a [u8]) -> Result<Self> {
match id {
DataUnitId::EbuTeletextNonSubtitle
| DataUnitId::EbuTeletextSubtitle
| DataUnitId::InvertedTeletext => {
Ok(DataUnitPayload::Teletext(TeletextDataField::parse(body)?))
}
DataUnitId::Vps => Ok(DataUnitPayload::Vps(VpsDataField::parse(body)?)),
DataUnitId::Wss => Ok(DataUnitPayload::Wss(WssDataField::parse(body)?)),
DataUnitId::ClosedCaptioning => Ok(DataUnitPayload::ClosedCaptioning(
ClosedCaptioningDataField::parse(body)?,
)),
DataUnitId::Monochrome422Samples => Ok(DataUnitPayload::Monochrome(
MonochromeDataField::parse(body)?,
)),
DataUnitId::Stuffing => {
if body.len() > u8::MAX as usize {
return Err(Error::InvalidDataUnitLength {
length: 0,
id: id.to_u8(),
reason: "stuffing length exceeds 8 bits",
});
}
Ok(DataUnitPayload::Stuffing {
length: body.len() as u8,
})
}
DataUnitId::Reserved(_) | DataUnitId::UserDefined(_) => {
Ok(DataUnitPayload::Opaque(body))
}
}
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
match self {
DataUnitPayload::Teletext(f) => f.serialize_into(out),
DataUnitPayload::Vps(f) => f.serialize_into(out),
DataUnitPayload::Wss(f) => f.serialize_into(out),
DataUnitPayload::ClosedCaptioning(f) => f.serialize_into(out),
DataUnitPayload::Monochrome(f) => f.serialize_into(out),
DataUnitPayload::Stuffing { length } => {
let n = *length as usize;
if out.len() < n {
return Err(Error::OutputBufferTooSmall {
need: n,
have: out.len(),
});
}
for b in out.iter_mut().take(n) {
*b = crate::data_unit_id::ID_STUFFING; }
Ok(n)
}
DataUnitPayload::Opaque(b) => {
if out.len() < b.len() {
return Err(Error::OutputBufferTooSmall {
need: b.len(),
have: out.len(),
});
}
out[..b.len()].copy_from_slice(b);
Ok(b.len())
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DataUnit<'a> {
pub id: DataUnitId,
#[cfg_attr(feature = "serde", serde(borrow))]
pub payload: DataUnitPayload<'a>,
}
impl<'a> DataUnit<'a> {
pub fn data_unit_length(&self) -> usize {
self.payload.serialized_len()
}
pub fn serialized_len(&self) -> usize {
2 + self.data_unit_length()
}
pub fn teletext(id: DataUnitId, field: TeletextDataField) -> Self {
DataUnit {
id,
payload: DataUnitPayload::Teletext(field),
}
}
pub fn vps(field: VpsDataField) -> Self {
DataUnit {
id: DataUnitId::Vps,
payload: DataUnitPayload::Vps(field),
}
}
pub fn wss(field: WssDataField) -> Self {
DataUnit {
id: DataUnitId::Wss,
payload: DataUnitPayload::Wss(field),
}
}
pub fn closed_captioning(field: ClosedCaptioningDataField) -> Self {
DataUnit {
id: DataUnitId::ClosedCaptioning,
payload: DataUnitPayload::ClosedCaptioning(field),
}
}
pub fn monochrome(field: MonochromeDataField<'a>) -> Self {
DataUnit {
id: DataUnitId::Monochrome422Samples,
payload: DataUnitPayload::Monochrome(field),
}
}
pub fn stuffing(length: u8) -> Self {
DataUnit {
id: DataUnitId::Stuffing,
payload: DataUnitPayload::Stuffing { length },
}
}
pub fn parse(data: &'a [u8]) -> Result<(Self, usize)> {
if data.len() < 2 {
return Err(Error::BufferTooShort {
need: 2,
have: data.len(),
what: "data_unit header (id + length)",
});
}
let id = DataUnitId::from_u8(data[0]);
let length = data[1] as usize;
let body_end = 2 + length;
if data.len() < body_end {
return Err(Error::BufferTooShort {
need: body_end,
have: data.len(),
what: "data_unit body",
});
}
let body = &data[2..body_end];
let payload = DataUnitPayload::parse(id, body)?;
if payload.serialized_len() != length {
return Err(Error::InvalidDataUnitLength {
length: data[1],
id: data[0],
reason: "typed payload size does not match data_unit_length",
});
}
Ok((DataUnit { id, payload }, body_end))
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if out.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: out.len(),
});
}
let length = self.data_unit_length();
if length > u8::MAX as usize {
return Err(Error::FieldTooWide {
what: "data_unit_length",
value: length as u32,
bits: 8,
});
}
out[0] = self.id.to_u8();
out[1] = length as u8;
let written = self.payload.serialize_into(&mut out[2..total])?;
debug_assert_eq!(written, length);
Ok(2 + written)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DataField<'a> {
pub data_identifier: u8,
#[cfg_attr(feature = "serde", serde(borrow))]
pub data_units: Vec<DataUnit<'a>>,
}
impl<'a> DataField<'a> {
pub fn new(data_identifier: u8, data_units: Vec<DataUnit<'a>>) -> Self {
DataField {
data_identifier,
data_units,
}
}
pub fn serialized_len(&self) -> usize {
1 + self
.data_units
.iter()
.map(DataUnit::serialized_len)
.sum::<usize>()
}
pub fn parse(data: &'a [u8]) -> Result<Self> {
if data.is_empty() {
return Err(Error::BufferTooShort {
need: 1,
have: 0,
what: "data_identifier",
});
}
let data_identifier = data[0];
let mut data_units = Vec::new();
let mut off = 1;
while off < data.len() {
let (unit, consumed) = DataUnit::parse(&data[off..])?;
data_units.push(unit);
off += consumed;
}
Ok(DataField {
data_identifier,
data_units,
})
}
pub fn serialize_into(&self, out: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
if out.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: out.len(),
});
}
out[0] = self.data_identifier;
let mut off = 1;
for unit in &self.data_units {
off += unit.serialize_into(&mut out[off..])?;
}
Ok(off)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::line_header::LineHeader;
use alloc::vec;
fn unit_round_trip(unit: &DataUnit, expected_wire: &[u8]) {
let mut out = vec![0u8; unit.serialized_len()];
let n = unit.serialize_into(&mut out).unwrap();
assert_eq!(n, unit.serialized_len());
assert_eq!(out, expected_wire, "exact wire bytes");
let (re, consumed) = DataUnit::parse(&out).unwrap();
assert_eq!(consumed, out.len());
assert_eq!(&re, unit, "reparse must equal the original");
}
#[test]
fn teletext_exact_wire_bytes() {
let block = [0xAAu8; TXT_DATA_BLOCK_LEN];
let field = TeletextDataField {
header: LineHeader::new(true, 7), framing_code: FRAMING_CODE_EBU,
txt_data_block: block,
};
let unit = DataUnit::teletext(DataUnitId::EbuTeletextSubtitle, field);
let mut expected = vec![0x03, 0x2C, 0xE7, FRAMING_CODE_EBU];
expected.extend_from_slice(&block);
assert_eq!(unit.data_unit_length(), TELETEXT_DATA_UNIT_LENGTH as usize);
unit_round_trip(&unit, &expected);
}
#[test]
fn vps_exact_wire_bytes() {
let block = [
0x01u8, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
];
let field = VpsDataField {
header: LineHeader::new(true, 16), vps_data_block: block,
};
let unit = DataUnit::vps(field);
let mut expected = vec![0xC3, 0x0E, 0xF0];
expected.extend_from_slice(&block);
unit_round_trip(&unit, &expected);
}
#[test]
fn wss_exact_wire_bytes_and_bit_packing() {
let field = WssDataField {
header: LineHeader::new(true, 23), wss_data_block: 0x3A5C,
};
let unit = DataUnit::wss(field);
let expected = vec![0xC4, 0x03, 0xF7, 0xE9, 0x73];
unit_round_trip(&unit, &expected);
}
#[test]
fn cc_exact_wire_bytes() {
let field = ClosedCaptioningDataField {
header: LineHeader::new(false, 21), closed_captioning_data_block: 0x9425,
};
let unit = DataUnit::closed_captioning(field);
let expected = vec![0xC5, 0x03, 0xD5, 0x94, 0x25];
unit_round_trip(&unit, &expected);
}
#[test]
fn monochrome_exact_wire_bytes() {
let samples = [0x10u8, 0x40, 0x80, 0xEB];
let field = MonochromeDataField {
first_segment: true,
last_segment: false,
field_parity: true,
line_offset: 10,
first_pixel_position: 0x0123,
samples: &samples,
};
let unit = DataUnit::monochrome(field);
let mut expected = vec![0xC6, 0x08, 0xAA, 0x01, 0x23, 0x04];
expected.extend_from_slice(&samples);
unit_round_trip(&unit, &expected);
}
#[test]
fn stuffing_exact_wire_bytes() {
let unit = DataUnit::stuffing(3);
let expected = vec![0xFF, 0x03, 0xFF, 0xFF, 0xFF];
unit_round_trip(&unit, &expected);
}
#[test]
fn opaque_reserved_round_trips() {
let body = [0xDEu8, 0xAD, 0xBE];
let unit = DataUnit {
id: DataUnitId::Reserved(0x55),
payload: DataUnitPayload::Opaque(&body),
};
let expected = vec![0x55, 0x03, 0xDE, 0xAD, 0xBE];
unit_round_trip(&unit, &expected);
}
#[test]
fn mutating_a_field_changes_wire_bytes() {
let block = [0u8; VPS_DATA_BLOCK_LEN];
let a = DataUnit::vps(VpsDataField {
header: LineHeader::new(true, 16),
vps_data_block: block,
});
let mut block_b = block;
block_b[0] = 0xFF;
let b = DataUnit::vps(VpsDataField {
header: LineHeader::new(true, 16),
vps_data_block: block_b,
});
let mut out_a = vec![0u8; a.serialized_len()];
a.serialize_into(&mut out_a).unwrap();
let mut out_b = vec![0u8; b.serialized_len()];
b.serialize_into(&mut out_b).unwrap();
assert_ne!(
out_a, out_b,
"different vps_data_block must change wire bytes"
);
let c = DataUnit::vps(VpsDataField {
header: LineHeader::new(false, 16),
vps_data_block: block,
});
let mut out_c = vec![0u8; c.serialized_len()];
c.serialize_into(&mut out_c).unwrap();
assert_ne!(out_a, out_c, "field_parity must change the header byte");
assert_eq!(out_a[2] & 0b0010_0000, 0b0010_0000);
assert_eq!(out_c[2] & 0b0010_0000, 0);
}
#[test]
fn multi_unit_data_field_round_trip() {
let vps = DataUnit::vps(VpsDataField {
header: LineHeader::new(true, 16),
vps_data_block: [0x11; VPS_DATA_BLOCK_LEN],
});
let wss = DataUnit::wss(WssDataField {
header: LineHeader::new(true, 23),
wss_data_block: 0x1234,
});
let block = [0x42u8; TXT_DATA_BLOCK_LEN];
let txt = DataUnit::teletext(
DataUnitId::EbuTeletextNonSubtitle,
TeletextDataField {
header: LineHeader::new(false, 9),
framing_code: FRAMING_CODE_EBU,
txt_data_block: block,
},
);
let field = DataField::new(0x10, vec![vps, wss, txt]);
let mut out = vec![0u8; field.serialized_len()];
let n = field.serialize_into(&mut out).unwrap();
assert_eq!(n, field.serialized_len());
assert_eq!(out[0], 0x10);
let parsed = DataField::parse(&out).unwrap();
assert_eq!(parsed, field, "multi-unit data field must round-trip");
assert_eq!(parsed.data_units.len(), 3);
assert_eq!(parsed.data_units[0].id, DataUnitId::Vps);
assert_eq!(parsed.data_units[1].id, DataUnitId::Wss);
assert_eq!(parsed.data_units[2].id, DataUnitId::EbuTeletextNonSubtitle);
let mut out2 = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut out2).unwrap();
assert_eq!(out, out2);
}
#[test]
fn rejects_truncated_data_unit() {
let data = [0xC3u8, 0x0E, 0x00];
assert!(DataUnit::parse(&data).is_err());
}
#[test]
fn rejects_length_mismatch() {
let data = [
0xC3u8, 0x0F, 0xF0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
];
assert!(matches!(
DataUnit::parse(&data),
Err(Error::InvalidDataUnitLength { .. })
));
}
#[test]
fn wss_bit_packing_recovers_value() {
for v in [0u16, 1, 0x3FFF, 0x2A55, 0x1FFF] {
let f = WssDataField {
header: LineHeader::new(true, 23),
wss_data_block: v,
};
let mut out = [0u8; WSS_FIELD_LEN];
f.serialize_into(&mut out).unwrap();
let re = WssDataField::parse(&out).unwrap();
assert_eq!(re.wss_data_block, v, "wss value {v:#06X}");
assert_eq!(out[2] & 0b11, WSS_RESERVED_TAIL);
}
}
#[test]
fn monochrome_rejects_zero_n_pixels() {
let data = [0b1110_0111u8, 0x00, 0x00, 0x00];
let result = MonochromeDataField::parse(&data);
assert!(
matches!(
result,
Err(crate::error::Error::InvalidField {
what: "n_pixels",
..
})
),
"n_pixels=0 must be rejected with InvalidField, got: {result:?}"
);
}
#[test]
fn mutating_teletext_framing_code_changes_wire_bytes() {
let block = [0xBBu8; TXT_DATA_BLOCK_LEN];
let a = DataUnit::teletext(
DataUnitId::EbuTeletextNonSubtitle,
TeletextDataField {
header: LineHeader::new(true, 7),
framing_code: FRAMING_CODE_EBU,
txt_data_block: block,
},
);
let b = DataUnit::teletext(
DataUnitId::EbuTeletextNonSubtitle,
TeletextDataField {
header: LineHeader::new(true, 7),
framing_code: FRAMING_CODE_INVERTED,
txt_data_block: block,
},
);
let mut out_a = vec![0u8; a.serialized_len()];
a.serialize_into(&mut out_a).unwrap();
let mut out_b = vec![0u8; b.serialized_len()];
b.serialize_into(&mut out_b).unwrap();
assert_ne!(
out_a, out_b,
"different framing_code must change wire bytes"
);
}
#[test]
fn mutating_teletext_txt_data_block_changes_wire_bytes() {
let block_a = [0x00u8; TXT_DATA_BLOCK_LEN];
let mut block_b = block_a;
block_b[0] = 0xFF;
let a = DataUnit::teletext(
DataUnitId::EbuTeletextNonSubtitle,
TeletextDataField {
header: LineHeader::new(true, 7),
framing_code: FRAMING_CODE_EBU,
txt_data_block: block_a,
},
);
let b = DataUnit::teletext(
DataUnitId::EbuTeletextNonSubtitle,
TeletextDataField {
header: LineHeader::new(true, 7),
framing_code: FRAMING_CODE_EBU,
txt_data_block: block_b,
},
);
let mut out_a = vec![0u8; a.serialized_len()];
a.serialize_into(&mut out_a).unwrap();
let mut out_b = vec![0u8; b.serialized_len()];
b.serialize_into(&mut out_b).unwrap();
assert_ne!(
out_a, out_b,
"different txt_data_block must change wire bytes"
);
}
#[test]
fn mutating_wss_data_block_changes_wire_bytes() {
let a = DataUnit::wss(WssDataField {
header: LineHeader::new(true, 23),
wss_data_block: 0x0000,
});
let b = DataUnit::wss(WssDataField {
header: LineHeader::new(true, 23),
wss_data_block: 0x3FFF,
});
let mut out_a = vec![0u8; a.serialized_len()];
a.serialize_into(&mut out_a).unwrap();
let mut out_b = vec![0u8; b.serialized_len()];
b.serialize_into(&mut out_b).unwrap();
assert_ne!(
out_a, out_b,
"different wss_data_block must change wire bytes"
);
}
#[test]
fn mutating_cc_data_block_changes_wire_bytes() {
let a = DataUnit::closed_captioning(ClosedCaptioningDataField {
header: LineHeader::new(false, 21),
closed_captioning_data_block: 0x0000,
});
let b = DataUnit::closed_captioning(ClosedCaptioningDataField {
header: LineHeader::new(false, 21),
closed_captioning_data_block: 0xFFFF,
});
let mut out_a = vec![0u8; a.serialized_len()];
a.serialize_into(&mut out_a).unwrap();
let mut out_b = vec![0u8; b.serialized_len()];
b.serialize_into(&mut out_b).unwrap();
assert_ne!(
out_a, out_b,
"different closed_captioning_data_block must change wire bytes"
);
}
#[test]
fn mutating_monochrome_first_segment_flag_changes_wire_bytes() {
let samples = [0x10u8, 0x80];
let a = DataUnit::monochrome(MonochromeDataField {
first_segment: true,
last_segment: false,
field_parity: true,
line_offset: 10,
first_pixel_position: 0,
samples: &samples,
});
let b = DataUnit::monochrome(MonochromeDataField {
first_segment: false,
last_segment: false,
field_parity: true,
line_offset: 10,
first_pixel_position: 0,
samples: &samples,
});
let mut out_a = vec![0u8; a.serialized_len()];
a.serialize_into(&mut out_a).unwrap();
let mut out_b = vec![0u8; b.serialized_len()];
b.serialize_into(&mut out_b).unwrap();
assert_ne!(
out_a, out_b,
"different first_segment flag must change the first wire byte"
);
}
#[test]
fn mutating_monochrome_y_sample_changes_wire_bytes() {
let samples_a = [0x10u8, 0x80];
let mut samples_b = samples_a;
samples_b[0] = 0xFF;
let a = DataUnit::monochrome(MonochromeDataField {
first_segment: true,
last_segment: true,
field_parity: true,
line_offset: 10,
first_pixel_position: 0,
samples: &samples_a,
});
let b = DataUnit::monochrome(MonochromeDataField {
first_segment: true,
last_segment: true,
field_parity: true,
line_offset: 10,
first_pixel_position: 0,
samples: &samples_b,
});
let mut out_a = vec![0u8; a.serialized_len()];
a.serialize_into(&mut out_a).unwrap();
let mut out_b = vec![0u8; b.serialized_len()];
b.serialize_into(&mut out_b).unwrap();
assert_ne!(out_a, out_b, "different Y sample must change wire bytes");
}
#[test]
fn every_non_opaque_data_unit_id_has_a_typed_payload() {
use crate::data_unit_id::{
ID_CLOSED_CAPTIONING, ID_EBU_TELETEXT_NON_SUBTITLE, ID_EBU_TELETEXT_SUBTITLE,
ID_INVERTED_TELETEXT, ID_MONOCHROME_422_SAMPLES, ID_STUFFING, ID_VPS, ID_WSS,
};
let typed_ids: &[u8] = &[
ID_EBU_TELETEXT_NON_SUBTITLE,
ID_EBU_TELETEXT_SUBTITLE,
ID_INVERTED_TELETEXT,
ID_VPS,
ID_WSS,
ID_CLOSED_CAPTIONING,
ID_MONOCHROME_422_SAMPLES,
ID_STUFFING,
];
let teletext_body = {
let mut b = vec![0u8; TELETEXT_FIELD_LEN];
b[0] = 0xE7;
b[1] = FRAMING_CODE_EBU;
b
};
let vps_body = {
let mut b = vec![0u8; VPS_FIELD_LEN];
b[0] = 0xF0; b
};
let wss_body = {
let mut b = vec![0u8; WSS_FIELD_LEN];
b[0] = 0xF7; b
};
let cc_body = {
let mut b = vec![0u8; CC_FIELD_LEN];
b[0] = 0xD5; b
};
let mono_body = vec![0b1110_0111u8, 0x00, 0x00, 0x01, 0x80];
let stuffing_body = vec![0xFFu8; 3];
let bodies: &[&[u8]] = &[
&teletext_body,
&teletext_body,
&teletext_body,
&vps_body,
&wss_body,
&cc_body,
&mono_body,
&stuffing_body,
];
for (&id, &body) in typed_ids.iter().zip(bodies.iter()) {
let du_id = DataUnitId::from_u8(id);
let payload = DataUnitPayload::parse(du_id, body)
.unwrap_or_else(|e| panic!("id={id:#04X} parse failed: {e}"));
assert!(
!matches!(payload, DataUnitPayload::Opaque(_)),
"id={id:#04X} ({}) fell to Opaque — add a typed dispatch arm",
DataUnitId::from_u8(id).name()
);
}
}
}