use crate::objects::ca_pmt::{
CaPmt, CaPmtCmdId, CaPmtListManagement, CaPmtStream, CA_DESCRIPTOR_TAG,
};
use alloc::vec::Vec;
use dvb_common::Serialize;
use dvb_si::descriptors::DescriptorLoop;
use dvb_si::tables::pmt::PmtSection;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CaPmtBuilt {
list_management: CaPmtListManagement,
program_number: u16,
version_number: u8,
current_next_indicator: bool,
cmd_id: CaPmtCmdId,
program_ca_descriptors: Vec<u8>,
streams: Vec<BuiltStream>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct BuiltStream {
stream_type: u8,
elementary_pid: u16,
ca_descriptors: Vec<u8>,
}
fn ca_system_id(body: &[u8]) -> Option<u16> {
body.first_chunk::<2>().map(|b| u16::from_be_bytes(*b))
}
fn ca_descriptors_filtered(loop_: &DescriptorLoop<'_>, allowed: Option<&[u16]>) -> Vec<u8> {
let mut out = Vec::new();
for (tag, body) in loop_.raw_tags() {
if tag != CA_DESCRIPTOR_TAG {
continue;
}
if let Some(allow) = allowed {
match ca_system_id(body) {
Some(id) if allow.contains(&id) => {}
_ => continue,
}
}
out.push(tag);
out.push(body.len() as u8);
out.extend_from_slice(body);
}
out
}
fn retain_loop(buf: &mut Vec<u8>, allowed: &[u16]) {
let mut out = Vec::new();
let mut pos = 0;
while pos + 2 <= buf.len() {
let end = pos + 2 + buf[pos + 1] as usize;
if end > buf.len() {
break;
}
if ca_system_id(&buf[pos + 2..end]).is_some_and(|id| allowed.contains(&id)) {
out.extend_from_slice(&buf[pos..end]);
}
pos = end;
}
*buf = out;
}
#[must_use]
pub fn build_ca_pmt(
pmt: &PmtSection<'_>,
list_management: CaPmtListManagement,
cmd_id: CaPmtCmdId,
) -> CaPmtBuilt {
build(pmt, None, list_management, cmd_id)
}
#[must_use]
pub fn build_ca_pmt_for_caids(
pmt: &PmtSection<'_>,
allowed: &[u16],
list_management: CaPmtListManagement,
cmd_id: CaPmtCmdId,
) -> CaPmtBuilt {
build(pmt, Some(allowed), list_management, cmd_id)
}
fn build(
pmt: &PmtSection<'_>,
allowed: Option<&[u16]>,
list_management: CaPmtListManagement,
cmd_id: CaPmtCmdId,
) -> CaPmtBuilt {
let program_ca_descriptors = ca_descriptors_filtered(&pmt.program_info, allowed);
let streams = pmt
.streams
.iter()
.map(|s| BuiltStream {
stream_type: s.stream_type.to_u8(),
elementary_pid: s.elementary_pid,
ca_descriptors: ca_descriptors_filtered(&s.es_info, allowed),
})
.collect();
CaPmtBuilt {
list_management,
program_number: pmt.program_number,
version_number: pmt.version_number,
current_next_indicator: pmt.current_next_indicator,
cmd_id,
program_ca_descriptors,
streams,
}
}
impl CaPmtBuilt {
#[must_use]
pub fn as_ca_pmt(&self) -> CaPmt<'_> {
CaPmt {
list_management: self.list_management,
program_number: self.program_number,
version_number: self.version_number,
current_next_indicator: self.current_next_indicator,
cmd_id: cmd_for(self.cmd_id, &self.program_ca_descriptors),
program_ca_descriptors: &self.program_ca_descriptors,
streams: self
.streams
.iter()
.map(|s| CaPmtStream {
stream_type: s.stream_type,
elementary_pid: s.elementary_pid,
cmd_id: cmd_for(self.cmd_id, &s.ca_descriptors),
ca_descriptors: &s.ca_descriptors,
})
.collect(),
}
}
pub fn retain_caids(&mut self, allowed: &[u16]) {
retain_loop(&mut self.program_ca_descriptors, allowed);
for s in &mut self.streams {
retain_loop(&mut s.ca_descriptors, allowed);
}
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
self.as_ca_pmt().to_bytes()
}
}
fn cmd_for(cmd_id: CaPmtCmdId, descriptors: &[u8]) -> Option<CaPmtCmdId> {
if descriptors.is_empty() {
None
} else {
Some(cmd_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::objects::ca_pmt::CaPmt;
use alloc::vec;
use dvb_common::Parse;
#[test]
fn builds_from_real_pmt_fixture() {
let pmt_bytes = build_test_pmt();
let pmt = PmtSection::parse(&pmt_bytes).expect("valid PMT");
let built = build_ca_pmt(&pmt, CaPmtListManagement::Only, CaPmtCmdId::OkDescrambling);
let bytes = built.to_bytes();
let parsed = CaPmt::parse(&bytes).unwrap();
let view = built.as_ca_pmt();
assert_eq!(parsed, view);
assert!(!parsed.program_ca_descriptors.is_empty());
assert_eq!(parsed.program_ca_descriptors[0], CA_DESCRIPTOR_TAG);
assert_eq!(parsed.cmd_id, Some(CaPmtCmdId::OkDescrambling));
assert_eq!(parsed.streams.len(), 2);
assert!(!parsed.streams[0].ca_descriptors.is_empty());
assert_eq!(parsed.streams[0].cmd_id, Some(CaPmtCmdId::OkDescrambling));
assert!(parsed.streams[1].ca_descriptors.is_empty());
assert_eq!(parsed.streams[1].cmd_id, None);
}
#[test]
fn strips_non_ca_descriptors() {
let pmt_bytes = build_test_pmt();
let pmt = PmtSection::parse(&pmt_bytes).unwrap();
let built = build_ca_pmt(&pmt, CaPmtListManagement::Add, CaPmtCmdId::Query);
let view = built.as_ca_pmt();
let mut pos = 0;
let d = view.program_ca_descriptors;
while pos < d.len() {
assert_eq!(d[pos], CA_DESCRIPTOR_TAG);
pos += 2 + d[pos + 1] as usize;
}
}
fn caids(buf: &[u8]) -> Vec<u16> {
let mut ids = Vec::new();
let mut pos = 0;
while pos + 2 <= buf.len() {
let end = pos + 2 + buf[pos + 1] as usize;
ids.push(u16::from_be_bytes([buf[pos + 2], buf[pos + 3]]));
pos = end;
}
ids
}
#[test]
fn for_caids_keeps_only_allowed_system_ids() {
let pmt_bytes = build_test_pmt();
let pmt = PmtSection::parse(&pmt_bytes).unwrap();
let built = build_ca_pmt_for_caids(
&pmt,
&[0x0500],
CaPmtListManagement::Only,
CaPmtCmdId::OkDescrambling,
);
assert_eq!(caids(&built.program_ca_descriptors), vec![0x0500]);
let view = built.as_ca_pmt();
assert!(!view.streams[0].ca_descriptors.is_empty());
assert!(view.streams[1].ca_descriptors.is_empty());
assert_eq!(CaPmt::parse(&built.to_bytes()).unwrap(), view);
}
#[test]
fn for_caids_empty_allowlist_drops_all_ca() {
let pmt_bytes = build_test_pmt();
let pmt = PmtSection::parse(&pmt_bytes).unwrap();
let built = build_ca_pmt_for_caids(&pmt, &[], CaPmtListManagement::Only, CaPmtCmdId::Query);
assert!(built.program_ca_descriptors.is_empty());
let view = built.as_ca_pmt();
assert_eq!(view.cmd_id, None);
assert!(view.streams.iter().all(|s| s.cmd_id.is_none()));
}
#[test]
fn retain_caids_matches_the_filtering_constructor() {
let pmt_bytes = build_test_pmt();
let pmt = PmtSection::parse(&pmt_bytes).unwrap();
let allow = [0x1800u16];
let mut post = build_ca_pmt(&pmt, CaPmtListManagement::Only, CaPmtCmdId::OkDescrambling);
post.retain_caids(&allow);
let pre = build_ca_pmt_for_caids(
&pmt,
&allow,
CaPmtListManagement::Only,
CaPmtCmdId::OkDescrambling,
);
assert_eq!(post, pre);
assert_eq!(caids(&post.program_ca_descriptors), vec![0x1800]);
}
fn ca_descriptor(ca_system_id: u16, pid: u16) -> [u8; 6] {
[
0x09,
0x04,
(ca_system_id >> 8) as u8,
ca_system_id as u8,
0xE0 | ((pid >> 8) as u8 & 0x1F),
pid as u8,
]
}
fn build_test_pmt() -> Vec<u8> {
let prog_ca = ca_descriptor(0x0500, 0x0100);
let prog_ca2 = ca_descriptor(0x1800, 0x0110);
let reg = [0x05u8, 0x04, b'H', b'D', b'M', b'V'];
let mut program_info = Vec::new();
program_info.extend_from_slice(&prog_ca);
program_info.extend_from_slice(&prog_ca2);
program_info.extend_from_slice(®);
let es0_ca = ca_descriptor(0x0500, 0x0101);
let lang = [0x0Au8, 0x04, b'e', b'n', b'g', 0x00];
let mut body = Vec::new();
body.push(0x02);
body.push(0);
body.push(0);
body.extend_from_slice(&[0x00, 0x01]);
body.push(0xC3);
body.push(0x00);
body.push(0x00);
body.push(0xE0 | 0x02);
body.push(0x00);
let pil = program_info.len();
body.push(0xF0 | ((pil >> 8) as u8 & 0x0F));
body.push(pil as u8);
body.extend_from_slice(&program_info);
body.push(0x02); body.push(0xE0 | 0x02); body.push(0x00);
body.push(0xF0 | ((es0_ca.len() >> 8) as u8 & 0x0F));
body.push(es0_ca.len() as u8);
body.extend_from_slice(&es0_ca);
body.push(0x03);
body.push(0xE0 | 0x02); body.push(0x01);
body.push(0xF0 | ((lang.len() >> 8) as u8 & 0x0F));
body.push(lang.len() as u8);
body.extend_from_slice(&lang);
let section_length = body.len() - 3 + 4;
body[1] = 0xB0 | ((section_length >> 8) as u8 & 0x0F);
body[2] = section_length as u8;
let crc = dvb_common::crc32_mpeg2::compute(&body);
body.extend_from_slice(&crc.to_be_bytes());
body
}
}