1use crate::objects::ca_pmt::{
20 CA_DESCRIPTOR_TAG, CaPmt, CaPmtCmdId, CaPmtListManagement, CaPmtStream,
21};
22use alloc::vec::Vec;
23use broadcast_common::Serialize;
24use dvb_si::descriptors::DescriptorLoop;
25use dvb_si::tables::pmt::PmtSection;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct CaPmtBuilt {
31 list_management: CaPmtListManagement,
32 program_number: u16,
33 version_number: u8,
34 current_next_indicator: bool,
35 cmd_id: CaPmtCmdId,
36 program_ca_descriptors: Vec<u8>,
37 streams: Vec<BuiltStream>,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41struct BuiltStream {
42 stream_type: u8,
43 elementary_pid: u16,
44 ca_descriptors: Vec<u8>,
45}
46
47fn ca_system_id(body: &[u8]) -> Option<u16> {
50 body.first_chunk::<2>().map(|b| u16::from_be_bytes(*b))
51}
52
53fn ca_descriptors_filtered(loop_: &DescriptorLoop<'_>, allowed: Option<&[u16]>) -> Vec<u8> {
59 let mut out = Vec::new();
60 for (tag, body) in loop_.raw_tags() {
61 if tag != CA_DESCRIPTOR_TAG {
62 continue;
63 }
64 if let Some(allow) = allowed {
65 match ca_system_id(body) {
66 Some(id) if allow.contains(&id) => {}
67 _ => continue,
68 }
69 }
70 out.push(tag);
73 out.push(body.len() as u8);
74 out.extend_from_slice(body);
75 }
76 out
77}
78
79fn retain_loop(buf: &mut Vec<u8>, allowed: &[u16]) {
82 let mut out = Vec::new();
83 let mut pos = 0;
84 while pos + 2 <= buf.len() {
85 let end = pos + 2 + buf[pos + 1] as usize;
86 if end > buf.len() {
87 break;
88 }
89 if ca_system_id(&buf[pos + 2..end]).is_some_and(|id| allowed.contains(&id)) {
90 out.extend_from_slice(&buf[pos..end]);
91 }
92 pos = end;
93 }
94 *buf = out;
95}
96
97#[must_use]
106pub fn build_ca_pmt(
107 pmt: &PmtSection<'_>,
108 list_management: CaPmtListManagement,
109 cmd_id: CaPmtCmdId,
110) -> CaPmtBuilt {
111 build(pmt, None, list_management, cmd_id)
112}
113
114#[must_use]
123pub fn build_ca_pmt_for_caids(
124 pmt: &PmtSection<'_>,
125 allowed: &[u16],
126 list_management: CaPmtListManagement,
127 cmd_id: CaPmtCmdId,
128) -> CaPmtBuilt {
129 build(pmt, Some(allowed), list_management, cmd_id)
130}
131
132fn build(
133 pmt: &PmtSection<'_>,
134 allowed: Option<&[u16]>,
135 list_management: CaPmtListManagement,
136 cmd_id: CaPmtCmdId,
137) -> CaPmtBuilt {
138 let program_ca_descriptors = ca_descriptors_filtered(&pmt.program_info, allowed);
139 let streams = pmt
140 .streams
141 .iter()
142 .map(|s| BuiltStream {
143 stream_type: s.stream_type.to_u8(),
144 elementary_pid: s.elementary_pid,
145 ca_descriptors: ca_descriptors_filtered(&s.es_info, allowed),
146 })
147 .collect();
148 CaPmtBuilt {
149 list_management,
150 program_number: pmt.program_number,
151 version_number: pmt.version_number,
152 current_next_indicator: pmt.current_next_indicator,
153 cmd_id,
154 program_ca_descriptors,
155 streams,
156 }
157}
158
159impl CaPmtBuilt {
160 #[must_use]
164 pub fn as_ca_pmt(&self) -> CaPmt<'_> {
165 CaPmt {
166 list_management: self.list_management,
167 program_number: self.program_number,
168 version_number: self.version_number,
169 current_next_indicator: self.current_next_indicator,
170 cmd_id: cmd_for(self.cmd_id, &self.program_ca_descriptors),
171 program_ca_descriptors: &self.program_ca_descriptors,
172 streams: self
173 .streams
174 .iter()
175 .map(|s| CaPmtStream {
176 stream_type: s.stream_type,
177 elementary_pid: s.elementary_pid,
178 cmd_id: cmd_for(self.cmd_id, &s.ca_descriptors),
179 ca_descriptors: &s.ca_descriptors,
180 })
181 .collect(),
182 }
183 }
184
185 pub fn retain_caids(&mut self, allowed: &[u16]) {
190 retain_loop(&mut self.program_ca_descriptors, allowed);
191 for s in &mut self.streams {
192 retain_loop(&mut s.ca_descriptors, allowed);
193 }
194 }
195
196 #[must_use]
198 pub fn to_bytes(&self) -> Vec<u8> {
199 self.as_ca_pmt().to_bytes()
200 }
201}
202
203fn cmd_for(cmd_id: CaPmtCmdId, descriptors: &[u8]) -> Option<CaPmtCmdId> {
206 if descriptors.is_empty() {
207 None
208 } else {
209 Some(cmd_id)
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use crate::objects::ca_pmt::CaPmt;
217 use alloc::vec;
218 use broadcast_common::Parse;
219
220 #[test]
221 fn builds_from_real_pmt_fixture() {
222 let pmt_bytes = build_test_pmt();
227 let pmt = PmtSection::parse(&pmt_bytes).expect("valid PMT");
228
229 let built = build_ca_pmt(&pmt, CaPmtListManagement::Only, CaPmtCmdId::OkDescrambling);
230 let bytes = built.to_bytes();
231
232 let parsed = CaPmt::parse(&bytes).unwrap();
234 let view = built.as_ca_pmt();
235 assert_eq!(parsed, view);
236
237 assert!(!parsed.program_ca_descriptors.is_empty());
239 assert_eq!(parsed.program_ca_descriptors[0], CA_DESCRIPTOR_TAG);
240 assert_eq!(parsed.cmd_id, Some(CaPmtCmdId::OkDescrambling));
241
242 assert_eq!(parsed.streams.len(), 2);
244 assert!(!parsed.streams[0].ca_descriptors.is_empty());
245 assert_eq!(parsed.streams[0].cmd_id, Some(CaPmtCmdId::OkDescrambling));
246 assert!(parsed.streams[1].ca_descriptors.is_empty());
247 assert_eq!(parsed.streams[1].cmd_id, None);
248 }
249
250 #[test]
251 fn strips_non_ca_descriptors() {
252 let pmt_bytes = build_test_pmt();
253 let pmt = PmtSection::parse(&pmt_bytes).unwrap();
254 let built = build_ca_pmt(&pmt, CaPmtListManagement::Add, CaPmtCmdId::Query);
255 let view = built.as_ca_pmt();
256 let mut pos = 0;
258 let d = view.program_ca_descriptors;
259 while pos < d.len() {
260 assert_eq!(d[pos], CA_DESCRIPTOR_TAG);
261 pos += 2 + d[pos + 1] as usize;
262 }
263 }
264
265 fn caids(buf: &[u8]) -> Vec<u16> {
267 let mut ids = Vec::new();
268 let mut pos = 0;
269 while pos + 2 <= buf.len() {
270 let end = pos + 2 + buf[pos + 1] as usize;
271 ids.push(u16::from_be_bytes([buf[pos + 2], buf[pos + 3]]));
272 pos = end;
273 }
274 ids
275 }
276
277 #[test]
278 fn for_caids_keeps_only_allowed_system_ids() {
279 let pmt_bytes = build_test_pmt();
280 let pmt = PmtSection::parse(&pmt_bytes).unwrap();
281
282 let built = build_ca_pmt_for_caids(
284 &pmt,
285 &[0x0500],
286 CaPmtListManagement::Only,
287 CaPmtCmdId::OkDescrambling,
288 );
289 assert_eq!(caids(&built.program_ca_descriptors), vec![0x0500]);
290
291 let view = built.as_ca_pmt();
293 assert!(!view.streams[0].ca_descriptors.is_empty());
294 assert!(view.streams[1].ca_descriptors.is_empty());
295 assert_eq!(CaPmt::parse(&built.to_bytes()).unwrap(), view);
297 }
298
299 #[test]
300 fn for_caids_empty_allowlist_drops_all_ca() {
301 let pmt_bytes = build_test_pmt();
302 let pmt = PmtSection::parse(&pmt_bytes).unwrap();
303 let built = build_ca_pmt_for_caids(&pmt, &[], CaPmtListManagement::Only, CaPmtCmdId::Query);
304 assert!(built.program_ca_descriptors.is_empty());
305 let view = built.as_ca_pmt();
307 assert_eq!(view.cmd_id, None);
308 assert!(view.streams.iter().all(|s| s.cmd_id.is_none()));
309 }
310
311 #[test]
312 fn retain_caids_matches_the_filtering_constructor() {
313 let pmt_bytes = build_test_pmt();
314 let pmt = PmtSection::parse(&pmt_bytes).unwrap();
315 let allow = [0x1800u16];
316
317 let mut post = build_ca_pmt(&pmt, CaPmtListManagement::Only, CaPmtCmdId::OkDescrambling);
318 post.retain_caids(&allow);
319 let pre = build_ca_pmt_for_caids(
320 &pmt,
321 &allow,
322 CaPmtListManagement::Only,
323 CaPmtCmdId::OkDescrambling,
324 );
325 assert_eq!(post, pre);
326 assert_eq!(caids(&post.program_ca_descriptors), vec![0x1800]);
328 }
329
330 fn ca_descriptor(ca_system_id: u16, pid: u16) -> [u8; 6] {
333 [
334 0x09,
335 0x04,
336 (ca_system_id >> 8) as u8,
337 ca_system_id as u8,
338 0xE0 | ((pid >> 8) as u8 & 0x1F),
339 pid as u8,
340 ]
341 }
342
343 fn build_test_pmt() -> Vec<u8> {
344 let prog_ca = ca_descriptor(0x0500, 0x0100);
347 let prog_ca2 = ca_descriptor(0x1800, 0x0110);
348 let reg = [0x05u8, 0x04, b'H', b'D', b'M', b'V'];
349 let mut program_info = Vec::new();
350 program_info.extend_from_slice(&prog_ca);
351 program_info.extend_from_slice(&prog_ca2);
352 program_info.extend_from_slice(®);
353
354 let es0_ca = ca_descriptor(0x0500, 0x0101);
356 let lang = [0x0Au8, 0x04, b'e', b'n', b'g', 0x00];
358
359 let mut body = Vec::new();
360 body.push(0x02);
362 body.push(0);
364 body.push(0);
365 body.extend_from_slice(&[0x00, 0x01]);
367 body.push(0xC3);
369 body.push(0x00);
371 body.push(0x00);
372 body.push(0xE0 | 0x02);
374 body.push(0x00);
375 let pil = program_info.len();
377 body.push(0xF0 | ((pil >> 8) as u8 & 0x0F));
378 body.push(pil as u8);
379 body.extend_from_slice(&program_info);
380
381 body.push(0x02); body.push(0xE0 | 0x02); body.push(0x00);
385 body.push(0xF0 | ((es0_ca.len() >> 8) as u8 & 0x0F));
386 body.push(es0_ca.len() as u8);
387 body.extend_from_slice(&es0_ca);
388
389 body.push(0x03);
391 body.push(0xE0 | 0x02); body.push(0x01);
393 body.push(0xF0 | ((lang.len() >> 8) as u8 & 0x0F));
394 body.push(lang.len() as u8);
395 body.extend_from_slice(&lang);
396
397 let section_length = body.len() - 3 + 4;
399 body[1] = 0xB0 | ((section_length >> 8) as u8 & 0x0F);
400 body[2] = section_length as u8;
401
402 let crc = broadcast_common::crc32_mpeg2::compute(&body);
405 body.extend_from_slice(&crc.to_be_bytes());
406 body
407 }
408}