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_descriptors_only(loop_: &DescriptorLoop<'_>) -> Vec<u8> {
let mut out = Vec::new();
for (tag, body) in loop_.raw_tags() {
if tag == CA_DESCRIPTOR_TAG {
out.push(tag);
out.push(body.len() as u8);
out.extend_from_slice(body);
}
}
out
}
#[must_use]
pub fn build_ca_pmt(
pmt: &PmtSection<'_>,
list_management: CaPmtListManagement,
cmd_id: CaPmtCmdId,
) -> CaPmtBuilt {
let program_ca_descriptors = ca_descriptors_only(&pmt.program_info);
let streams = pmt
.streams
.iter()
.map(|s| BuiltStream {
stream_type: s.stream_type.to_u8(),
elementary_pid: s.elementary_pid,
ca_descriptors: ca_descriptors_only(&s.es_info),
})
.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(),
}
}
#[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 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 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 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(®);
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
}
}