Skip to main content

dvb_bbframe/
packet.rs

1//! User packet extraction from BBFrame data fields.
2//!
3//! Supports Normal Mode (NM, 188-byte stride) and High Efficiency Mode
4//! (HEM, 187-byte stride) per EN 302 755 §5.1.8.
5//!
6//! In NM the first byte of each user packet in the data field is a CRC-8
7//! that replaces the original sync byte (0x47). In HEM the sync byte is
8//! simply absent and must be prepended.
9//!
10//! ## SYNCD handling
11//!
12//! `SYNCD` gives the bit offset from the start of the DATA FIELD to the
13//! first bit of the CRC-8 byte of the first user packet (NM) or the first
14//! byte of the first user packet (HEM).  Callers typically prepend any
15//! carry-over from the previous BBFrame and pass the result plus SYNCD.
16//!
17//! ## NPD/DNP reinsertion (HEM only)
18//!
19//! When NPD is active (`matype.npd == true`), each transmitted user
20//! packet is followed by a 1-byte DNP counter. The [`HemTsIter`] skips
21//! these DNP bytes automatically.
22
23use crate::header::{Bbheader, Mode, BBHEADER_LEN};
24
25/// User packet size in Normal Mode (188 bytes = full MPEG-2 TS packet).
26pub const NM_UP_SIZE: usize = 188;
27
28/// User packet size in High Efficiency Mode (187 bytes = TS minus sync byte).
29pub const HEM_UP_SIZE: usize = 187;
30
31/// MPEG-2 sync byte that CRC-8 replaces in NM.
32pub const TS_SYNC_BYTE: u8 = 0x47;
33
34/// Iterator over NM TS user packets.
35///
36/// Each item is a `[u8; 188]` with the sync byte restored to 0x47,
37/// replacing the CRC-8 byte that occupies position 0 in the data field.
38pub struct NmTsIter<'a> {
39    data: &'a [u8],
40    pos: usize,
41}
42
43impl<'a> NmTsIter<'a> {
44    /// Create a new NM TS user packet iterator.
45    ///
46    /// `data` is the full data field (after skipping SYNCD bytes if
47    /// already aligned). The iterator starts at byte 0 of `data`.
48    pub fn new(data: &'a [u8]) -> Self {
49        Self { data, pos: 0 }
50    }
51
52    /// Return the unconsumed tail of the data field.
53    pub fn remaining(self) -> &'a [u8] {
54        &self.data[self.pos..]
55    }
56}
57
58impl Iterator for NmTsIter<'_> {
59    type Item = [u8; NM_UP_SIZE];
60
61    fn next(&mut self) -> Option<Self::Item> {
62        if self.pos + NM_UP_SIZE > self.data.len() {
63            return None;
64        }
65        let mut pkt = [0u8; NM_UP_SIZE];
66        pkt[0] = TS_SYNC_BYTE; // Replace CRC-8 byte with sync byte
67        pkt[1..].copy_from_slice(&self.data[self.pos + 1..self.pos + NM_UP_SIZE]);
68        self.pos += NM_UP_SIZE;
69        Some(pkt)
70    }
71
72    fn size_hint(&self) -> (usize, Option<usize>) {
73        let remaining = self.data.len().saturating_sub(self.pos);
74        let count = remaining / NM_UP_SIZE;
75        (count, Some(count))
76    }
77}
78
79/// Iterator over HEM TS user packets.
80///
81/// Each item is a `[u8; 188]` with the sync byte prepended (0x47).
82/// The 187-byte user packets in the data field have no sync byte.
83/// If NPD is active, DNP bytes are skipped automatically.
84pub struct HemTsIter<'a> {
85    data: &'a [u8],
86    pos: usize,
87    npd: bool,
88}
89
90impl<'a> HemTsIter<'a> {
91    /// Create a new HEM TS user packet iterator.
92    ///
93    /// `data` is the full data field (after skipping SYNCD bytes if
94    /// already aligned). The iterator starts at byte 0 of `data`.
95    pub fn new(data: &'a [u8], npd: bool) -> Self {
96        Self { data, pos: 0, npd }
97    }
98
99    /// Return the unconsumed tail of the data field.
100    pub fn remaining(self) -> &'a [u8] {
101        &self.data[self.pos..]
102    }
103}
104
105impl Iterator for HemTsIter<'_> {
106    type Item = [u8; NM_UP_SIZE];
107
108    fn next(&mut self) -> Option<Self::Item> {
109        let stride = HEM_UP_SIZE + if self.npd { 1 } else { 0 };
110        if self.pos + stride > self.data.len() {
111            return None;
112        }
113        let mut pkt = [0u8; NM_UP_SIZE];
114        pkt[0] = TS_SYNC_BYTE;
115        pkt[1..].copy_from_slice(&self.data[self.pos..self.pos + HEM_UP_SIZE]);
116        self.pos += stride;
117        Some(pkt)
118    }
119
120    fn size_hint(&self) -> (usize, Option<usize>) {
121        let stride = HEM_UP_SIZE + if self.npd { 1 } else { 0 };
122        let remaining = self.data.len().saturating_sub(self.pos);
123        let count = remaining / stride;
124        (count, Some(count))
125    }
126}
127
128/// Build an appropriate user-packet iterator for the given BBHEADER.
129///
130/// Returns either an NM or HEM iterator depending on the detected mode.
131/// The caller must handle SYNCD alignment before calling this — typically
132/// by skipping `syncd / 8` bytes at the start of the data field.
133pub fn up_iter<'a>(
134    data: &'a [u8],
135    bbheader: &Bbheader,
136) -> Box<dyn Iterator<Item = [u8; NM_UP_SIZE]> + 'a> {
137    match bbheader.mode {
138        Mode::Normal => Box::new(NmTsIter::new(data)),
139        Mode::HighEfficiency => Box::new(HemTsIter::new(data, bbheader.matype.npd)),
140    }
141}
142
143/// Stateful UP extractor that carries partial user packets across BBFrame
144/// boundaries — a single UP can span multiple frames, especially in HEM
145/// where stride=187 bytes.
146///
147/// Use `feed_nm` / `feed_hem` per received frame; the returned Vec holds
148/// whichever 188-byte TS packets completed during that frame.
149pub struct CarryOverExtractor {
150    /// Partial TS packet being assembled (sync byte position 0).
151    buf: [u8; NM_UP_SIZE],
152    /// Bytes already written into `buf`.
153    pos: usize,
154}
155
156impl Default for CarryOverExtractor {
157    fn default() -> Self {
158        Self {
159            buf: [0u8; NM_UP_SIZE],
160            pos: 0,
161        }
162    }
163}
164
165impl CarryOverExtractor {
166    /// Create a fresh extractor with no carried-over state.
167    pub fn new() -> Self {
168        Self::default()
169    }
170
171    /// Feed a HEM BBFrame's header + data field. Returns any TS packets that
172    /// completed during this frame.
173    ///
174    /// `npd` is the MATYPE-1 NPD flag for the frame — when true the stream
175    /// would additionally carry DNP bytes between UPs. NPD reinsertion is
176    /// NOT YET implemented here; callers must not pass `npd=true` until
177    /// the DNP path lands.
178    pub fn feed_hem(
179        &mut self,
180        bbheader_bytes: &[u8; BBHEADER_LEN],
181        data_field: &[u8],
182        npd: bool,
183    ) -> Vec<[u8; NM_UP_SIZE]> {
184        assert!(
185            !npd,
186            "CarryOverExtractor does not yet implement NPD/DNP reinsertion"
187        );
188        let hdr = match Bbheader::parse(bbheader_bytes) {
189            Ok(h) => h,
190            Err(_) => return Vec::new(),
191        };
192        assert_eq!(
193            hdr.mode,
194            Mode::HighEfficiency,
195            "feed_hem called on non-HEM header"
196        );
197
198        let stride = HEM_UP_SIZE;
199        let syncd_bytes = (hdr.syncd / 8) as usize;
200        let dfl_bytes = (hdr.dfl / 8) as usize;
201        let data = &data_field[..dfl_bytes.min(data_field.len())];
202
203        let mut out = Vec::new();
204
205        // Complete the partial UP from the previous frame.
206        if self.pos > 0 {
207            let need = stride + 1 - self.pos; // +1 for the sync byte we'll prepend
208            if syncd_bytes == need && data.len() >= need {
209                self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
210                self.buf[0] = 0x47;
211                out.push(self.buf);
212                self.pos = 0;
213            } else {
214                // Stride mismatch — discard partial and resync to syncd.
215                self.pos = 0;
216            }
217        }
218
219        // Extract complete UPs at stride.
220        let mut i = syncd_bytes;
221        while i + stride <= data.len() {
222            // HEM: 187 bytes of UP, prepend 0x47.
223            self.buf[0] = 0x47;
224            self.buf[1..1 + stride].copy_from_slice(&data[i..i + stride]);
225            out.push(self.buf);
226            i += stride;
227        }
228
229        // Buffer trailing partial packet (may be filled by next frame).
230        if i < data.len() {
231            let tail = (data.len() - i).min(stride);
232            // Store at offset 1 (reserving byte 0 for the sync we prepend later).
233            self.buf[1..1 + tail].copy_from_slice(&data[i..i + tail]);
234            self.pos = 1 + tail;
235        } else {
236            self.pos = 0;
237        }
238
239        out
240    }
241
242    /// Feed an NM BBFrame. Stride=188, `byte[0]` of each UP is the CRC-8 of
243    /// the previous UP and gets replaced with 0x47.
244    pub fn feed_nm(
245        &mut self,
246        bbheader_bytes: &[u8; BBHEADER_LEN],
247        data_field: &[u8],
248    ) -> Vec<[u8; NM_UP_SIZE]> {
249        let hdr = match Bbheader::parse(bbheader_bytes) {
250            Ok(h) => h,
251            Err(_) => return Vec::new(),
252        };
253        assert_eq!(hdr.mode, Mode::Normal, "feed_nm called on non-NM header");
254
255        let stride = NM_UP_SIZE;
256        let syncd_bytes = (hdr.syncd / 8) as usize;
257        let dfl_bytes = (hdr.dfl / 8) as usize;
258        let data = &data_field[..dfl_bytes.min(data_field.len())];
259
260        let mut out = Vec::new();
261
262        // Complete partial UP from previous frame.
263        if self.pos > 0 {
264            let need = stride - self.pos;
265            if syncd_bytes == need && data.len() >= need {
266                self.buf[self.pos..self.pos + need].copy_from_slice(&data[..need]);
267                self.buf[0] = 0x47; // replace CRC-8 with sync byte
268                out.push(self.buf);
269                self.pos = 0;
270            } else {
271                self.pos = 0;
272            }
273        }
274
275        // Extract complete UPs at stride.
276        let mut i = syncd_bytes;
277        while i + stride <= data.len() {
278            self.buf.copy_from_slice(&data[i..i + stride]);
279            self.buf[0] = 0x47; // replace CRC-8 with sync byte
280            out.push(self.buf);
281            i += stride;
282        }
283
284        // Buffer trailing partial.
285        if i < data.len() {
286            let tail = (data.len() - i).min(stride);
287            self.buf[..tail].copy_from_slice(&data[i..i + tail]);
288            self.pos = tail;
289        } else {
290            self.pos = 0;
291        }
292
293        out
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::header::{Bbheader, Matype, Mode, TsGs};
301
302    fn make_nm_header(syncd: u16) -> Bbheader {
303        Bbheader {
304            matype: Matype {
305                ts_gs: TsGs::Ts,
306                sis: true,
307                ccm: true,
308                issyi: false,
309                npd: false,
310                ext: 0,
311                isi: 0,
312            },
313            upl: 0,
314            sync: 0x47,
315            dfl: 0,
316            syncd,
317            mode: Mode::Normal,
318            issy_in_header: None,
319        }
320    }
321
322    fn make_hem_header(npd: bool) -> Bbheader {
323        Bbheader {
324            matype: Matype {
325                ts_gs: TsGs::Ts,
326                sis: true,
327                ccm: true,
328                issyi: false,
329                npd,
330                ext: 0,
331                isi: 0,
332            },
333            upl: 0,
334            sync: 0,
335            dfl: 0,
336            syncd: 0,
337            mode: Mode::HighEfficiency,
338            issy_in_header: None,
339        }
340    }
341
342    #[test]
343    fn nm_extracts_single_complete_up() {
344        let mut data = vec![0xAA; NM_UP_SIZE];
345        data[0] = 0xFF; // CRC-8 byte (will be replaced with sync)
346        for (i, byte) in data.iter_mut().enumerate().skip(1) {
347            *byte = i as u8;
348        }
349
350        let _hdr = make_nm_header(0);
351        let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
352
353        assert_eq!(pkts.len(), 1);
354        assert_eq!(pkts[0][0], TS_SYNC_BYTE);
355        assert_eq!(&pkts[0][1..], &data[1..]);
356    }
357
358    #[test]
359    fn nm_multiple_back_to_back_ups() {
360        let num_ups = 3;
361        let mut data = Vec::with_capacity(num_ups * NM_UP_SIZE);
362
363        for i in 0..num_ups {
364            data.push(0x00); // CRC-8 byte
365            for j in 1..NM_UP_SIZE {
366                data.push((i * 10 + j) as u8);
367            }
368        }
369
370        let _hdr = make_nm_header(0);
371        let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
372
373        assert_eq!(pkts.len(), num_ups);
374        for pkt in &pkts {
375            assert_eq!(pkt[0], TS_SYNC_BYTE);
376        }
377    }
378
379    #[test]
380    fn nm_partial_tail_does_not_yield() {
381        let mut data = vec![0xAA; NM_UP_SIZE + 50];
382        data[0] = 0xFF; // CRC-8
383        for (i, byte) in data.iter_mut().enumerate().skip(1) {
384            *byte = i as u8;
385        }
386
387        let _hdr = make_nm_header(0);
388        let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
389
390        assert_eq!(pkts.len(), 1); // Only one complete UP
391    }
392
393    #[test]
394    fn nm_crc_byte_replaced_with_sync_only() {
395        let mut data = vec![0u8; NM_UP_SIZE];
396        data[0] = 0x42; // Some CRC value
397        data[1] = 0x47; // Actual sync byte in payload position 1
398        for (i, byte) in data.iter_mut().enumerate().skip(2) {
399            *byte = i as u8;
400        }
401
402        let _hdr = make_nm_header(0);
403        let pkt = up_iter(&data, &_hdr).next().unwrap();
404
405        assert_eq!(pkt[0], TS_SYNC_BYTE); // CRC replaced
406        assert_eq!(pkt[1], 0x47); // Original byte preserved
407        assert_eq!(&pkt[2..], &data[2..]);
408    }
409
410    #[test]
411    fn hem_extracts_up_with_sync_prepend() {
412        let data: Vec<u8> = (0..HEM_UP_SIZE as u8).cycle().take(HEM_UP_SIZE).collect();
413        let mut expected = [0u8; NM_UP_SIZE];
414        expected[0] = TS_SYNC_BYTE;
415        expected[1..].copy_from_slice(&data[..HEM_UP_SIZE]);
416
417        let _hdr = make_hem_header(false);
418        let pkt = up_iter(&data, &_hdr).next().unwrap();
419
420        assert_eq!(pkt, expected);
421    }
422
423    #[test]
424    fn hem_multiple_ups_without_npd() {
425        let num_ups = 3;
426        let data = vec![0xAB; num_ups * HEM_UP_SIZE];
427        let expected: [u8; NM_UP_SIZE] = {
428            let mut e = [0xAB; NM_UP_SIZE];
429            e[0] = TS_SYNC_BYTE;
430            e
431        };
432
433        let _hdr = make_hem_header(false);
434        let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
435
436        assert_eq!(pkts.len(), num_ups);
437        for pkt in &pkts {
438            assert_eq!(*pkt, expected);
439        }
440    }
441
442    #[test]
443    fn hem_with_npd_skips_dnp_bytes() {
444        let up_size = HEM_UP_SIZE;
445        let num_ups = 2;
446        // Each UP followed by a DNP byte
447        let stride = up_size + 1;
448        let mut data = Vec::with_capacity(num_ups * stride);
449
450        for i in 0..num_ups {
451            for j in 0..up_size {
452                data.push((i * up_size + j) as u8);
453            }
454            data.push(i as u8); // DNP counter
455        }
456
457        let _hdr = make_hem_header(true);
458        let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
459
460        assert_eq!(pkts.len(), num_ups);
461        for (i, pkt) in pkts.iter().enumerate() {
462            assert_eq!(pkt[0], TS_SYNC_BYTE);
463            // Verify the 187 bytes match the expected slice
464            let offset = i * stride;
465            assert_eq!(&pkt[1..], &data[offset..offset + up_size]);
466        }
467    }
468
469    #[test]
470    fn nm_remaining_returns_unconsumed_tail() {
471        let data = vec![0xAA; NM_UP_SIZE * 2 + 50];
472        let _hdr = make_nm_header(0);
473        let mut iter = NmTsIter::new(&data);
474
475        let _p1 = iter.next().unwrap();
476        let _p2 = iter.next().unwrap();
477        let remaining = iter.remaining();
478
479        assert_eq!(remaining.len(), 50);
480    }
481
482    #[test]
483    fn hem_remaining_returns_unconsumed_tail() {
484        let data = vec![0xAA; HEM_UP_SIZE * 2 + 30];
485        let _hdr = make_hem_header(false);
486        let mut iter = HemTsIter::new(&data, false);
487
488        let _p1 = iter.next().unwrap();
489        let _p2 = iter.next().unwrap();
490        let remaining = iter.remaining();
491
492        assert_eq!(remaining.len(), 30);
493    }
494
495    #[test]
496    fn empty_data_yields_nothing() {
497        let _hdr = make_nm_header(0);
498        let pkts: Vec<_> = up_iter(&[], &_hdr).collect();
499        assert!(pkts.is_empty());
500    }
501
502    #[test]
503    fn data_shorter_than_one_up_yields_nothing() {
504        let data = vec![0xAA; 100]; // Less than NM_UP_SIZE or HEM_UP_SIZE
505        let _hdr = make_nm_header(0);
506        let pkts: Vec<_> = up_iter(&data, &_hdr).collect();
507        assert!(pkts.is_empty());
508    }
509
510    #[test]
511    fn carry_over_extractor_emits_ts_across_two_bbframes_hem() {
512        // Two HEM BBFrames where the first ends with a partial UP (70 bytes) and
513        // the second completes it (117 bytes) then starts a new UP.
514        let make_hem_header = |syncd_bits: u16, dfl_bits: u16| -> [u8; 10] {
515            let mut h = [0u8; 10];
516            // MATYPE-1: TS=0b11 → 0xC0, SIS=1 → 0x20, CCM=1 → 0x10, ISSYI=0, NPD=0, EXT=0
517            h[0] = 0xF0;
518            h[1] = 0x00; // ISI=0
519                         // UPL=0 (ignored in HEM)
520            h[2] = 0x00;
521            h[3] = 0x00;
522            h[4] = (dfl_bits >> 8) as u8;
523            h[5] = (dfl_bits & 0xFF) as u8;
524            h[6] = 0x00; // sync (ignored in HEM)
525            h[7] = (syncd_bits >> 8) as u8;
526            h[8] = (syncd_bits & 0xFF) as u8;
527            // byte 9 = CRC-8 XOR MODE with MODE=1 for HEM
528            let crc = crate::crc::crc8(&h[..9]);
529            h[9] = crc ^ 1;
530            h
531        };
532
533        // Two BBFrames, each 70 data bytes. Pattern: each data byte is (frame << 4) | offset_lo.
534        // We expect CarryOverExtractor to produce 0 packets on frame1 (partial tail),
535        // then 1 on frame2 (187-byte completion across the boundary).
536        let frame1_data = (0..70u8).map(|i| 0xA0 | (i & 0x0F)).collect::<Vec<u8>>();
537        let frame2_data = (0..200u8).map(|i| 0xB0 | (i & 0x0F)).collect::<Vec<u8>>();
538        let hdr1 = make_hem_header(0, (frame1_data.len() * 8) as u16);
539        let hdr2 = make_hem_header(0, (frame2_data.len() * 8) as u16);
540
541        let mut extractor = CarryOverExtractor::new();
542        let packets1 = extractor.feed_hem(&hdr1, &frame1_data, false);
543        assert_eq!(packets1.len(), 0, "70 bytes (< 187) must not yet emit a UP");
544
545        let packets2 = extractor.feed_hem(&hdr2, &frame2_data, false);
546        assert!(!packets2.is_empty(), "boundary UP should complete");
547        assert_eq!(
548            packets2[0][0], 0x47,
549            "first emitted packet has sync byte prepended"
550        );
551    }
552}