use crate::error::{Error, Result};
use crate::objects;
use crate::objects::ca_pmt::{
CaPmtCmdId, CaPmtListManagement, info_block_len, parse_cmd_and_descriptors, write_info_block,
};
use crate::objects::ca_pmt_reply::{CaEnable, decode_enable_byte, encode_enable_byte};
use crate::reserved::ReservedOnes;
use crate::tag::{CA_PMT, CA_PMT_REPLY};
use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
use mpeg_ts::pid::Pid;
pub use crate::objects::ca_pmt::{
CaPmtCmdId as MsCaPmtCmdId, CaPmtListManagement as MsCaPmtListManagement,
};
pub use crate::objects::ca_pmt_reply::CaEnable as MsCaEnable;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MsCaPmtStream<'a> {
pub stream_type: u8,
pub elementary_pid: Pid,
pub cmd_id: Option<CaPmtCmdId>,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub ca_descriptors: &'a [u8],
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_pid: ReservedOnes<3>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_es_info: ReservedOnes<4>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MsCaPmt<'a> {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub lts_id: u8,
pub list_management: CaPmtListManagement,
pub program_number: u16,
pub pmt_pid: Pid,
pub version_number: u8,
pub current_next_indicator: bool,
pub cmd_id: Option<CaPmtCmdId>,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub program_ca_descriptors: &'a [u8],
#[cfg_attr(feature = "serde", serde(borrow))]
pub streams: Vec<MsCaPmtStream<'a>>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_pmt_pid: ReservedOnes<3>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_version: ReservedOnes<2>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_program_info: ReservedOnes<4>,
}
const MS_CA_PMT_PREFIX: usize = 1 + 1 + 2 + 2 + 1 + 2;
const MS_ES_PREFIX: usize = 5;
impl<'a> Parse<'a> for MsCaPmt<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (length_form, body) = crate::objects::parse_apdu_header(bytes, CA_PMT, "ms ca_pmt")?;
if body.len() < MS_CA_PMT_PREFIX {
return Err(Error::BufferTooShort {
need: MS_CA_PMT_PREFIX,
have: body.len(),
what: "ms ca_pmt prefix",
});
}
let lts_id = body[0];
let list_management = CaPmtListManagement::from_u8(body[1]);
let program_number = u16::from_be_bytes([body[2], body[3]]);
let pmt_pid = Pid::new((((body[4] & 0x1F) as u16) << 8) | body[5] as u16);
let reserved_pmt_pid = ReservedOnes::from_bits(body[4] >> 5);
let reserved_version = ReservedOnes::from_bits(body[6] >> 6);
let reserved_program_info = ReservedOnes::from_bits(body[7] >> 4);
let version_number = (body[6] >> 1) & 0x1F;
let current_next_indicator = (body[6] & 0x01) != 0;
let program_info_length = (((body[7] & 0x0F) as usize) << 8) | body[8] as usize;
let mut pos = MS_CA_PMT_PREFIX;
let (cmd_id, program_ca_descriptors) = parse_cmd_and_descriptors(
body,
&mut pos,
program_info_length,
"ms ca_pmt program_info",
)?;
let mut streams = Vec::new();
while pos < body.len() {
if pos + MS_ES_PREFIX > body.len() {
return Err(Error::BufferTooShort {
need: pos + MS_ES_PREFIX,
have: body.len(),
what: "ms ca_pmt ES prefix",
});
}
let stream_type = body[pos];
let reserved_pid = ReservedOnes::from_bits(body[pos + 1] >> 5);
let reserved_es_info = ReservedOnes::from_bits(body[pos + 3] >> 4);
let elementary_pid =
Pid::new((((body[pos + 1] & 0x1F) as u16) << 8) | body[pos + 2] as u16);
let es_info_length = (((body[pos + 3] & 0x0F) as usize) << 8) | body[pos + 4] as usize;
pos += MS_ES_PREFIX;
let (es_cmd, ca_descriptors) =
parse_cmd_and_descriptors(body, &mut pos, es_info_length, "ms ca_pmt ES_info")?;
streams.push(MsCaPmtStream {
stream_type,
elementary_pid,
cmd_id: es_cmd,
ca_descriptors,
reserved_pid,
reserved_es_info,
});
}
Ok(Self {
length_form,
lts_id,
list_management,
program_number,
pmt_pid,
version_number,
current_next_indicator,
cmd_id,
program_ca_descriptors,
streams,
reserved_pmt_pid,
reserved_version,
reserved_program_info,
})
}
}
impl Serialize for MsCaPmt<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
let mut body = MS_CA_PMT_PREFIX + info_block_len(self.cmd_id, self.program_ca_descriptors);
for s in &self.streams {
body += MS_ES_PREFIX + info_block_len(s.cmd_id, s.ca_descriptors);
}
crate::objects::apdu_len(body, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::fit_bits(
u64::from(self.version_number),
5,
"ms ca_pmt version_number",
)?;
let program_info_length = info_block_len(self.cmd_id, self.program_ca_descriptors);
objects::fit_bits(
program_info_length as u64,
12,
"ms ca_pmt program_info_length",
)?;
let mut body = MS_CA_PMT_PREFIX + program_info_length;
for s in &self.streams {
let es_info_length = info_block_len(s.cmd_id, s.ca_descriptors);
objects::fit_bits(es_info_length as u64, 12, "ms ca_pmt ES_info_length")?;
body += MS_ES_PREFIX + es_info_length;
}
let mut pos = crate::objects::write_apdu_header(CA_PMT, body, self.length_form, buf)?;
buf[pos] = self.lts_id;
buf[pos + 1] = self.list_management.to_u8();
buf[pos + 2..pos + 4].copy_from_slice(&self.program_number.to_be_bytes());
buf[pos + 4] = (self.reserved_pmt_pid.get() << 5) | (self.pmt_pid.value() >> 8) as u8;
buf[pos + 5] = self.pmt_pid.value() as u8;
buf[pos + 6] = (self.reserved_version.get() << 6)
| (self.version_number << 1)
| u8::from(self.current_next_indicator);
buf[pos + 7] = (self.reserved_program_info.get() << 4) | (program_info_length >> 8) as u8;
buf[pos + 8] = program_info_length as u8;
pos += MS_CA_PMT_PREFIX;
pos += write_info_block(self.cmd_id, self.program_ca_descriptors, &mut buf[pos..])?;
for s in &self.streams {
let es_info_length = info_block_len(s.cmd_id, s.ca_descriptors);
buf[pos] = s.stream_type;
buf[pos + 1] = (s.reserved_pid.get() << 5) | (s.elementary_pid.value() >> 8) as u8;
buf[pos + 2] = s.elementary_pid.value() as u8;
buf[pos + 3] = (s.reserved_es_info.get() << 4) | (es_info_length >> 8) as u8;
buf[pos + 4] = es_info_length as u8;
pos += MS_ES_PREFIX;
pos += write_info_block(s.cmd_id, s.ca_descriptors, &mut buf[pos..])?;
}
Ok(pos)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MsCaPmtReplyStream {
pub elementary_pid: Pid,
pub ca_enable: Option<CaEnable>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_pid: ReservedOnes<3>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_ca_enable: ReservedOnes<7>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MsCaPmtReply {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub lts_id: u8,
pub program_number: u16,
pub version_number: u8,
pub current_next_indicator: bool,
pub ca_enable: Option<CaEnable>,
pub streams: Vec<MsCaPmtReplyStream>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_version: ReservedOnes<2>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_ca_enable: ReservedOnes<7>,
}
const MS_REPLY_PREFIX: usize = 1 + 2 + 1 + 1;
const MS_REPLY_ES_LEN: usize = 3;
impl<'a> Parse<'a> for MsCaPmtReply {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (length_form, body) =
crate::objects::parse_apdu_header(bytes, CA_PMT_REPLY, "ms ca_pmt_reply")?;
if body.len() < MS_REPLY_PREFIX {
return Err(Error::BufferTooShort {
need: MS_REPLY_PREFIX,
have: body.len(),
what: "ms ca_pmt_reply prefix",
});
}
let lts_id = body[0];
let program_number = u16::from_be_bytes([body[1], body[2]]);
let version_number = (body[3] >> 1) & 0x1F;
let current_next_indicator = (body[3] & 0x01) != 0;
let (ca_enable, reserved_ca_enable) = decode_enable_byte(body[4]);
let mut pos = MS_REPLY_PREFIX;
let mut streams = Vec::new();
while pos < body.len() {
if pos + MS_REPLY_ES_LEN > body.len() {
return Err(Error::BufferTooShort {
need: pos + MS_REPLY_ES_LEN,
have: body.len(),
what: "ms ca_pmt_reply ES",
});
}
let elementary_pid =
Pid::new((((body[pos] & 0x1F) as u16) << 8) | body[pos + 1] as u16);
let (es_enable, es_reserved) = decode_enable_byte(body[pos + 2]);
streams.push(MsCaPmtReplyStream {
elementary_pid,
ca_enable: es_enable,
reserved_pid: ReservedOnes::from_bits(body[pos] >> 5),
reserved_ca_enable: es_reserved,
});
pos += MS_REPLY_ES_LEN;
}
Ok(Self {
length_form,
lts_id,
program_number,
version_number,
current_next_indicator,
ca_enable,
streams,
reserved_version: ReservedOnes::from_bits(body[3] >> 6),
reserved_ca_enable,
})
}
}
impl Serialize for MsCaPmtReply {
type Error = Error;
fn serialized_len(&self) -> usize {
crate::objects::apdu_len(
MS_REPLY_PREFIX + self.streams.len() * MS_REPLY_ES_LEN,
self.length_form,
)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::fit_bits(
u64::from(self.version_number),
5,
"ms ca_pmt_reply version_number",
)?;
let body = MS_REPLY_PREFIX + self.streams.len() * MS_REPLY_ES_LEN;
let mut pos = crate::objects::write_apdu_header(CA_PMT_REPLY, body, self.length_form, buf)?;
buf[pos] = self.lts_id;
buf[pos + 1..pos + 3].copy_from_slice(&self.program_number.to_be_bytes());
buf[pos + 3] = (self.reserved_version.get() << 6)
| (self.version_number << 1)
| u8::from(self.current_next_indicator);
buf[pos + 4] = encode_enable_byte(self.ca_enable, self.reserved_ca_enable);
pos += MS_REPLY_PREFIX;
for s in &self.streams {
buf[pos] = (s.reserved_pid.get() << 5) | (s.elementary_pid.value() >> 8) as u8;
buf[pos + 1] = s.elementary_pid.value() as u8;
buf[pos + 2] = encode_enable_byte(s.ca_enable, s.reserved_ca_enable);
pos += MS_REPLY_ES_LEN;
}
Ok(pos)
}
}
crate::dispatch::declare_resource_apdus! {
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum CaSupportApdu<'a> ("ca_support") {
CaPmt(MsCaPmt<'a>) = CA_PMT,
CaPmtReply(MsCaPmtReply) = CA_PMT_REPLY,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::objects::ca_pmt::CA_DESCRIPTOR_TAG;
fn sample_ca_descriptor(ca_system_id: u16, pid: u16) -> [u8; 6] {
[
CA_DESCRIPTOR_TAG,
0x04,
(ca_system_id >> 8) as u8,
ca_system_id as u8,
0xE0 | ((pid >> 8) as u8 & 0x1F),
pid as u8,
]
}
#[test]
fn ms_ca_pmt_round_trips_and_bites() {
let prog = sample_ca_descriptor(0x1234, 0x0100);
let es_desc = sample_ca_descriptor(0x1234, 0x0101);
let pmt = MsCaPmt {
reserved_program_info: Default::default(),
reserved_version: Default::default(),
reserved_pmt_pid: Default::default(),
length_form: crate::length::LengthForm::Minimal,
lts_id: 0x02,
list_management: CaPmtListManagement::Only,
program_number: 0x0001,
pmt_pid: Pid::new(0x0064),
version_number: 1,
current_next_indicator: true,
cmd_id: Some(CaPmtCmdId::OkDescrambling),
program_ca_descriptors: &prog,
streams: alloc::vec![MsCaPmtStream {
reserved_es_info: Default::default(),
reserved_pid: Default::default(),
stream_type: 0x02,
elementary_pid: Pid::new(0x0200),
cmd_id: Some(CaPmtCmdId::OkDescrambling),
ca_descriptors: &es_desc,
}],
};
let bytes = pmt.try_to_bytes().unwrap();
let pid = 0x0200u16;
let es_hi = 0xE0 | ((pid >> 8) as u8 & 0x1F);
let expected = {
let mut v = alloc::vec![
0x9F, 0x80, 0x32, 0x1C, 0x02, 0x03, 0x00, 0x01, 0xE0, 0x64, 0xC3, 0xF0, 0x07, 0x01, ];
v.extend_from_slice(&prog); v.extend_from_slice(&[0x02, es_hi, 0x00, 0xF0, 0x07, 0x01]); v.extend_from_slice(&es_desc); v
};
assert_eq!(bytes, expected);
assert_eq!(MsCaPmt::parse(&bytes).unwrap(), pmt);
let mut other = pmt.clone();
other.pmt_pid = Pid::new(0x0065);
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn ms_ca_pmt_no_descriptors() {
let pmt = MsCaPmt {
reserved_program_info: Default::default(),
reserved_version: Default::default(),
reserved_pmt_pid: Default::default(),
length_form: crate::length::LengthForm::Minimal,
lts_id: 0x00,
list_management: CaPmtListManagement::Update,
program_number: 0x0009,
pmt_pid: Pid::new(0x1FFF),
version_number: 0,
current_next_indicator: true,
cmd_id: None,
program_ca_descriptors: &[],
streams: Vec::new(),
};
let bytes = pmt.try_to_bytes().unwrap();
assert_eq!(
bytes,
[
0x9F, 0x80, 0x32, 0x09, 0x00, 0x05, 0x00, 0x09, 0xFF, 0xFF, 0xC1, 0xF0, 0x00
]
);
assert_eq!(MsCaPmt::parse(&bytes).unwrap(), pmt);
}
#[test]
fn ms_ca_pmt_reply_round_trips_and_bites() {
let reply = MsCaPmtReply {
reserved_ca_enable: Default::default(),
reserved_version: Default::default(),
length_form: crate::length::LengthForm::Minimal,
lts_id: 0x03,
program_number: 0x0001,
version_number: 1,
current_next_indicator: true,
ca_enable: Some(CaEnable::Possible),
streams: alloc::vec![
MsCaPmtReplyStream {
reserved_ca_enable: Default::default(),
reserved_pid: Default::default(),
elementary_pid: Pid::new(0x0200),
ca_enable: Some(CaEnable::Possible),
},
MsCaPmtReplyStream {
reserved_ca_enable: Default::default(),
reserved_pid: Default::default(),
elementary_pid: Pid::new(0x0201),
ca_enable: None,
},
],
};
let bytes = reply.try_to_bytes().unwrap();
let expected = [
0x9F, 0x80, 0x33, 0x0B, 0x03, 0x00, 0x01, 0xC3, 0x81, 0xE2, 0x00, 0x81, 0xE2, 0x01, 0x7F, ];
assert_eq!(bytes, expected);
assert_eq!(MsCaPmtReply::parse(&bytes).unwrap(), reply);
let mut other = reply.clone();
other.lts_id = 0x04;
assert_ne!(bytes, other.try_to_bytes().unwrap());
assert_eq!(other.try_to_bytes().unwrap()[4], 0x04);
}
#[test]
fn dispatch_helper_routes_by_tag() {
let pmt = MsCaPmt {
reserved_program_info: Default::default(),
reserved_version: Default::default(),
reserved_pmt_pid: Default::default(),
length_form: crate::length::LengthForm::Minimal,
lts_id: 0,
list_management: CaPmtListManagement::Only,
program_number: 1,
pmt_pid: Pid::new(0x64),
version_number: 0,
current_next_indicator: true,
cmd_id: None,
program_ca_descriptors: &[],
streams: Vec::new(),
};
let bytes = pmt.try_to_bytes().unwrap();
let parsed = CaSupportApdu::parse(&bytes).unwrap();
assert!(matches!(parsed, CaSupportApdu::CaPmt(_)));
assert_eq!(parsed.try_to_bytes().unwrap(), bytes);
let reply = MsCaPmtReply {
reserved_ca_enable: Default::default(),
reserved_version: Default::default(),
length_form: crate::length::LengthForm::Minimal,
lts_id: 1,
program_number: 1,
version_number: 0,
current_next_indicator: true,
ca_enable: None,
streams: Vec::new(),
};
let rb = reply.try_to_bytes().unwrap();
assert!(matches!(
CaSupportApdu::parse(&rb).unwrap(),
CaSupportApdu::CaPmtReply(_)
));
}
#[test]
fn max_pmt_pid_still_serializes_and_round_trips() {
let pmt = MsCaPmt {
reserved_program_info: Default::default(),
reserved_version: Default::default(),
reserved_pmt_pid: Default::default(),
length_form: crate::length::LengthForm::Minimal,
lts_id: 0,
list_management: CaPmtListManagement::Only,
program_number: 1,
pmt_pid: Pid::new(0x1FFF),
version_number: 0,
current_next_indicator: true,
cmd_id: None,
program_ca_descriptors: &[],
streams: Vec::new(),
};
let bytes = pmt.try_to_bytes().unwrap();
assert_eq!(MsCaPmt::parse(&bytes).unwrap(), pmt);
}
#[test]
fn oversized_pids_are_unrepresentable() {
assert!(Pid::try_new(0x2064).is_err());
assert!(Pid::try_new(0x1FFF).is_ok());
}
}