use crate::error::{Error, Result};
use alloc::vec::Vec;
use dvb_common::{Parse, Serialize};
const MAX_CC_COUNT: usize = 31;
const FF: u8 = 0xFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum CcType {
Ntsc608Field1,
Ntsc608Field2,
Dtvcc708Data,
Dtvcc708Start,
}
impl CcType {
#[must_use]
pub fn from_bits(v: u8) -> Self {
match v & 0x03 {
0 => Self::Ntsc608Field1,
1 => Self::Ntsc608Field2,
2 => Self::Dtvcc708Data,
_ => Self::Dtvcc708Start,
}
}
#[must_use]
pub fn to_bits(self) -> u8 {
match self {
Self::Ntsc608Field1 => 0,
Self::Ntsc608Field2 => 1,
Self::Dtvcc708Data => 2,
Self::Dtvcc708Start => 3,
}
}
#[must_use]
pub fn is_cea608(self) -> bool {
matches!(self, Self::Ntsc608Field1 | Self::Ntsc608Field2)
}
#[must_use]
pub fn is_cea708(self) -> bool {
matches!(self, Self::Dtvcc708Data | Self::Dtvcc708Start)
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Ntsc608Field1 => "ntsc_608_field1",
Self::Ntsc608Field2 => "ntsc_608_field2",
Self::Dtvcc708Data => "dtvcc_708_data",
Self::Dtvcc708Start => "dtvcc_708_start",
}
}
}
dvb_common::impl_spec_display!(CcType);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CcTriplet {
pub cc_valid: bool,
pub cc_type: CcType,
pub cc_data_1: u8,
pub cc_data_2: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CcData {
pub process_cc_data_flag: bool,
pub triplets: Vec<CcTriplet>,
}
impl CcData {
pub fn cea608(&self) -> impl Iterator<Item = &CcTriplet> {
self.triplets.iter().filter(|t| t.cc_type.is_cea608())
}
pub fn cea708(&self) -> impl Iterator<Item = &CcTriplet> {
self.triplets.iter().filter(|t| t.cc_type.is_cea708())
}
}
impl<'a> Parse<'a> for CcData {
type Error = Error;
fn parse(b: &'a [u8]) -> Result<Self> {
if b.len() < 2 {
return Err(Error::BufferTooShort {
need: 2,
have: b.len(),
what: "cc_data header",
});
}
let process_cc_data_flag = (b[0] >> 6) & 0x01 != 0;
let cc_count = usize::from(b[0] & 0x1F);
let total = 2 + cc_count * 3 + 1; if b.len() < total {
return Err(Error::BufferTooShort {
need: total,
have: b.len(),
what: "cc_data triplets",
});
}
let mut triplets = Vec::with_capacity(cc_count);
let mut pos = 2;
for _ in 0..cc_count {
let flags = b[pos];
triplets.push(CcTriplet {
cc_valid: (flags >> 2) & 0x01 != 0,
cc_type: CcType::from_bits(flags),
cc_data_1: b[pos + 1],
cc_data_2: b[pos + 2],
});
pos += 3;
}
Ok(CcData {
process_cc_data_flag,
triplets,
})
}
}
impl Serialize for CcData {
type Error = Error;
fn serialized_len(&self) -> usize {
2 + self.triplets.len() * 3 + 1
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
if self.triplets.len() > MAX_CC_COUNT {
return Err(Error::TooManyTriplets(self.triplets.len()));
}
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
let cc_count = self.triplets.len() as u8 & 0x1F;
buf[0] = 0x80 | (u8::from(self.process_cc_data_flag) << 6) | cc_count;
buf[1] = FF;
let mut pos = 2;
for t in &self.triplets {
buf[pos] = 0xF8 | (u8::from(t.cc_valid) << 2) | t.cc_type.to_bits();
buf[pos + 1] = t.cc_data_1;
buf[pos + 2] = t.cc_data_2;
pos += 3;
}
buf[pos] = FF; Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tr(cc_valid: bool, cc_type: CcType, d1: u8, d2: u8) -> CcTriplet {
CcTriplet {
cc_valid,
cc_type,
cc_data_1: d1,
cc_data_2: d2,
}
}
fn sample() -> CcData {
CcData {
process_cc_data_flag: true,
triplets: alloc::vec![
tr(true, CcType::Dtvcc708Start, 0xC1, 0x02),
tr(true, CcType::Ntsc608Field1, 0x94, 0x2C),
tr(false, CcType::Dtvcc708Data, 0x00, 0x00),
],
}
}
#[test]
fn round_trip_constructed() {
let cc = sample();
let bytes = cc.to_bytes();
assert_eq!(CcData::parse(&bytes).unwrap(), cc);
assert_eq!(bytes[0], 0b1100_0011);
assert_eq!(bytes[1], 0xFF);
assert_eq!(*bytes.last().unwrap(), 0xFF);
}
#[test]
fn mutate_field_changes_output() {
let a = sample().to_bytes();
let mut cc = sample();
cc.triplets[0].cc_data_1 = 0x42;
let b = cc.to_bytes();
assert_ne!(a, b, "mutating a field must change serialized bytes");
}
#[test]
fn splits_608_708() {
let cc = sample();
assert_eq!(cc.cea608().count(), 1);
assert_eq!(cc.cea708().count(), 2);
}
#[test]
fn empty_round_trip() {
let cc = CcData {
process_cc_data_flag: false,
triplets: alloc::vec![],
};
let bytes = cc.to_bytes();
assert_eq!(bytes, [0x80, 0xFF, 0xFF]); assert_eq!(CcData::parse(&bytes).unwrap(), cc);
}
#[test]
fn serialize_rejects_over_31_triplets() {
let cc = CcData {
process_cc_data_flag: true,
triplets: alloc::vec![tr(true, CcType::Ntsc608Field1, 0, 0); 32],
};
let mut buf = [0u8; 200];
assert!(matches!(
cc.serialize_into(&mut buf),
Err(Error::TooManyTriplets(32))
));
}
}