1use crate::error::{Error, Result};
29use crate::objects::ca_pmt::{CaPmtCmdId, CaPmtListManagement};
30use crate::objects::ca_pmt_reply::CaEnable;
31use crate::tag::{ApduTag, CA_PMT, CA_PMT_REPLY};
32use alloc::vec::Vec;
33use broadcast_common::{Parse, Serialize};
34
35pub use crate::objects::ca_pmt::{
37 CaPmtCmdId as MsCaPmtCmdId, CaPmtListManagement as MsCaPmtListManagement,
38};
39pub use crate::objects::ca_pmt_reply::CaEnable as MsCaEnable;
40
41#[derive(Debug, Clone, PartialEq, Eq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize))]
48pub struct MsCaPmtStream<'a> {
49 pub stream_type: u8,
51 pub elementary_pid: u16,
53 pub cmd_id: Option<CaPmtCmdId>,
55 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
57 pub ca_descriptors: &'a [u8],
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize))]
64pub struct MsCaPmt<'a> {
65 pub lts_id: u8,
67 pub list_management: CaPmtListManagement,
69 pub program_number: u16,
71 pub pmt_pid: u16,
73 pub version_number: u8,
75 pub current_next_indicator: bool,
77 pub cmd_id: Option<CaPmtCmdId>,
79 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
81 pub program_ca_descriptors: &'a [u8],
82 #[cfg_attr(feature = "serde", serde(borrow))]
84 pub streams: Vec<MsCaPmtStream<'a>>,
85}
86
87const MS_CA_PMT_PREFIX: usize = 1 + 1 + 2 + 2 + 1 + 2;
90const MS_ES_PREFIX: usize = 5;
92
93fn info_block_len(cmd_id: Option<CaPmtCmdId>, descriptors: &[u8]) -> usize {
94 if cmd_id.is_some() || !descriptors.is_empty() {
95 1 + descriptors.len()
96 } else {
97 0
98 }
99}
100
101fn parse_cmd_and_descriptors<'a>(
102 body: &'a [u8],
103 pos: &mut usize,
104 info_length: usize,
105 what: &'static str,
106) -> Result<(Option<CaPmtCmdId>, &'a [u8])> {
107 if info_length == 0 {
108 return Ok((None, &body[..0]));
109 }
110 let end = *pos + info_length;
111 if end > body.len() {
112 return Err(Error::LengthMismatch {
113 what,
114 declared: info_length,
115 actual: body.len().saturating_sub(*pos),
116 });
117 }
118 let cmd_id = CaPmtCmdId::from_u8(body[*pos]);
119 let descriptors = &body[*pos + 1..end];
120 *pos = end;
121 Ok((Some(cmd_id), descriptors))
122}
123
124fn write_info_block(
125 cmd_id: Option<CaPmtCmdId>,
126 descriptors: &[u8],
127 buf: &mut [u8],
128) -> Result<usize> {
129 let len = info_block_len(cmd_id, descriptors);
130 if len == 0 {
131 return Ok(0);
132 }
133 if buf.len() < len {
134 return Err(Error::OutputBufferTooSmall {
135 need: len,
136 have: buf.len(),
137 });
138 }
139 buf[0] = cmd_id.unwrap_or(CaPmtCmdId::OkDescrambling).to_u8();
140 buf[1..len].copy_from_slice(descriptors);
141 Ok(len)
142}
143
144impl<'a> Parse<'a> for MsCaPmt<'a> {
145 type Error = Error;
146 fn parse(bytes: &'a [u8]) -> Result<Self> {
147 let body = crate::objects::parse_apdu_header(bytes, CA_PMT, "ms ca_pmt")?;
148 if body.len() < MS_CA_PMT_PREFIX {
149 return Err(Error::BufferTooShort {
150 need: MS_CA_PMT_PREFIX,
151 have: body.len(),
152 what: "ms ca_pmt prefix",
153 });
154 }
155 let lts_id = body[0];
156 let list_management = CaPmtListManagement::from_u8(body[1]);
157 let program_number = u16::from_be_bytes([body[2], body[3]]);
158 let pmt_pid = (((body[4] & 0x1F) as u16) << 8) | body[5] as u16;
160 let version_number = (body[6] >> 1) & 0x1F;
162 let current_next_indicator = (body[6] & 0x01) != 0;
163 let program_info_length = (((body[7] & 0x0F) as usize) << 8) | body[8] as usize;
165
166 let mut pos = MS_CA_PMT_PREFIX;
167 let (cmd_id, program_ca_descriptors) = parse_cmd_and_descriptors(
168 body,
169 &mut pos,
170 program_info_length,
171 "ms ca_pmt program_info",
172 )?;
173
174 let mut streams = Vec::new();
175 while pos < body.len() {
176 if pos + MS_ES_PREFIX > body.len() {
177 return Err(Error::BufferTooShort {
178 need: pos + MS_ES_PREFIX,
179 have: body.len(),
180 what: "ms ca_pmt ES prefix",
181 });
182 }
183 let stream_type = body[pos];
184 let elementary_pid = (((body[pos + 1] & 0x1F) as u16) << 8) | body[pos + 2] as u16;
185 let es_info_length = (((body[pos + 3] & 0x0F) as usize) << 8) | body[pos + 4] as usize;
186 pos += MS_ES_PREFIX;
187 let (es_cmd, ca_descriptors) =
188 parse_cmd_and_descriptors(body, &mut pos, es_info_length, "ms ca_pmt ES_info")?;
189 streams.push(MsCaPmtStream {
190 stream_type,
191 elementary_pid,
192 cmd_id: es_cmd,
193 ca_descriptors,
194 });
195 }
196
197 Ok(Self {
198 lts_id,
199 list_management,
200 program_number,
201 pmt_pid,
202 version_number,
203 current_next_indicator,
204 cmd_id,
205 program_ca_descriptors,
206 streams,
207 })
208 }
209}
210
211impl Serialize for MsCaPmt<'_> {
212 type Error = Error;
213 fn serialized_len(&self) -> usize {
214 let mut body = MS_CA_PMT_PREFIX + info_block_len(self.cmd_id, self.program_ca_descriptors);
215 for s in &self.streams {
216 body += MS_ES_PREFIX + info_block_len(s.cmd_id, s.ca_descriptors);
217 }
218 crate::objects::apdu_len(body)
219 }
220 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
221 let program_info_length = info_block_len(self.cmd_id, self.program_ca_descriptors);
222 let mut body = MS_CA_PMT_PREFIX + program_info_length;
223 for s in &self.streams {
224 body += MS_ES_PREFIX + info_block_len(s.cmd_id, s.ca_descriptors);
225 }
226 let mut pos = crate::objects::write_apdu_header(CA_PMT, body, buf)?;
227 buf[pos] = self.lts_id;
228 buf[pos + 1] = self.list_management.to_u8();
229 buf[pos + 2..pos + 4].copy_from_slice(&self.program_number.to_be_bytes());
230 buf[pos + 4] = 0xE0 | ((self.pmt_pid >> 8) as u8 & 0x1F);
232 buf[pos + 5] = self.pmt_pid as u8;
233 buf[pos + 6] =
235 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
236 buf[pos + 7] = 0xF0 | ((program_info_length >> 8) as u8 & 0x0F);
238 buf[pos + 8] = program_info_length as u8;
239 pos += MS_CA_PMT_PREFIX;
240 pos += write_info_block(self.cmd_id, self.program_ca_descriptors, &mut buf[pos..])?;
241
242 for s in &self.streams {
243 let es_info_length = info_block_len(s.cmd_id, s.ca_descriptors);
244 buf[pos] = s.stream_type;
245 buf[pos + 1] = 0xE0 | ((s.elementary_pid >> 8) as u8 & 0x1F);
246 buf[pos + 2] = s.elementary_pid as u8;
247 buf[pos + 3] = 0xF0 | ((es_info_length >> 8) as u8 & 0x0F);
248 buf[pos + 4] = es_info_length as u8;
249 pos += MS_ES_PREFIX;
250 pos += write_info_block(s.cmd_id, s.ca_descriptors, &mut buf[pos..])?;
251 }
252 Ok(pos)
253 }
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
262#[cfg_attr(feature = "serde", derive(serde::Serialize))]
263pub struct MsCaPmtReplyStream {
264 pub elementary_pid: u16,
266 pub ca_enable: Option<CaEnable>,
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
273#[cfg_attr(feature = "serde", derive(serde::Serialize))]
274pub struct MsCaPmtReply {
275 pub lts_id: u8,
277 pub program_number: u16,
279 pub version_number: u8,
281 pub current_next_indicator: bool,
283 pub ca_enable: Option<CaEnable>,
286 pub streams: Vec<MsCaPmtReplyStream>,
288}
289
290const MS_REPLY_PREFIX: usize = 1 + 2 + 1 + 1;
292const MS_REPLY_ES_LEN: usize = 3; fn encode_enable_byte(enable: Option<CaEnable>) -> u8 {
296 match enable {
297 Some(e) => 0x80 | (e.to_u8() & 0x7F),
298 None => 0x7F,
299 }
300}
301
302impl<'a> Parse<'a> for MsCaPmtReply {
303 type Error = Error;
304 fn parse(bytes: &'a [u8]) -> Result<Self> {
305 let body = crate::objects::parse_apdu_header(bytes, CA_PMT_REPLY, "ms ca_pmt_reply")?;
306 if body.len() < MS_REPLY_PREFIX {
307 return Err(Error::BufferTooShort {
308 need: MS_REPLY_PREFIX,
309 have: body.len(),
310 what: "ms ca_pmt_reply prefix",
311 });
312 }
313 let lts_id = body[0];
314 let program_number = u16::from_be_bytes([body[1], body[2]]);
315 let version_number = (body[3] >> 1) & 0x1F;
316 let current_next_indicator = (body[3] & 0x01) != 0;
317 let ca_enable_flag = (body[4] & 0x80) != 0;
318 let ca_enable = if ca_enable_flag {
319 Some(CaEnable::from_u8(body[4] & 0x7F))
320 } else {
321 None
322 };
323
324 let mut pos = MS_REPLY_PREFIX;
325 let mut streams = Vec::new();
326 while pos < body.len() {
327 if pos + MS_REPLY_ES_LEN > body.len() {
328 return Err(Error::BufferTooShort {
329 need: pos + MS_REPLY_ES_LEN,
330 have: body.len(),
331 what: "ms ca_pmt_reply ES",
332 });
333 }
334 let elementary_pid = (((body[pos] & 0x1F) as u16) << 8) | body[pos + 1] as u16;
335 let es_flag = (body[pos + 2] & 0x80) != 0;
336 let es_enable = if es_flag {
337 Some(CaEnable::from_u8(body[pos + 2] & 0x7F))
338 } else {
339 None
340 };
341 streams.push(MsCaPmtReplyStream {
342 elementary_pid,
343 ca_enable: es_enable,
344 });
345 pos += MS_REPLY_ES_LEN;
346 }
347
348 Ok(Self {
349 lts_id,
350 program_number,
351 version_number,
352 current_next_indicator,
353 ca_enable,
354 streams,
355 })
356 }
357}
358
359impl Serialize for MsCaPmtReply {
360 type Error = Error;
361 fn serialized_len(&self) -> usize {
362 crate::objects::apdu_len(MS_REPLY_PREFIX + self.streams.len() * MS_REPLY_ES_LEN)
363 }
364 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
365 let body = MS_REPLY_PREFIX + self.streams.len() * MS_REPLY_ES_LEN;
366 let mut pos = crate::objects::write_apdu_header(CA_PMT_REPLY, body, buf)?;
367 buf[pos] = self.lts_id;
368 buf[pos + 1..pos + 3].copy_from_slice(&self.program_number.to_be_bytes());
369 buf[pos + 3] =
371 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
372 buf[pos + 4] = encode_enable_byte(self.ca_enable);
373 pos += MS_REPLY_PREFIX;
374 for s in &self.streams {
375 buf[pos] = 0xE0 | ((s.elementary_pid >> 8) as u8 & 0x1F);
376 buf[pos + 1] = s.elementary_pid as u8;
377 buf[pos + 2] = encode_enable_byte(s.ca_enable);
378 pos += MS_REPLY_ES_LEN;
379 }
380 Ok(pos)
381 }
382}
383
384#[derive(Debug, Clone, PartialEq, Eq)]
395#[cfg_attr(feature = "serde", derive(serde::Serialize))]
396#[non_exhaustive]
397pub enum CaSupportApdu<'a> {
398 CaPmt(MsCaPmt<'a>),
400 CaPmtReply(MsCaPmtReply),
402}
403
404impl<'a> CaSupportApdu<'a> {
405 pub fn parse(body: &'a [u8]) -> Result<Self> {
407 if body.len() < 3 {
408 return Err(Error::BufferTooShort {
409 need: 3,
410 have: body.len(),
411 what: "ca_support apdu_tag",
412 });
413 }
414 let t = ApduTag::from_bytes(body[0], body[1], body[2]);
415 match t {
416 CA_PMT => Ok(Self::CaPmt(MsCaPmt::parse(body)?)),
417 CA_PMT_REPLY => Ok(Self::CaPmtReply(MsCaPmtReply::parse(body)?)),
418 _ => Err(Error::UnexpectedApduTag {
419 got: t.as_u24(),
420 expected: CA_PMT.as_u24(),
421 what: "ca_support",
422 }),
423 }
424 }
425}
426
427impl Serialize for CaSupportApdu<'_> {
428 type Error = Error;
429 fn serialized_len(&self) -> usize {
430 match self {
431 Self::CaPmt(o) => o.serialized_len(),
432 Self::CaPmtReply(o) => o.serialized_len(),
433 }
434 }
435 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
436 match self {
437 Self::CaPmt(o) => o.serialize_into(buf),
438 Self::CaPmtReply(o) => o.serialize_into(buf),
439 }
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use crate::objects::ca_pmt::CA_DESCRIPTOR_TAG;
447
448 fn sample_ca_descriptor(ca_system_id: u16, pid: u16) -> [u8; 6] {
449 [
450 CA_DESCRIPTOR_TAG,
451 0x04,
452 (ca_system_id >> 8) as u8,
453 ca_system_id as u8,
454 0xE0 | ((pid >> 8) as u8 & 0x1F),
455 pid as u8,
456 ]
457 }
458
459 #[test]
460 fn ms_ca_pmt_round_trips_and_bites() {
461 let prog = sample_ca_descriptor(0x1234, 0x0100);
462 let es_desc = sample_ca_descriptor(0x1234, 0x0101);
463 let pmt = MsCaPmt {
464 lts_id: 0x02,
465 list_management: CaPmtListManagement::Only,
466 program_number: 0x0001,
467 pmt_pid: 0x0064,
468 version_number: 1,
469 current_next_indicator: true,
470 cmd_id: Some(CaPmtCmdId::OkDescrambling),
471 program_ca_descriptors: &prog,
472 streams: alloc::vec![MsCaPmtStream {
473 stream_type: 0x02,
474 elementary_pid: 0x0200,
475 cmd_id: Some(CaPmtCmdId::OkDescrambling),
476 ca_descriptors: &es_desc,
477 }],
478 };
479 let bytes = pmt.to_bytes();
480 let pid = 0x0200u16;
486 let es_hi = 0xE0 | ((pid >> 8) as u8 & 0x1F);
487 let expected = {
488 let mut v = alloc::vec![
489 0x9F, 0x80, 0x32, 0x1C, 0x02, 0x03, 0x00, 0x01, 0xE0, 0x64, 0xC3, 0xF0, 0x07, 0x01, ];
499 v.extend_from_slice(&prog); v.extend_from_slice(&[0x02, es_hi, 0x00, 0xF0, 0x07, 0x01]); v.extend_from_slice(&es_desc); v
503 };
504 assert_eq!(bytes, expected);
505 assert_eq!(MsCaPmt::parse(&bytes).unwrap(), pmt);
506 let mut other = pmt.clone();
508 other.pmt_pid = 0x0065;
509 assert_ne!(bytes, other.to_bytes());
510 }
511
512 #[test]
513 fn ms_ca_pmt_no_descriptors() {
514 let pmt = MsCaPmt {
515 lts_id: 0x00,
516 list_management: CaPmtListManagement::Update,
517 program_number: 0x0009,
518 pmt_pid: 0x1FFF,
519 version_number: 0,
520 current_next_indicator: true,
521 cmd_id: None,
522 program_ca_descriptors: &[],
523 streams: Vec::new(),
524 };
525 let bytes = pmt.to_bytes();
526 assert_eq!(
528 bytes,
529 [
530 0x9F, 0x80, 0x32, 0x09, 0x00, 0x05, 0x00, 0x09, 0xFF, 0xFF, 0xC1, 0xF0, 0x00
531 ]
532 );
533 assert_eq!(MsCaPmt::parse(&bytes).unwrap(), pmt);
534 }
535
536 #[test]
537 fn ms_ca_pmt_reply_round_trips_and_bites() {
538 let reply = MsCaPmtReply {
539 lts_id: 0x03,
540 program_number: 0x0001,
541 version_number: 1,
542 current_next_indicator: true,
543 ca_enable: Some(CaEnable::Possible),
544 streams: alloc::vec![
545 MsCaPmtReplyStream {
546 elementary_pid: 0x0200,
547 ca_enable: Some(CaEnable::Possible),
548 },
549 MsCaPmtReplyStream {
550 elementary_pid: 0x0201,
551 ca_enable: None,
552 },
553 ],
554 };
555 let bytes = reply.to_bytes();
556 let expected = [
557 0x9F, 0x80, 0x33, 0x0B, 0x03, 0x00, 0x01, 0xC3, 0x81, 0xE2, 0x00, 0x81, 0xE2, 0x01, 0x7F, ];
566 assert_eq!(bytes, expected);
567 assert_eq!(MsCaPmtReply::parse(&bytes).unwrap(), reply);
568 let mut other = reply.clone();
570 other.lts_id = 0x04;
571 assert_ne!(bytes, other.to_bytes());
572 assert_eq!(other.to_bytes()[4], 0x04);
573 }
574
575 #[test]
576 fn dispatch_helper_routes_by_tag() {
577 let pmt = MsCaPmt {
578 lts_id: 0,
579 list_management: CaPmtListManagement::Only,
580 program_number: 1,
581 pmt_pid: 0x64,
582 version_number: 0,
583 current_next_indicator: true,
584 cmd_id: None,
585 program_ca_descriptors: &[],
586 streams: Vec::new(),
587 };
588 let bytes = pmt.to_bytes();
589 let parsed = CaSupportApdu::parse(&bytes).unwrap();
590 assert!(matches!(parsed, CaSupportApdu::CaPmt(_)));
591 assert_eq!(parsed.to_bytes(), bytes);
592
593 let reply = MsCaPmtReply {
594 lts_id: 1,
595 program_number: 1,
596 version_number: 0,
597 current_next_indicator: true,
598 ca_enable: None,
599 streams: Vec::new(),
600 };
601 let rb = reply.to_bytes();
602 assert!(matches!(
603 CaSupportApdu::parse(&rb).unwrap(),
604 CaSupportApdu::CaPmtReply(_)
605 ));
606 }
607}