1use dvb_bbframe::header::{Bbheader, Mode, BBHEADER_LEN};
27use dvb_bbframe::packet::{CarryOverExtractor, NM_UP_SIZE};
28
29use crate::payload::AnyPayload;
30use crate::pump::{Stats, T2miPump};
31
32pub struct InnerTsRecovery {
43 pump: T2miPump,
44 extractor: CarryOverExtractor,
45 out: Vec<[u8; NM_UP_SIZE]>,
46 up_buf: Vec<[u8; NM_UP_SIZE]>,
47 target_plp: Option<u8>,
48 filtered_out: u64,
49}
50
51impl InnerTsRecovery {
52 #[must_use]
55 pub fn new(t2mi_pid: u16) -> Self {
56 Self::build(t2mi_pid, None)
57 }
58
59 #[must_use]
63 pub fn new_for_plp(t2mi_pid: u16, plp_id: u8) -> Self {
64 Self::build(t2mi_pid, Some(plp_id))
65 }
66
67 fn build(t2mi_pid: u16, target_plp: Option<u8>) -> Self {
68 Self {
69 pump: T2miPump::new(t2mi_pid),
70 extractor: CarryOverExtractor::new(),
71 out: Vec::new(),
72 up_buf: Vec::new(),
73 target_plp,
74 filtered_out: 0,
75 }
76 }
77
78 pub fn feed(&mut self, ts_packet: &[u8]) -> &[[u8; NM_UP_SIZE]] {
82 self.out.clear();
83 let events: Vec<_> = self.pump.feed_ts(ts_packet).collect();
87 for event in events {
88 let Ok(AnyPayload::Bbframe(bb)) = event.payload() else {
89 continue;
90 };
91 if self.target_plp.is_some_and(|t| bb.plp_id != t) {
92 self.filtered_out += 1;
93 continue;
94 }
95 if bb.bbframe.len() < BBHEADER_LEN {
96 continue;
97 }
98 let Ok(hdr) = Bbheader::parse(bb.bbframe) else {
99 continue;
100 };
101 let header_bytes: [u8; BBHEADER_LEN] = match bb.bbframe[..BBHEADER_LEN].try_into() {
102 Ok(b) => b,
103 Err(_) => continue,
104 };
105 let data_field = &bb.bbframe[BBHEADER_LEN..];
106 match hdr.mode {
107 Mode::Normal => {
108 self.extractor
109 .feed_nm_into(&header_bytes, data_field, &mut self.up_buf);
110 }
111 Mode::HighEfficiency if !hdr.matype.npd => {
112 self.extractor.feed_hem_into(
113 &header_bytes,
114 data_field,
115 false,
116 &mut self.up_buf,
117 );
118 }
119 _ => continue,
121 }
122 self.out.append(&mut self.up_buf);
123 }
124 &self.out
125 }
126
127 #[must_use]
130 pub fn stats(&self) -> Stats {
131 self.pump.stats()
132 }
133
134 #[must_use]
138 pub fn filtered_bbframes(&self) -> u64 {
139 self.filtered_out
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use dvb_bbframe::crc::crc8;
147 use dvb_bbframe::header::{Matype, TsGs};
148 use dvb_common::crc32_mpeg2;
149
150 const TS_SYNC: u8 = 0x47;
151 const TS_LEN: usize = 188;
152
153 fn inner_packet() -> [u8; TS_LEN] {
155 let mut p = [0xAAu8; TS_LEN];
156 p[0] = TS_SYNC;
157 p[1] = 0x41; p[2] = 0x00;
159 p[3] = 0x10; p
161 }
162
163 fn nm_bbframe(inner: &[u8; TS_LEN]) -> Vec<u8> {
165 let hdr = Bbheader {
166 matype: Matype {
167 ts_gs: TsGs::Ts,
168 sis: true,
169 ccm: true,
170 issyi: false,
171 npd: false,
172 ext: 0,
173 isi: 0,
174 },
175 upl: 1504,
176 sync: TS_SYNC,
177 dfl: 1504,
178 syncd: 0,
179 mode: Mode::Normal,
180 issy_in_header: None,
181 };
182 let mut frame = hdr.serialize().to_vec();
183 let mut data = [0u8; TS_LEN];
184 data[0] = crc8(&[0u8; TS_LEN]); data[1..].copy_from_slice(&inner[1..]);
186 frame.extend_from_slice(&data);
187 frame
188 }
189
190 fn t2mi_packet(bbframe: &[u8]) -> Vec<u8> {
192 let mut payload = vec![0x00, 0x05, 0x80]; payload.extend_from_slice(bbframe);
194 let mut pkt = vec![0x00u8, 0x01, 0x00, 0x00];
195 pkt.extend_from_slice(&((payload.len() * 8) as u16).to_be_bytes());
196 pkt.extend_from_slice(&payload);
197 let crc = crc32_mpeg2::compute(&pkt);
198 pkt.extend_from_slice(&crc.to_be_bytes());
199 pkt
200 }
201
202 fn outer_ts(pid: u16, data: &[u8]) -> Vec<[u8; TS_LEN]> {
204 let mut out = Vec::new();
205 let first_cap = TS_LEN - 5;
206 let cont_cap = TS_LEN - 4;
207 let mut off = 0;
208 let mut first = true;
209 while off < data.len() {
210 let mut pkt = [0xFFu8; TS_LEN];
211 pkt[0] = TS_SYNC;
212 let cap = if first { first_cap } else { cont_cap };
213 pkt[1] = (if first { 0x40 } else { 0x00 }) | (((pid >> 8) as u8) & 0x1F);
214 pkt[2] = (pid & 0xFF) as u8;
215 pkt[3] = 0x10;
216 let hdr_len = if first {
217 pkt[4] = 0x00; 5
219 } else {
220 4
221 };
222 let n = (data.len() - off).min(cap);
223 pkt[hdr_len..hdr_len + n].copy_from_slice(&data[off..off + n]);
224 out.push(pkt);
225 off += n;
226 first = false;
227 }
228 out
229 }
230
231 #[test]
232 fn recovers_inner_ts_from_nm_bbframe_chain() {
233 let pid = 0x1000;
234 let inner = inner_packet();
235 let outer = outer_ts(pid, &t2mi_packet(&nm_bbframe(&inner)));
236
237 let mut rec = InnerTsRecovery::new(pid);
238 let mut recovered: Vec<[u8; TS_LEN]> = Vec::new();
239 for pkt in &outer {
240 recovered.extend_from_slice(rec.feed(pkt));
241 }
242
243 assert_eq!(recovered.len(), 1, "exactly one inner TS packet expected");
244 assert_eq!(recovered[0][0], TS_SYNC, "sync byte restored");
245 assert_eq!(&recovered[0][1..], &inner[1..]);
247 }
248
249 #[test]
250 fn wrong_pid_yields_nothing() {
251 let inner = inner_packet();
252 let outer = outer_ts(0x1000, &t2mi_packet(&nm_bbframe(&inner)));
253 let mut rec = InnerTsRecovery::new(0x0064); let mut n = 0;
255 for pkt in &outer {
256 n += rec.feed(pkt).len();
257 }
258 assert_eq!(n, 0);
259 }
260
261 #[test]
262 fn garbage_packet_no_panic_no_output() {
263 let mut rec = InnerTsRecovery::new(0x1000);
264 let junk = [0u8; TS_LEN];
265 assert!(rec.feed(&junk).is_empty());
266 }
267
268 fn t2mi_packet_for_plp(bbframe: &[u8], plp_id: u8) -> Vec<u8> {
270 let mut payload = vec![0x00, plp_id, 0x80]; payload.extend_from_slice(bbframe);
272 let mut pkt = vec![0x00u8, 0x01, 0x00, 0x00];
273 pkt.extend_from_slice(&((payload.len() * 8) as u16).to_be_bytes());
274 pkt.extend_from_slice(&payload);
275 let crc = crc32_mpeg2::compute(&pkt);
276 pkt.extend_from_slice(&crc.to_be_bytes());
277 pkt
278 }
279
280 fn tagged_inner_packet(marker: u8) -> [u8; TS_LEN] {
282 let mut p = [0xAAu8; TS_LEN];
283 p[0] = TS_SYNC;
284 p[1] = 0x41;
285 p[2] = 0x00;
286 p[3] = 0x10;
287 p[4] = marker;
288 p
289 }
290
291 #[test]
292 fn plp_filter_keeps_only_target_plp() {
293 let pid = 0x1000;
294 let inner_plp0 = tagged_inner_packet(0xA0);
295 let inner_plp1 = tagged_inner_packet(0xB0);
296
297 let bb_plp0 = nm_bbframe(&inner_plp0);
299 let bb_plp1 = nm_bbframe(&inner_plp1);
300
301 let t2mi_plp0 = t2mi_packet_for_plp(&bb_plp0, 0);
303 let t2mi_plp1 = t2mi_packet_for_plp(&bb_plp1, 1);
304
305 let mut combined = t2mi_plp0;
307 combined.extend_from_slice(&t2mi_plp1);
308 let outer = outer_ts(pid, &combined);
309
310 let mut rec0 = InnerTsRecovery::new_for_plp(pid, 0);
312 let mut recovered_0: Vec<[u8; TS_LEN]> = Vec::new();
313 for pkt in &outer {
314 recovered_0.extend_from_slice(rec0.feed(pkt));
315 }
316 assert_eq!(
317 recovered_0.len(),
318 1,
319 "plp 0 filter should recover exactly one inner packet"
320 );
321 assert_eq!(recovered_0[0][4], 0xA0, "should be the plp 0 packet");
322 assert_eq!(
323 rec0.filtered_bbframes(),
324 1,
325 "one BBFRAME (plp 1) filtered out"
326 );
327
328 let mut rec1 = InnerTsRecovery::new_for_plp(pid, 1);
330 let mut recovered_1: Vec<[u8; TS_LEN]> = Vec::new();
331 for pkt in &outer {
332 recovered_1.extend_from_slice(rec1.feed(pkt));
333 }
334 assert_eq!(
335 recovered_1.len(),
336 1,
337 "plp 1 filter should recover exactly one inner packet"
338 );
339 assert_eq!(recovered_1[0][4], 0xB0, "should be the plp 1 packet");
340 assert_eq!(
341 rec1.filtered_bbframes(),
342 1,
343 "one BBFRAME (plp 0) filtered out"
344 );
345
346 let mut all = InnerTsRecovery::new(pid);
348 let mut recovered_all: Vec<[u8; TS_LEN]> = Vec::new();
349 for pkt in &outer {
350 recovered_all.extend_from_slice(all.feed(pkt));
351 }
352 assert_eq!(
353 recovered_all.len(),
354 2,
355 "unfiltered should recover both inner packets"
356 );
357 assert_eq!(
358 all.filtered_bbframes(),
359 0,
360 "no filtering when target is None"
361 );
362 }
363}