use crate::error::{Error, Result};
use crate::objects;
use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
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: u16 = 0x1FFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CicamMultistreamCapability {
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 body = objects::parse_apdu_header(
bytes,
tag::CICAM_MULTISTREAM_CAPABILITY,
"CICAM_multistream_capability",
)?;
if body.len() < CAPABILITY_BODY {
return Err(Error::BufferTooShort {
need: CAPABILITY_BODY,
have: body.len(),
what: "CICAM_multistream_capability",
});
}
crate::objects::reject_trailing_body(
body,
CAPABILITY_BODY,
"CICAM_multistream_capability",
)?;
Ok(Self {
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)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos =
objects::write_apdu_header(tag::CICAM_MULTISTREAM_CAPABILITY, CAPABILITY_BODY, 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: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PidSelectReq {
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 body = objects::parse_apdu_header(bytes, tag::PID_SELECT_REQ, "PID_select_req")?;
if body.len() < 2 {
return Err(Error::BufferTooShort {
need: 2,
have: body.len(),
what: "PID_select_req",
});
}
let lts_id = body[0];
let num_pid = body[1] as usize;
let loop_bytes = &body[2..];
if loop_bytes.len() < num_pid * PID_ENTRY_LEN {
return Err(Error::BufferTooShort {
need: num_pid * PID_ENTRY_LEN,
have: loop_bytes.len(),
what: "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 = u16::from_be_bytes([chunk[0], chunk[1]]) & PID_MASK;
pids.push(PidSelectRequest {
critical_for_descrambling: critical,
pid,
});
}
Ok(Self { 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)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::fit_bits(self.pids.len() as u64, 8, "PID_select_req num_PID")?;
for entry in &self.pids {
objects::fit_bits(u64::from(entry.pid), 13, "PID_select_req PID")?;
}
let body_len = 2 + self.pids.len() * PID_ENTRY_LEN;
let mut pos = objects::write_apdu_header(tag::PID_SELECT_REQ, body_len, 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.pid >> 8) as u8;
if entry.critical_for_descrambling {
hi |= CRITICAL_FLAG_BIT;
}
buf[pos] = hi;
buf[pos + 1] = entry.pid 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: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PidSelectReply {
pub lts_id: u8,
pub pid_selection: bool,
pub pids: Vec<PidSelectedEntry>,
}
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 body = objects::parse_apdu_header(bytes, tag::PID_SELECT_REPLY, "PID_select_reply")?;
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "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..];
if loop_bytes.len() < num_pid * PID_ENTRY_LEN {
return Err(Error::BufferTooShort {
need: num_pid * PID_ENTRY_LEN,
have: loop_bytes.len(),
what: "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 = u16::from_be_bytes([chunk[0], chunk[1]]) & PID_MASK;
pids.push(PidSelectedEntry {
pid_selected: selected,
pid,
});
}
Ok(Self {
lts_id,
pid_selection,
pids,
})
}
}
impl Serialize for PidSelectReply {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(3 + self.pids.len() * PID_ENTRY_LEN)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::fit_bits(self.pids.len() as u64, 8, "PID_select_reply num_PID")?;
for entry in &self.pids {
objects::fit_bits(u64::from(entry.pid), 13, "PID_select_reply PID")?;
}
let body_len = 3 + self.pids.len() * PID_ENTRY_LEN;
let mut pos = objects::write_apdu_header(tag::PID_SELECT_REPLY, body_len, buf)?;
buf[pos] = self.lts_id;
buf[pos + 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.pid >> 8) as u8;
if entry.pid_selected {
hi |= PID_SELECTED_FLAG_BIT;
}
buf[pos] = hi;
buf[pos + 1] = entry.pid 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 {
max_local_ts: 0x04,
max_descramblers: 0x0102,
};
let bytes = cap.to_bytes();
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.to_bytes());
}
#[test]
fn pid_select_req_round_trips_and_bites() {
let req = PidSelectReq {
lts_id: 0x07,
pids: alloc::vec![
PidSelectRequest {
critical_for_descrambling: true,
pid: 0x0123,
},
PidSelectRequest {
critical_for_descrambling: false,
pid: 0x1FFE,
},
],
};
let bytes = req.to_bytes();
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.to_bytes());
assert_eq!(other.to_bytes()[6], 0x01);
}
#[test]
fn pid_select_req_empty_loop() {
let req = PidSelectReq {
lts_id: 0x00,
pids: Vec::new(),
};
let bytes = req.to_bytes();
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 {
lts_id: 0x05,
pid_selection: true,
pids: alloc::vec![
PidSelectedEntry {
pid_selected: true,
pid: 0x0064,
},
PidSelectedEntry {
pid_selected: false,
pid: 0x00C8,
},
],
};
let bytes = reply.to_bytes();
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.to_bytes()[5], 0x00);
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn pid_select_reply_whole_ts() {
let reply = PidSelectReply {
lts_id: 0x01,
pid_selection: false,
pids: Vec::new(),
};
let bytes = reply.to_bytes();
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 {
max_local_ts: 1,
max_descramblers: 1,
}
.to_bytes();
assert!(matches!(
MultistreamApdu::parse(&cap).unwrap(),
MultistreamApdu::CicamMultistreamCapability(_)
));
let reply = PidSelectReply {
lts_id: 0,
pid_selection: false,
pids: Vec::new(),
}
.to_bytes();
let parsed = MultistreamApdu::parse(&reply).unwrap();
assert!(matches!(parsed, MultistreamApdu::PidSelectReply(_)));
assert_eq!(parsed.to_bytes(), reply);
}
#[test]
fn oversized_req_pid_is_rejected_not_wrapped() {
let req = PidSelectReq {
lts_id: 0,
pids: alloc::vec![PidSelectRequest {
critical_for_descrambling: false,
pid: 0x2064,
}],
};
let mut buf = [0u8; 32];
assert!(matches!(
req.serialize_into(&mut buf),
Err(Error::InvalidObject { .. })
));
}
#[test]
fn max_req_pid_still_serializes_and_round_trips() {
let req = PidSelectReq {
lts_id: 0,
pids: alloc::vec![PidSelectRequest {
critical_for_descrambling: false,
pid: 0x1FFF,
}],
};
let bytes = req.to_bytes();
assert_eq!(PidSelectReq::parse(&bytes).unwrap(), req);
}
#[test]
fn oversized_num_pid_req_is_rejected_not_wrapped() {
let req = PidSelectReq {
lts_id: 0,
pids: (0..256)
.map(|_| PidSelectRequest {
critical_for_descrambling: false,
pid: 0x0100,
})
.collect(),
};
let mut buf = alloc::vec![0u8; 4096];
assert!(matches!(
req.serialize_into(&mut buf),
Err(Error::InvalidObject { .. })
));
}
#[test]
fn oversized_reply_pid_is_rejected_not_wrapped() {
let reply = PidSelectReply {
lts_id: 0,
pid_selection: false,
pids: alloc::vec![PidSelectedEntry {
pid_selected: false,
pid: 0x2064,
}],
};
let mut buf = [0u8; 32];
assert!(matches!(
reply.serialize_into(&mut buf),
Err(Error::InvalidObject { .. })
));
}
}