1use crate::error::{Error, Result};
13use crate::tag::{self, ApduTag};
14use crate::traits::ApduDef;
15use alloc::vec::Vec;
16use dvb_common::{Parse, Serialize};
17
18pub const CA_DESCRIPTOR_TAG: u8 = 0x09;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize))]
25#[non_exhaustive]
26pub enum CaPmtListManagement {
27 More,
29 First,
31 Last,
33 Only,
35 Add,
37 Update,
39 Reserved(u8),
41}
42
43impl CaPmtListManagement {
44 #[must_use]
46 pub fn from_u8(v: u8) -> Self {
47 match v {
48 0x00 => Self::More,
49 0x01 => Self::First,
50 0x02 => Self::Last,
51 0x03 => Self::Only,
52 0x04 => Self::Add,
53 0x05 => Self::Update,
54 other => Self::Reserved(other),
55 }
56 }
57 #[must_use]
59 pub const fn to_u8(self) -> u8 {
60 match self {
61 Self::More => 0x00,
62 Self::First => 0x01,
63 Self::Last => 0x02,
64 Self::Only => 0x03,
65 Self::Add => 0x04,
66 Self::Update => 0x05,
67 Self::Reserved(v) => v,
68 }
69 }
70 #[must_use]
72 pub fn name(&self) -> &'static str {
73 match self {
74 Self::More => "more",
75 Self::First => "first",
76 Self::Last => "last",
77 Self::Only => "only",
78 Self::Add => "add",
79 Self::Update => "update",
80 Self::Reserved(_) => "reserved",
81 }
82 }
83}
84dvb_common::impl_spec_display!(CaPmtListManagement, Reserved);
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize))]
89#[non_exhaustive]
90pub enum CaPmtCmdId {
91 OkDescrambling,
93 OkMmi,
95 Query,
97 NotSelected,
99 Rfu(u8),
101}
102
103impl CaPmtCmdId {
104 #[must_use]
106 pub fn from_u8(v: u8) -> Self {
107 match v {
108 0x01 => Self::OkDescrambling,
109 0x02 => Self::OkMmi,
110 0x03 => Self::Query,
111 0x04 => Self::NotSelected,
112 other => Self::Rfu(other),
113 }
114 }
115 #[must_use]
117 pub const fn to_u8(self) -> u8 {
118 match self {
119 Self::OkDescrambling => 0x01,
120 Self::OkMmi => 0x02,
121 Self::Query => 0x03,
122 Self::NotSelected => 0x04,
123 Self::Rfu(v) => v,
124 }
125 }
126 #[must_use]
128 pub fn name(&self) -> &'static str {
129 match self {
130 Self::OkDescrambling => "ok_descrambling",
131 Self::OkMmi => "ok_mmi",
132 Self::Query => "query",
133 Self::NotSelected => "not_selected",
134 Self::Rfu(_) => "reserved",
135 }
136 }
137}
138dvb_common::impl_spec_display!(CaPmtCmdId, Rfu);
139
140#[derive(Debug, Clone, PartialEq, Eq)]
142#[cfg_attr(feature = "serde", derive(serde::Serialize))]
143pub struct CaPmtStream<'a> {
144 pub stream_type: u8,
146 pub elementary_pid: u16,
148 pub cmd_id: Option<CaPmtCmdId>,
152 #[cfg_attr(feature = "serde", serde(borrow, with = "super::bytes_serde"))]
154 pub ca_descriptors: &'a [u8],
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize))]
160pub struct CaPmt<'a> {
161 pub list_management: CaPmtListManagement,
163 pub program_number: u16,
165 pub version_number: u8,
167 pub current_next_indicator: bool,
169 pub cmd_id: Option<CaPmtCmdId>,
172 #[cfg_attr(feature = "serde", serde(borrow, with = "super::bytes_serde"))]
174 pub program_ca_descriptors: &'a [u8],
175 pub streams: Vec<CaPmtStream<'a>>,
177}
178
179const CA_PMT_PREFIX: usize = 6;
182const ES_PREFIX: usize = 5;
185
186impl<'a> Parse<'a> for CaPmt<'a> {
187 type Error = Error;
188 fn parse(bytes: &'a [u8]) -> Result<Self> {
189 let body = super::parse_apdu_header(bytes, tag::CA_PMT, "ca_pmt")?;
190 if body.len() < CA_PMT_PREFIX {
191 return Err(Error::BufferTooShort {
192 need: CA_PMT_PREFIX,
193 have: body.len(),
194 what: "ca_pmt prefix",
195 });
196 }
197 let list_management = CaPmtListManagement::from_u8(body[0]);
198 let program_number = u16::from_be_bytes([body[1], body[2]]);
199 let version_number = (body[3] >> 1) & 0x1F;
200 let current_next_indicator = (body[3] & 0x01) != 0;
201 let program_info_length = (((body[4] & 0x0F) as usize) << 8) | body[5] as usize;
202
203 let mut pos = CA_PMT_PREFIX;
204 let (cmd_id, program_ca_descriptors) =
205 parse_cmd_and_descriptors(body, &mut pos, program_info_length, "ca_pmt program_info")?;
206
207 let mut streams = Vec::new();
208 while pos < body.len() {
209 if pos + ES_PREFIX > body.len() {
210 return Err(Error::BufferTooShort {
211 need: pos + ES_PREFIX,
212 have: body.len(),
213 what: "ca_pmt ES prefix",
214 });
215 }
216 let stream_type = body[pos];
217 let elementary_pid = (((body[pos + 1] & 0x1F) as u16) << 8) | body[pos + 2] as u16;
218 let es_info_length = (((body[pos + 3] & 0x0F) as usize) << 8) | body[pos + 4] as usize;
219 pos += ES_PREFIX;
220 let (es_cmd, ca_descriptors) =
221 parse_cmd_and_descriptors(body, &mut pos, es_info_length, "ca_pmt ES_info")?;
222 streams.push(CaPmtStream {
223 stream_type,
224 elementary_pid,
225 cmd_id: es_cmd,
226 ca_descriptors,
227 });
228 }
229
230 Ok(Self {
231 list_management,
232 program_number,
233 version_number,
234 current_next_indicator,
235 cmd_id,
236 program_ca_descriptors,
237 streams,
238 })
239 }
240}
241
242fn parse_cmd_and_descriptors<'a>(
246 body: &'a [u8],
247 pos: &mut usize,
248 info_length: usize,
249 what: &'static str,
250) -> Result<(Option<CaPmtCmdId>, &'a [u8])> {
251 if info_length == 0 {
252 return Ok((None, &body[..0]));
253 }
254 let end = *pos + info_length;
255 if end > body.len() {
256 return Err(Error::LengthMismatch {
257 what,
258 declared: info_length,
259 actual: body.len().saturating_sub(*pos),
260 });
261 }
262 let cmd_id = CaPmtCmdId::from_u8(body[*pos]);
263 let descriptors = &body[*pos + 1..end];
264 *pos = end;
265 Ok((Some(cmd_id), descriptors))
266}
267
268fn info_block_len(cmd_id: Option<CaPmtCmdId>, descriptors: &[u8]) -> usize {
271 if cmd_id.is_some() || !descriptors.is_empty() {
272 1 + descriptors.len()
273 } else {
274 0
275 }
276}
277
278impl Serialize for CaPmt<'_> {
279 type Error = Error;
280 fn serialized_len(&self) -> usize {
281 let mut body = CA_PMT_PREFIX + info_block_len(self.cmd_id, self.program_ca_descriptors);
282 for s in &self.streams {
283 body += ES_PREFIX + info_block_len(s.cmd_id, s.ca_descriptors);
284 }
285 super::apdu_len(body)
286 }
287
288 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
289 let program_info_length = info_block_len(self.cmd_id, self.program_ca_descriptors);
290 let mut body = CA_PMT_PREFIX + program_info_length;
291 for s in &self.streams {
292 body += ES_PREFIX + info_block_len(s.cmd_id, s.ca_descriptors);
293 }
294 let mut pos = super::write_apdu_header(tag::CA_PMT, body, buf)?;
295
296 buf[pos] = self.list_management.to_u8();
297 buf[pos + 1..pos + 3].copy_from_slice(&self.program_number.to_be_bytes());
298 buf[pos + 3] =
300 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
301 buf[pos + 4] = 0xF0 | ((program_info_length >> 8) as u8 & 0x0F);
303 buf[pos + 5] = program_info_length as u8;
304 pos += CA_PMT_PREFIX;
305 pos += write_info_block(self.cmd_id, self.program_ca_descriptors, &mut buf[pos..])?;
306
307 for s in &self.streams {
308 let es_info_length = info_block_len(s.cmd_id, s.ca_descriptors);
309 buf[pos] = s.stream_type;
310 buf[pos + 1] = 0xE0 | ((s.elementary_pid >> 8) as u8 & 0x1F);
312 buf[pos + 2] = s.elementary_pid as u8;
313 buf[pos + 3] = 0xF0 | ((es_info_length >> 8) as u8 & 0x0F);
315 buf[pos + 4] = es_info_length as u8;
316 pos += ES_PREFIX;
317 pos += write_info_block(s.cmd_id, s.ca_descriptors, &mut buf[pos..])?;
318 }
319 Ok(pos)
320 }
321}
322
323fn write_info_block(
324 cmd_id: Option<CaPmtCmdId>,
325 descriptors: &[u8],
326 buf: &mut [u8],
327) -> Result<usize> {
328 let len = info_block_len(cmd_id, descriptors);
329 if len == 0 {
330 return Ok(0);
331 }
332 if buf.len() < len {
333 return Err(Error::OutputBufferTooSmall {
334 need: len,
335 have: buf.len(),
336 });
337 }
338 buf[0] = cmd_id.unwrap_or(CaPmtCmdId::OkDescrambling).to_u8();
341 buf[1..len].copy_from_slice(descriptors);
342 Ok(len)
343}
344
345impl<'a> ApduDef<'a> for CaPmt<'a> {
346 const TAG: ApduTag = tag::CA_PMT;
347 const NAME: &'static str = "CA_PMT";
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 fn sample_ca_descriptor(ca_system_id: u16, pid: u16) -> [u8; 6] {
355 [
357 CA_DESCRIPTOR_TAG,
358 0x04,
359 (ca_system_id >> 8) as u8,
360 ca_system_id as u8,
361 0xE0 | ((pid >> 8) as u8 & 0x1F),
362 pid as u8,
363 ]
364 }
365
366 #[test]
367 fn empty_ca_pmt_round_trips() {
368 let pmt = CaPmt {
369 list_management: CaPmtListManagement::Only,
370 program_number: 0x1234,
371 version_number: 5,
372 current_next_indicator: true,
373 cmd_id: None,
374 program_ca_descriptors: &[],
375 streams: Vec::new(),
376 };
377 let bytes = pmt.to_bytes();
378 assert_eq!(&bytes[..4], &[0x9F, 0x80, 0x32, 0x06]);
379 assert_eq!(CaPmt::parse(&bytes).unwrap(), pmt);
380 }
381
382 #[test]
383 fn multi_es_round_trips_and_bites() {
384 let prog_desc = sample_ca_descriptor(0x0500, 0x0100);
385 let es0_desc = sample_ca_descriptor(0x0500, 0x0101);
386 let es1_desc = sample_ca_descriptor(0x0B00, 0x0201);
387 let pmt = CaPmt {
388 list_management: CaPmtListManagement::Only,
389 program_number: 0x0001,
390 version_number: 1,
391 current_next_indicator: true,
392 cmd_id: Some(CaPmtCmdId::OkDescrambling),
393 program_ca_descriptors: &prog_desc,
394 streams: alloc::vec![
395 CaPmtStream {
396 stream_type: 0x02,
397 elementary_pid: 0x0200,
398 cmd_id: Some(CaPmtCmdId::OkDescrambling),
399 ca_descriptors: &es0_desc,
400 },
401 CaPmtStream {
402 stream_type: 0x03,
403 elementary_pid: 0x0201,
404 cmd_id: Some(CaPmtCmdId::OkDescrambling),
405 ca_descriptors: &es1_desc,
406 },
407 ],
408 };
409 let bytes = pmt.to_bytes();
410 let parsed = CaPmt::parse(&bytes).unwrap();
411 assert_eq!(parsed, pmt);
412 assert_eq!(parsed.streams.len(), 2);
413 assert_eq!(parsed.list_management.name(), "only");
414
415 let mut other = pmt.clone();
417 other.program_number = 0x0002;
418 assert_ne!(bytes, other.to_bytes());
419
420 let mut other2 = pmt.clone();
422 other2.streams[0].elementary_pid = 0x0300;
423 assert_ne!(bytes, other2.to_bytes());
424 }
425
426 #[test]
427 fn es_without_ca_info_omits_cmd_id() {
428 let pmt = CaPmt {
429 list_management: CaPmtListManagement::Add,
430 program_number: 7,
431 version_number: 0,
432 current_next_indicator: true,
433 cmd_id: None,
434 program_ca_descriptors: &[],
435 streams: alloc::vec![CaPmtStream {
436 stream_type: 0x1B,
437 elementary_pid: 0x00FF,
438 cmd_id: None,
439 ca_descriptors: &[],
440 }],
441 };
442 let bytes = pmt.to_bytes();
443 let parsed = CaPmt::parse(&bytes).unwrap();
444 assert_eq!(parsed, pmt);
445 assert!(parsed.streams[0].cmd_id.is_none());
446 }
447}