1use crate::objects::ca_pmt::{
20 CaPmt, CaPmtCmdId, CaPmtListManagement, CaPmtStream, CA_DESCRIPTOR_TAG,
21};
22use alloc::vec::Vec;
23use dvb_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_descriptors_only(loop_: &DescriptorLoop<'_>) -> Vec<u8> {
50 let mut out = Vec::new();
51 for (tag, body) in loop_.raw_tags() {
52 if tag == CA_DESCRIPTOR_TAG {
53 out.push(tag);
56 out.push(body.len() as u8);
57 out.extend_from_slice(body);
58 }
59 }
60 out
61}
62
63#[must_use]
72pub fn build_ca_pmt(
73 pmt: &PmtSection<'_>,
74 list_management: CaPmtListManagement,
75 cmd_id: CaPmtCmdId,
76) -> CaPmtBuilt {
77 let program_ca_descriptors = ca_descriptors_only(&pmt.program_info);
78 let streams = pmt
79 .streams
80 .iter()
81 .map(|s| BuiltStream {
82 stream_type: s.stream_type.to_u8(),
83 elementary_pid: s.elementary_pid,
84 ca_descriptors: ca_descriptors_only(&s.es_info),
85 })
86 .collect();
87 CaPmtBuilt {
88 list_management,
89 program_number: pmt.program_number,
90 version_number: pmt.version_number,
91 current_next_indicator: pmt.current_next_indicator,
92 cmd_id,
93 program_ca_descriptors,
94 streams,
95 }
96}
97
98impl CaPmtBuilt {
99 #[must_use]
103 pub fn as_ca_pmt(&self) -> CaPmt<'_> {
104 CaPmt {
105 list_management: self.list_management,
106 program_number: self.program_number,
107 version_number: self.version_number,
108 current_next_indicator: self.current_next_indicator,
109 cmd_id: cmd_for(self.cmd_id, &self.program_ca_descriptors),
110 program_ca_descriptors: &self.program_ca_descriptors,
111 streams: self
112 .streams
113 .iter()
114 .map(|s| CaPmtStream {
115 stream_type: s.stream_type,
116 elementary_pid: s.elementary_pid,
117 cmd_id: cmd_for(self.cmd_id, &s.ca_descriptors),
118 ca_descriptors: &s.ca_descriptors,
119 })
120 .collect(),
121 }
122 }
123
124 #[must_use]
126 pub fn to_bytes(&self) -> Vec<u8> {
127 self.as_ca_pmt().to_bytes()
128 }
129}
130
131fn cmd_for(cmd_id: CaPmtCmdId, descriptors: &[u8]) -> Option<CaPmtCmdId> {
134 if descriptors.is_empty() {
135 None
136 } else {
137 Some(cmd_id)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::objects::ca_pmt::CaPmt;
145 use dvb_common::Parse;
146
147 #[test]
148 fn builds_from_real_pmt_fixture() {
149 let pmt_bytes = build_test_pmt();
154 let pmt = PmtSection::parse(&pmt_bytes).expect("valid PMT");
155
156 let built = build_ca_pmt(&pmt, CaPmtListManagement::Only, CaPmtCmdId::OkDescrambling);
157 let bytes = built.to_bytes();
158
159 let parsed = CaPmt::parse(&bytes).unwrap();
161 let view = built.as_ca_pmt();
162 assert_eq!(parsed, view);
163
164 assert!(!parsed.program_ca_descriptors.is_empty());
166 assert_eq!(parsed.program_ca_descriptors[0], CA_DESCRIPTOR_TAG);
167 assert_eq!(parsed.cmd_id, Some(CaPmtCmdId::OkDescrambling));
168
169 assert_eq!(parsed.streams.len(), 2);
171 assert!(!parsed.streams[0].ca_descriptors.is_empty());
172 assert_eq!(parsed.streams[0].cmd_id, Some(CaPmtCmdId::OkDescrambling));
173 assert!(parsed.streams[1].ca_descriptors.is_empty());
174 assert_eq!(parsed.streams[1].cmd_id, None);
175 }
176
177 #[test]
178 fn strips_non_ca_descriptors() {
179 let pmt_bytes = build_test_pmt();
180 let pmt = PmtSection::parse(&pmt_bytes).unwrap();
181 let built = build_ca_pmt(&pmt, CaPmtListManagement::Add, CaPmtCmdId::Query);
182 let view = built.as_ca_pmt();
183 let mut pos = 0;
185 let d = view.program_ca_descriptors;
186 while pos < d.len() {
187 assert_eq!(d[pos], CA_DESCRIPTOR_TAG);
188 pos += 2 + d[pos + 1] as usize;
189 }
190 }
191
192 fn ca_descriptor(ca_system_id: u16, pid: u16) -> [u8; 6] {
195 [
196 0x09,
197 0x04,
198 (ca_system_id >> 8) as u8,
199 ca_system_id as u8,
200 0xE0 | ((pid >> 8) as u8 & 0x1F),
201 pid as u8,
202 ]
203 }
204
205 fn build_test_pmt() -> Vec<u8> {
206 let prog_ca = ca_descriptor(0x0500, 0x0100);
208 let reg = [0x05u8, 0x04, b'H', b'D', b'M', b'V'];
209 let mut program_info = Vec::new();
210 program_info.extend_from_slice(&prog_ca);
211 program_info.extend_from_slice(®);
212
213 let es0_ca = ca_descriptor(0x0500, 0x0101);
215 let lang = [0x0Au8, 0x04, b'e', b'n', b'g', 0x00];
217
218 let mut body = Vec::new();
219 body.push(0x02);
221 body.push(0);
223 body.push(0);
224 body.extend_from_slice(&[0x00, 0x01]);
226 body.push(0xC3);
228 body.push(0x00);
230 body.push(0x00);
231 body.push(0xE0 | 0x02);
233 body.push(0x00);
234 let pil = program_info.len();
236 body.push(0xF0 | ((pil >> 8) as u8 & 0x0F));
237 body.push(pil as u8);
238 body.extend_from_slice(&program_info);
239
240 body.push(0x02); body.push(0xE0 | 0x02); body.push(0x00);
244 body.push(0xF0 | ((es0_ca.len() >> 8) as u8 & 0x0F));
245 body.push(es0_ca.len() as u8);
246 body.extend_from_slice(&es0_ca);
247
248 body.push(0x03);
250 body.push(0xE0 | 0x02); body.push(0x01);
252 body.push(0xF0 | ((lang.len() >> 8) as u8 & 0x0F));
253 body.push(lang.len() as u8);
254 body.extend_from_slice(&lang);
255
256 let section_length = body.len() - 3 + 4;
258 body[1] = 0xB0 | ((section_length >> 8) as u8 & 0x0F);
259 body[2] = section_length as u8;
260
261 let crc = dvb_common::crc32_mpeg2::compute(&body);
264 body.extend_from_slice(&crc.to_be_bytes());
265 body
266 }
267}