use super::{descriptor_body, write_descriptor_header};
use crate::error::{Error, Result};
use crate::text::LangCode;
use alloc::vec::Vec;
use broadcast_common::len::{fit_bits, fit_u8, need_in, need_out};
use broadcast_common::{Parse, Serialize};
use mpeg_ts::pid::Pid;
pub const TAG: u8 = 0x86;
const HEADER_LEN: usize = 2;
const SERVICE_LEN: usize = 6;
const COUNT_BYTE_LEN: usize = 1;
const PID_LEN: usize = 2;
const MAX_SERVICES: usize = 31;
const RESERVED3_ONES: u8 = 0b111;
const RESERVED2_ONES: u8 = 0b11;
const RESERVED1_ONES: u8 = 1;
const RESERVED8_ONES: u8 = 0xFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ChinaCaptionCharSet {
Gb2312,
Gb13000_1,
Gb18030,
Reserved(u8),
}
impl ChinaCaptionCharSet {
#[must_use]
pub const fn from_u8(v: u8) -> Self {
match v & 0x3F {
0 => Self::Gb2312,
1 => Self::Gb13000_1,
2 => Self::Gb18030,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::Gb2312 => 0,
Self::Gb13000_1 => 1,
Self::Gb18030 => 2,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Gb2312 => "GB 2312-1980",
Self::Gb13000_1 => "GB 13000.1-2010",
Self::Gb18030 => "GB 18030-2005",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(ChinaCaptionCharSet, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct ChinaCaptionService {
pub language: LangCode,
pub reserved_a: u8,
pub caption_service_number: u8,
pub reserved_b: u8,
pub wide_aspect_ratio: bool,
pub char_set: ChinaCaptionCharSet,
pub reserved_c: u8,
}
impl ChinaCaptionService {
#[must_use]
pub const fn new(
language: LangCode,
caption_service_number: u8,
wide_aspect_ratio: bool,
char_set: ChinaCaptionCharSet,
) -> Self {
Self {
language,
reserved_a: RESERVED2_ONES,
caption_service_number,
reserved_b: RESERVED1_ONES,
wide_aspect_ratio,
char_set,
reserved_c: RESERVED8_ONES,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct ChinaCaptionServiceDescriptor {
pub reserved_head: u8,
pub services: Vec<ChinaCaptionService>,
pub reserved_pid: u8,
pub caption_service_pid: Pid,
}
impl ChinaCaptionServiceDescriptor {
#[must_use]
pub fn new(services: Vec<ChinaCaptionService>, caption_service_pid: Pid) -> Self {
Self {
reserved_head: RESERVED3_ONES,
services,
reserved_pid: RESERVED3_ONES,
caption_service_pid,
}
}
}
impl<'a> Parse<'a> for ChinaCaptionServiceDescriptor {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"ChinaCaptionServiceDescriptor",
"unexpected tag for caption_service_descriptor",
)?;
need_in(
body,
COUNT_BYTE_LEN + PID_LEN,
"ChinaCaptionServiceDescriptor",
)?;
let n = usize::from(body[0] & 0x1F);
let need = COUNT_BYTE_LEN + n * SERVICE_LEN + PID_LEN;
if body.len() != need {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "descriptor_length does not match number_of_services",
});
}
let mut services = Vec::with_capacity(n);
for chunk in
body[COUNT_BYTE_LEN..COUNT_BYTE_LEN + n * SERVICE_LEN].chunks_exact(SERVICE_LEN)
{
services.push(ChinaCaptionService {
language: LangCode([chunk[0], chunk[1], chunk[2]]),
reserved_a: chunk[3] >> 6,
caption_service_number: chunk[3] & 0x3F,
reserved_b: chunk[4] >> 7,
wide_aspect_ratio: chunk[4] & 0x40 != 0,
char_set: ChinaCaptionCharSet::from_u8(chunk[4] & 0x3F),
reserved_c: chunk[5],
});
}
let tail = &body[need - PID_LEN..];
Ok(Self {
reserved_head: body[0] >> 5,
services,
reserved_pid: tail[0] >> 5,
caption_service_pid: Pid::new(u16::from_be_bytes([tail[0] & 0x1F, tail[1]])),
})
}
}
impl Serialize for ChinaCaptionServiceDescriptor {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
HEADER_LEN + COUNT_BYTE_LEN + self.services.len() * SERVICE_LEN + PID_LEN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
need_out(buf, len)?;
if self.services.len() > MAX_SERVICES {
return Err(Error::ValueOutOfRange {
field: "number_of_services",
reason: "exceeds 5-bit field",
});
}
write_descriptor_header(buf, TAG, len - HEADER_LEN)?;
let count = fit_u8(self.services.len(), "number_of_services")?;
let head = fit_bits(u64::from(self.reserved_head), 3, "reserved")? as u8;
buf[HEADER_LEN] = (head << 5) | count;
let mut i = HEADER_LEN + COUNT_BYTE_LEN;
for s in &self.services {
let ra = fit_bits(u64::from(s.reserved_a), 2, "reserved")? as u8;
let num = fit_bits(
u64::from(s.caption_service_number),
6,
"caption_service_number",
)? as u8;
let rb = fit_bits(u64::from(s.reserved_b), 1, "reserved")? as u8;
let cs = fit_bits(u64::from(s.char_set.to_u8()), 6, "char_set")? as u8;
buf[i..i + 3].copy_from_slice(&s.language.0);
buf[i + 3] = (ra << 6) | num;
buf[i + 4] = (rb << 7) | (u8::from(s.wide_aspect_ratio) << 6) | cs;
buf[i + 5] = s.reserved_c;
i += SERVICE_LEN;
}
let rp = fit_bits(u64::from(self.reserved_pid), 3, "reserved")? as u8;
let [pid_hi, pid_lo] = self.caption_service_pid.value().to_be_bytes();
buf[i] = (rp << 5) | pid_hi;
buf[i + 1] = pid_lo;
Ok(i + PID_LEN)
}
}
impl crate::traits::DescriptorDef<'_> for ChinaCaptionServiceDescriptor {
const TAG: u8 = TAG;
const NAME: &'static str = "CHINA_CAPTION_SERVICE";
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
fn round_trip(bytes: &[u8]) -> ChinaCaptionServiceDescriptor {
let d = ChinaCaptionServiceDescriptor::parse(bytes).unwrap();
let mut out = vec![0u8; d.serialized_len()];
let n = d.serialize_into(&mut out).unwrap();
assert_eq!(&out[..n], bytes, "byte-exact round trip");
d
}
#[test]
fn table_8_single_service() {
let bytes = [TAG, 9, 0xE1, b'c', b'h', b'i', 0xC1, 0xC2, 0xFF, 0xE1, 0x00];
let d = round_trip(&bytes);
assert_eq!(d.services.len(), 1);
let s = d.services[0];
assert_eq!(s.language, LangCode(*b"chi"));
assert_eq!(s.caption_service_number, 1);
assert!(s.wide_aspect_ratio);
assert_eq!(s.char_set, ChinaCaptionCharSet::Gb18030);
assert_eq!((s.reserved_a, s.reserved_b, s.reserved_c), (3, 1, 0xFF));
assert_eq!(d.caption_service_pid, Pid::new(0x0100));
assert_eq!((d.reserved_head, d.reserved_pid), (7, 7));
}
#[test]
fn table_8_two_services() {
let bytes = [
TAG, 15, 0xE2, b'c', b'h', b'i', 0xC1, 0x80, 0xFF, b'e', b'n', b'g', 0xC2, 0xC1, 0xFF,
0xFF, 0xFE,
];
let d = round_trip(&bytes);
assert_eq!(d.services[0].char_set, ChinaCaptionCharSet::Gb2312);
assert!(!d.services[0].wide_aspect_ratio);
assert_eq!(d.services[1].char_set, ChinaCaptionCharSet::Gb13000_1);
assert_eq!(d.services[1].caption_service_number, 2);
assert_eq!(d.caption_service_pid, Pid::new(0x1FFE));
}
#[test]
fn rejects_bad_tag_length_and_truncation() {
assert!(matches!(
ChinaCaptionServiceDescriptor::parse(&[0x87, 3, 0xE0, 0xE0, 0]),
Err(Error::InvalidDescriptor { tag: 0x87, .. })
));
assert!(matches!(
ChinaCaptionServiceDescriptor::parse(&[TAG, 3, 0xE1, 0xE0, 0]),
Err(Error::InvalidDescriptor { tag: TAG, .. })
));
assert!(matches!(
ChinaCaptionServiceDescriptor::parse(&[TAG, 2, 0xE0, 0xE0]),
Err(Error::BufferTooShort(..))
));
assert!(matches!(
ChinaCaptionServiceDescriptor::parse(&[TAG, 9, 0xE1]),
Err(Error::BufferTooShort(..))
));
}
#[test]
fn empty_service_list_round_trips() {
let bytes = [TAG, 3, 0xE0, 0xE1, 0x23];
let d = round_trip(&bytes);
assert!(d.services.is_empty());
assert_eq!(d.caption_service_pid, Pid::new(0x0123));
}
#[test]
fn serialize_rejects_overwide_fields() {
let svc =
ChinaCaptionService::new(LangCode(*b"chi"), 64, false, ChinaCaptionCharSet::Gb2312);
let d = ChinaCaptionServiceDescriptor::new(vec![svc], Pid::new(0x100));
assert!(matches!(
&(d.serialize_into(&mut [0u8; 16])),
Err(crate::error::Error::FieldOverflow(_))
));
let svc = ChinaCaptionService::new(
LangCode(*b"chi"),
1,
false,
ChinaCaptionCharSet::Reserved(0x7F),
);
let d = ChinaCaptionServiceDescriptor::new(vec![svc; 1], Pid::new(0x100));
let mut bad = d.clone();
bad.reserved_pid = 8;
assert!(matches!(
&(bad.serialize_into(&mut [0u8; 16])),
Err(crate::error::Error::FieldOverflow(_))
));
let many = ChinaCaptionServiceDescriptor::new(vec![svc; 32], Pid::new(0x100));
assert!(matches!(
&(many.serialize_into(&mut [0u8; 256])),
Err(crate::error::Error::ValueOutOfRange { .. })
));
}
#[test]
fn char_set_is_lossless_and_labelled() {
for v in 0u8..64 {
assert_eq!(ChinaCaptionCharSet::from_u8(v).to_u8(), v);
}
assert_eq!(ChinaCaptionCharSet::Gb18030.name(), "GB 18030-2005");
assert_eq!(
ChinaCaptionCharSet::Reserved(9).to_string(),
"reserved(0x09)"
);
}
#[test]
fn built_defaults_are_all_ones() {
let d = ChinaCaptionServiceDescriptor::new(
vec![ChinaCaptionService::new(
LangCode(*b"chi"),
1,
true,
ChinaCaptionCharSet::Gb18030,
)],
Pid::new(0x100),
);
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(
out,
[TAG, 9, 0xE1, b'c', b'h', b'i', 0xC1, 0xC2, 0xFF, 0xE1, 0x00]
);
}
}