use crate::error::{Error, Result};
use crate::objects;
use crate::reserved::ReservedZero;
use alloc::vec::Vec;
use broadcast_common::len::need_in;
use broadcast_common::{Parse, Serialize};
use mpeg_ts::pid::Pid;
pub mod tag {
use crate::tag::ApduTag;
pub const CICAM_MULTISTREAM_CAPABILITY: ApduTag = ApduTag::from_bytes(0x9F, 0x92, 0x00);
pub const PID_SELECT_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x92, 0x01);
pub const PID_SELECT_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x92, 0x02);
}
pub const NULL_PID: Pid = Pid::new(0x1FFF);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CicamMultistreamCapability {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub max_local_ts: u8,
pub max_descramblers: u16,
}
const CAPABILITY_BODY: usize = 1 + 2;
impl<'a> Parse<'a> for CicamMultistreamCapability {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (length_form, body) = objects::parse_apdu_header(
bytes,
tag::CICAM_MULTISTREAM_CAPABILITY,
"CICAM_multistream_capability",
)?;
need_in(body, CAPABILITY_BODY, "CICAM_multistream_capability")?;
crate::objects::reject_trailing_body(
body,
CAPABILITY_BODY,
"CICAM_multistream_capability",
)?;
Ok(Self {
length_form,
max_local_ts: body[0],
max_descramblers: u16::from_be_bytes([body[1], body[2]]),
})
}
}
impl Serialize for CicamMultistreamCapability {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(CAPABILITY_BODY, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(
tag::CICAM_MULTISTREAM_CAPABILITY,
CAPABILITY_BODY,
self.length_form,
buf,
)?;
buf[pos] = self.max_local_ts;
buf[pos + 1..pos + 3].copy_from_slice(&self.max_descramblers.to_be_bytes());
Ok(pos + CAPABILITY_BODY)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PidSelectRequest {
pub critical_for_descrambling: bool,
pub pid: Pid,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved: ReservedZero<2>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PidSelectReq {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub lts_id: u8,
pub pids: Vec<PidSelectRequest>,
}
const PID_ENTRY_LEN: usize = 2;
const PID_MASK: u16 = 0x1FFF;
const CRITICAL_FLAG_BIT: u8 = 0x20;
impl<'a> Parse<'a> for PidSelectReq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (length_form, body) =
objects::parse_apdu_header(bytes, tag::PID_SELECT_REQ, "PID_select_req")?;
need_in(body, 2, "PID_select_req")?;
let lts_id = body[0];
let num_pid = body[1] as usize;
let loop_bytes = &body[2..];
need_in(loop_bytes, num_pid * PID_ENTRY_LEN, "PID_select_req loop")?;
let mut pids = Vec::with_capacity(num_pid);
for chunk in loop_bytes[..num_pid * PID_ENTRY_LEN].chunks_exact(PID_ENTRY_LEN) {
let critical = chunk[0] & CRITICAL_FLAG_BIT != 0;
let pid = Pid::new(u16::from_be_bytes([chunk[0], chunk[1]]) & PID_MASK);
pids.push(PidSelectRequest {
critical_for_descrambling: critical,
pid,
reserved: ReservedZero::from_bits(chunk[0] >> 6),
});
}
objects::reject_trailing_body(loop_bytes, num_pid * PID_ENTRY_LEN, "PID_select_req")?;
Ok(Self {
length_form,
lts_id,
pids,
})
}
}
impl Serialize for PidSelectReq {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(2 + self.pids.len() * PID_ENTRY_LEN, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::fit_bits(self.pids.len() as u64, 8, "PID_select_req num_PID")?;
let body_len = 2 + self.pids.len() * PID_ENTRY_LEN;
let mut pos =
objects::write_apdu_header(tag::PID_SELECT_REQ, body_len, self.length_form, buf)?;
buf[pos] = self.lts_id;
buf[pos + 1] = self.pids.len() as u8;
pos += 2;
for entry in &self.pids {
let mut hi = (entry.reserved.get() << 6) | (entry.pid.value() >> 8) as u8;
if entry.critical_for_descrambling {
hi |= CRITICAL_FLAG_BIT;
}
buf[pos] = hi;
buf[pos + 1] = entry.pid.value() as u8;
pos += PID_ENTRY_LEN;
}
Ok(pos)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PidSelectedEntry {
pub pid_selected: bool,
pub pid: Pid,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved: ReservedZero<2>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PidSelectReply {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub lts_id: u8,
pub pid_selection: bool,
pub pids: Vec<PidSelectedEntry>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved: ReservedZero<7>,
}
const PID_SELECTED_FLAG_BIT: u8 = 0x20;
const PID_SELECTION_FLAG_BIT: u8 = 0x01;
impl<'a> Parse<'a> for PidSelectReply {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (length_form, body) =
objects::parse_apdu_header(bytes, tag::PID_SELECT_REPLY, "PID_select_reply")?;
need_in(body, 3, "PID_select_reply")?;
let lts_id = body[0];
let pid_selection = body[1] & PID_SELECTION_FLAG_BIT != 0;
let num_pid = body[2] as usize;
let loop_bytes = &body[3..];
need_in(loop_bytes, num_pid * PID_ENTRY_LEN, "PID_select_reply loop")?;
let mut pids = Vec::with_capacity(num_pid);
for chunk in loop_bytes[..num_pid * PID_ENTRY_LEN].chunks_exact(PID_ENTRY_LEN) {
let selected = chunk[0] & PID_SELECTED_FLAG_BIT != 0;
let pid = Pid::new(u16::from_be_bytes([chunk[0], chunk[1]]) & PID_MASK);
pids.push(PidSelectedEntry {
pid_selected: selected,
pid,
reserved: ReservedZero::from_bits(chunk[0] >> 6),
});
}
objects::reject_trailing_body(loop_bytes, num_pid * PID_ENTRY_LEN, "PID_select_reply")?;
Ok(Self {
length_form,
lts_id,
pid_selection,
pids,
reserved: ReservedZero::from_bits(body[1] >> 1),
})
}
}
impl Serialize for PidSelectReply {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(3 + self.pids.len() * PID_ENTRY_LEN, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::fit_bits(self.pids.len() as u64, 8, "PID_select_reply num_PID")?;
let body_len = 3 + self.pids.len() * PID_ENTRY_LEN;
let mut pos =
objects::write_apdu_header(tag::PID_SELECT_REPLY, body_len, self.length_form, buf)?;
buf[pos] = self.lts_id;
buf[pos + 1] = (self.reserved.get() << 1)
| if self.pid_selection {
PID_SELECTION_FLAG_BIT
} else {
0
};
buf[pos + 2] = self.pids.len() as u8;
pos += 3;
for entry in &self.pids {
let mut hi = (entry.reserved.get() << 6) | (entry.pid.value() >> 8) as u8;
if entry.pid_selected {
hi |= PID_SELECTED_FLAG_BIT;
}
buf[pos] = hi;
buf[pos + 1] = entry.pid.value() as u8;
pos += PID_ENTRY_LEN;
}
Ok(pos)
}
}
crate::dispatch::declare_resource_apdus! {
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum MultistreamApdu ("multistream") {
CicamMultistreamCapability(CicamMultistreamCapability) = tag::CICAM_MULTISTREAM_CAPABILITY,
PidSelectReq(PidSelectReq) = tag::PID_SELECT_REQ,
PidSelectReply(PidSelectReply) = tag::PID_SELECT_REPLY,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capability_round_trips_and_bites() {
let cap = CicamMultistreamCapability {
length_form: crate::length::LengthForm::Minimal,
max_local_ts: 0x04,
max_descramblers: 0x0102,
};
let bytes = cap.try_to_bytes().unwrap();
assert_eq!(bytes, [0x9F, 0x92, 0x00, 0x03, 0x04, 0x01, 0x02]);
assert_eq!(CicamMultistreamCapability::parse(&bytes).unwrap(), cap);
let mut other = cap;
other.max_descramblers = 0x0103;
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn pid_select_req_round_trips_and_bites() {
let req = PidSelectReq {
length_form: crate::length::LengthForm::Minimal,
lts_id: 0x07,
pids: alloc::vec![
PidSelectRequest {
reserved: Default::default(),
critical_for_descrambling: true,
pid: Pid::new(0x0123),
},
PidSelectRequest {
reserved: Default::default(),
critical_for_descrambling: false,
pid: Pid::new(0x1FFE),
},
],
};
let bytes = req.try_to_bytes().unwrap();
assert_eq!(
bytes,
[0x9F, 0x92, 0x01, 0x06, 0x07, 0x02, 0x21, 0x23, 0x1F, 0xFE]
);
assert_eq!(PidSelectReq::parse(&bytes).unwrap(), req);
let mut other = req.clone();
other.pids[0].critical_for_descrambling = false;
assert_ne!(bytes, other.try_to_bytes().unwrap());
assert_eq!(other.try_to_bytes().unwrap()[6], 0x01);
}
#[test]
fn pid_select_req_empty_loop() {
let req = PidSelectReq {
length_form: crate::length::LengthForm::Minimal,
lts_id: 0x00,
pids: Vec::new(),
};
let bytes = req.try_to_bytes().unwrap();
assert_eq!(bytes, [0x9F, 0x92, 0x01, 0x02, 0x00, 0x00]);
assert_eq!(PidSelectReq::parse(&bytes).unwrap(), req);
}
#[test]
fn pid_select_reply_round_trips_with_two_entries() {
let reply = PidSelectReply {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
lts_id: 0x05,
pid_selection: true,
pids: alloc::vec![
PidSelectedEntry {
reserved: Default::default(),
pid_selected: true,
pid: Pid::new(0x0064),
},
PidSelectedEntry {
reserved: Default::default(),
pid_selected: false,
pid: Pid::new(0x00C8),
},
],
};
let bytes = reply.try_to_bytes().unwrap();
assert_eq!(
bytes,
[
0x9F, 0x92, 0x02, 0x07, 0x05, 0x01, 0x02, 0x20, 0x64, 0x00, 0xC8
]
);
assert_eq!(PidSelectReply::parse(&bytes).unwrap(), reply);
let mut other = reply.clone();
other.pid_selection = false;
assert_eq!(other.try_to_bytes().unwrap()[5], 0x00);
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn pid_select_reply_whole_ts() {
let reply = PidSelectReply {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
lts_id: 0x01,
pid_selection: false,
pids: Vec::new(),
};
let bytes = reply.try_to_bytes().unwrap();
assert_eq!(bytes, [0x9F, 0x92, 0x02, 0x03, 0x01, 0x00, 0x00]);
assert_eq!(PidSelectReply::parse(&bytes).unwrap(), reply);
}
#[test]
fn dispatch_routes_each_tag() {
let cap = CicamMultistreamCapability {
length_form: crate::length::LengthForm::Minimal,
max_local_ts: 1,
max_descramblers: 1,
}
.try_to_bytes()
.unwrap();
assert!(matches!(
MultistreamApdu::parse(&cap).unwrap(),
MultistreamApdu::CicamMultistreamCapability(_)
));
let reply = PidSelectReply {
reserved: Default::default(),
length_form: crate::length::LengthForm::Minimal,
lts_id: 0,
pid_selection: false,
pids: Vec::new(),
}
.try_to_bytes()
.unwrap();
let parsed = MultistreamApdu::parse(&reply).unwrap();
assert!(matches!(parsed, MultistreamApdu::PidSelectReply(_)));
assert_eq!(parsed.try_to_bytes().unwrap(), reply);
}
#[test]
fn max_req_pid_still_serializes_and_round_trips() {
let req = PidSelectReq {
length_form: crate::length::LengthForm::Minimal,
lts_id: 0,
pids: alloc::vec![PidSelectRequest {
reserved: Default::default(),
critical_for_descrambling: false,
pid: Pid::new(0x1FFF),
}],
};
let bytes = req.try_to_bytes().unwrap();
assert_eq!(PidSelectReq::parse(&bytes).unwrap(), req);
}
#[test]
fn oversized_num_pid_req_is_rejected_not_wrapped() {
let req = PidSelectReq {
length_form: crate::length::LengthForm::Minimal,
lts_id: 0,
pids: (0..256)
.map(|_| PidSelectRequest {
reserved: Default::default(),
critical_for_descrambling: false,
pid: Pid::new(0x0100),
})
.collect(),
};
let mut buf = alloc::vec![0u8; 4096];
assert!(matches!(
req.serialize_into(&mut buf),
Err(Error::InvalidObject { .. })
));
}
#[test]
fn oversized_pids_are_unrepresentable() {
assert_eq!(Pid::try_new(0x2064).unwrap_err().value, 0x2064);
assert!(Pid::try_new(0x1FFF).is_ok());
}
}