Skip to main content

dvb_t2mi/
packet.rs

1//! T2-MI packet header and type parsing.
2
3use num_enum::TryFromPrimitive;
4
5/// T2-MI packet header size (ETSI TS 102 773 §5.1).
6const HEADER_LEN: usize = 6;
7
8/// Packet types per ETSI TS 102 773 Table 1.
9///
10/// Reserved for future use: `0x22..=0x2F`, `0x34..=0xFF`.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize))]
13#[repr(u8)]
14#[non_exhaustive]
15pub enum PacketType {
16    /// Baseband Frame (BBFRAME) — §5.2.1
17    BasebandFrame = 0x00,
18    /// Auxiliary stream I/Q data — §5.2.2
19    AuxiliaryIqData = 0x01,
20    /// Arbitrary cell insertion — §5.2.3
21    ArbitraryCellInsertion = 0x02,
22    /// L1-current signalling — §5.2.4
23    L1Current = 0x10,
24    /// L1-future signalling — §5.2.5
25    L1Future = 0x11,
26    /// P2 bias balancing cells — §5.2.6
27    P2BiasBalancing = 0x12,
28    /// DVB-T2 timestamp — §5.2.7
29    Timestamp = 0x20,
30    /// Individual addressing — §5.2.8
31    IndividualAddressing = 0x21,
32    /// FEF part: Null — §5.2.9
33    FefPartNull = 0x30,
34    /// FEF part: I/Q data — §5.2.10
35    FefPartIqData = 0x31,
36    /// FEF part: composite — §5.2.11
37    FefPartComposite = 0x32,
38    /// FEF sub-part — §5.2.12
39    FefSubPart = 0x33,
40}
41
42impl From<PacketType> for u8 {
43    fn from(pt: PacketType) -> Self {
44        pt as u8
45    }
46}
47
48impl From<num_enum::TryFromPrimitiveError<PacketType>> for crate::error::Error {
49    fn from(e: num_enum::TryFromPrimitiveError<PacketType>) -> Self {
50        crate::error::Error::InvalidPacketType { found: e.number }
51    }
52}
53
54/// T2-MI packet header (6 bytes / 48 bits) per §5.1.
55///
56/// Layout:
57/// - byte 0: packet_type (8 bits) — Table 1
58/// - byte 1: packet_count (8 bits) — wraps 0xFF→0x00, arbitrary start
59/// - byte 2 `[7:4]`: superframe_idx (4 bits)
60/// - byte 2 `[3]`: rfu (1 bit) — must be 0
61/// - byte 2 `[2:0]`: t2mi_stream_id (3 bits)
62/// - byte 3: rfu (8 bits) — must be 0
63/// - byte 4-5: payload_len (16 bits, unit = bits)
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize))]
66pub struct Header {
67    /// Table 1 packet type.
68    pub packet_type: PacketType,
69    /// Wraps 0xFF→0x00; arbitrary start value.
70    pub packet_count: u8,
71    /// Super-frame index [0..=15].
72    pub superframe_idx: u8,
73    /// T2-MI stream ID [0..=7].
74    pub t2mi_stream_id: u8,
75    /// Payload length in bits (not bytes).
76    pub payload_len_bits: u16,
77}
78
79impl<'a> dvb_common::Parse<'a> for Header {
80    type Error = crate::error::Error;
81
82    fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
83        use super::error::Error;
84
85        let len = bytes.len();
86        if len < HEADER_LEN {
87            return Err(Error::BufferTooShort {
88                need: HEADER_LEN,
89                have: len,
90                what: "T2MI Header",
91            });
92        }
93
94        let packet_type = PacketType::try_from(bytes[0])?;
95        let packet_count = bytes[1];
96
97        // byte 2 [7:4] = superframe_idx
98        let superframe_idx = (bytes[2] >> 4) & 0x0F;
99
100        // byte 2 [3] = rfu — spec says must be 0
101        if bytes[2] & 0x08 != 0 {
102            return Err(Error::ReservedBitsViolation {
103                field: "byte 2 bit 3",
104                reason: "RFU must be zero (ETSI TS 102 773 §5.1)",
105            });
106        }
107
108        // byte 2 [2:0] = t2mi_stream_id
109        let t2mi_stream_id = bytes[2] & 0x07;
110
111        // byte 3 = rfu — spec says must be 0
112        if bytes[3] != 0 {
113            return Err(Error::ReservedBitsViolation {
114                field: "byte 3",
115                reason: "All 8 RFU bits must be zero (ETSI TS 102 773 §5.1)",
116            });
117        }
118
119        let payload_len_bits = u16::from_be_bytes([bytes[4], bytes[5]]);
120
121        Ok(Header {
122            packet_type,
123            packet_count,
124            superframe_idx,
125            t2mi_stream_id,
126            payload_len_bits,
127        })
128    }
129}
130
131impl Header {
132    /// Compute payload length in bytes (ceil(bits / 8)).
133    #[must_use]
134    pub fn payload_len_bytes(&self) -> usize {
135        (self.payload_len_bits as usize).div_ceil(8)
136    }
137
138    /// Total packet size including header + payload + CRC-32 trailer, in bytes.
139    ///
140    /// = 6 (header) + ceil(payload_len_bits / 8) (payload + padding) + 4 (CRC).
141    #[must_use]
142    pub fn total_bytes(&self) -> usize {
143        HEADER_LEN + self.payload_len_bytes() + super::crc::CRC_LEN
144    }
145
146    /// Slice the payload bytes out of a raw T2-MI packet buffer without
147    /// requiring the `packet_type` byte to be a known [`PacketType`].
148    ///
149    /// Reads `payload_len_bits` from bytes `[4..6]`, computes
150    /// `ceil(payload_len_bits / 8)`, and returns
151    /// `packet[HEADER_LEN..HEADER_LEN + payload_len_bytes]`.
152    ///
153    /// # Errors
154    ///
155    /// Returns [`Error::BufferTooShort`](crate::error::Error::BufferTooShort)
156    /// if `packet.len() < HEADER_LEN`.
157    /// Returns [`Error::PayloadLengthMismatch`](crate::error::Error::PayloadLengthMismatch)
158    /// if the buffer is too short for the declared `payload_len_bits`.
159    pub fn raw_payload_bytes(packet: &[u8]) -> Result<&[u8], crate::error::Error> {
160        if packet.len() < HEADER_LEN {
161            return Err(crate::error::Error::BufferTooShort {
162                need: HEADER_LEN,
163                have: packet.len(),
164                what: "T2MI Header",
165            });
166        }
167        let payload_len_bits = u16::from_be_bytes([packet[4], packet[5]]);
168        let payload_len_bytes = (payload_len_bits as usize).div_ceil(8);
169        let end = HEADER_LEN + payload_len_bytes;
170        if packet.len() < end {
171            return Err(crate::error::Error::PayloadLengthMismatch {
172                declared_bits: payload_len_bits,
173                remaining_bytes: packet.len().saturating_sub(HEADER_LEN),
174            });
175        }
176        Ok(&packet[HEADER_LEN..end])
177    }
178
179    /// Slice the payload bytes out of the full packet buffer this header was
180    /// parsed from (the 6 header bytes, then `payload_len_bytes()` of payload,
181    /// then the 4-byte CRC trailer).
182    ///
183    /// Errors with [`Error::PayloadLengthMismatch`](crate::error::Error::PayloadLengthMismatch) when the buffer is too
184    /// short for the declared `payload_len_bits`.
185    pub fn payload_bytes<'a>(&self, packet: &'a [u8]) -> Result<&'a [u8], crate::error::Error> {
186        let end = HEADER_LEN + self.payload_len_bytes();
187        if packet.len() < end {
188            return Err(crate::error::Error::PayloadLengthMismatch {
189                declared_bits: self.payload_len_bits,
190                remaining_bytes: packet.len().saturating_sub(HEADER_LEN),
191            });
192        }
193        Ok(&packet[HEADER_LEN..end])
194    }
195}
196
197impl dvb_common::Serialize for Header {
198    type Error = crate::error::Error;
199
200    fn serialized_len(&self) -> usize {
201        HEADER_LEN
202    }
203
204    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
205        use super::error::Error;
206
207        if buf.len() < HEADER_LEN {
208            return Err(Error::OutputBufferTooSmall {
209                need: HEADER_LEN,
210                have: buf.len(),
211            });
212        }
213
214        if self.t2mi_stream_id > 7 {
215            return Err(Error::ReservedBitsViolation {
216                field: "t2mi_stream_id",
217                reason: "Must be in range 0..=7 (3-bit field)",
218            });
219        }
220
221        buf[0] = self.packet_type.into();
222        buf[1] = self.packet_count;
223        buf[2] = (self.superframe_idx & 0x0F) << 4 | (self.t2mi_stream_id & 0x07);
224        buf[3] = 0; // RFU = 0
225        let len_be = self.payload_len_bits.to_be_bytes();
226        buf[4] = len_be[0];
227        buf[5] = len_be[1];
228
229        Ok(HEADER_LEN)
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236    use dvb_common::{Parse, Serialize};
237
238    #[test]
239    fn packet_type_try_from_all_valid() {
240        // Table 1 — every defined type must round-trip
241        let valid_types = [
242            0x00, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
243        ];
244        for v in valid_types {
245            let result = PacketType::try_from(v);
246            assert!(result.is_ok(), "PacketType::try_from({:#04x}) failed", v);
247        }
248    }
249
250    #[test]
251    fn packet_type_rejects_reserved() {
252        // 0x22..=0x2F and 0x34..=0xFF are RFU
253        for v in 0x22..=0x2F {
254            assert!(
255                PacketType::try_from(v).is_err(),
256                "0x{v:02x} should be rejected"
257            );
258        }
259        for v in 0x34..=0xFF {
260            assert!(
261                PacketType::try_from(v).is_err(),
262                "0x{v:02x} should be rejected"
263            );
264        }
265    }
266
267    #[test]
268    fn exhaustive_byte_sweep() {
269        let mut matched = 0u16;
270        for byte in 0u8..=0xFF {
271            if let Ok(v) = PacketType::try_from(byte) {
272                assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
273                matched += 1;
274            }
275        }
276        assert_eq!(matched, 12, "expected 12 matched variants");
277    }
278
279    #[test]
280    fn parse_rejects_buffer_shorter_than_6() {
281        let buf = [0x00u8; 5];
282        let result = Header::parse(&buf);
283        assert!(result.is_err());
284        let err = result.unwrap_err();
285        if let crate::Error::BufferTooShort { need, have, what } = err {
286            assert_eq!(need, HEADER_LEN);
287            assert_eq!(have, 5);
288            assert_eq!(what, "T2MI Header");
289        } else {
290            panic!("Expected BufferTooShort, got {:?}", err);
291        }
292    }
293
294    #[test]
295    fn parse_extracts_packet_type_and_count() {
296        let buf = [0x10u8, 0xAB, 0x00, 0x00, 0x00, 0x08];
297        let hdr = Header::parse(&buf).unwrap();
298        assert_eq!(hdr.packet_type, PacketType::L1Current);
299        assert_eq!(hdr.packet_count, 0xAB);
300    }
301
302    #[test]
303    fn parse_extracts_superframe_idx() {
304        let buf = [0x00u8, 0x00, 0x50, 0x00, 0x00, 0x08];
305        let hdr = Header::parse(&buf).unwrap();
306        assert_eq!(hdr.superframe_idx, 5);
307    }
308
309    #[test]
310    fn parse_accepts_all_defined_packet_types() {
311        let types = [
312            0x00u8, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
313        ];
314        for &t in &types {
315            let buf = [t, 0x00, 0x00, 0x00, 0x00, 0x08];
316            let result = Header::parse(&buf);
317            assert!(
318                result.is_ok(),
319                "parse failed for packet_type {:#04x}: {:?}",
320                t,
321                result
322            );
323            assert_eq!(
324                result.unwrap().packet_type,
325                PacketType::try_from(t).unwrap()
326            );
327        }
328    }
329
330    #[test]
331    fn parse_rejects_reserved_packet_type_0x22() {
332        let buf = [0x22u8, 0x00, 0x00, 0x00, 0x00, 0x08];
333        let result = Header::parse(&buf);
334        assert!(result.is_err());
335        assert!(matches!(
336            result.unwrap_err(),
337            crate::Error::InvalidPacketType { found: 0x22 }
338        ));
339    }
340
341    #[test]
342    fn parse_extracts_t2mi_stream_id_0_through_7() {
343        for id in 0..=7 {
344            let buf = [0x00u8, 0x00, id, 0x00, 0x00, 0x08];
345            let hdr = Header::parse(&buf).unwrap();
346            assert_eq!(hdr.t2mi_stream_id, id, "stream_id mismatch for id={}", id);
347        }
348    }
349
350    #[test]
351    fn parse_rejects_nonzero_rfu_bits_in_byte2() {
352        // byte 2 bit 3 (0x08) is rfu — must be 0
353        let buf = [0x00u8, 0x00, 0x08, 0x00, 0x00, 0x08];
354        let result = Header::parse(&buf);
355        assert!(result.is_err());
356        assert!(matches!(
357            result.unwrap_err(),
358            crate::Error::ReservedBitsViolation { .. }
359        ));
360    }
361
362    #[test]
363    fn parse_rejects_nonzero_byte3() {
364        let buf = [0x00u8, 0x00, 0x00, 0x01, 0x00, 0x08];
365        let result = Header::parse(&buf);
366        assert!(result.is_err());
367    }
368
369    #[test]
370    fn parse_extracts_payload_len_bits() {
371        let buf = [0x00u8, 0x00, 0x00, 0x00, 0x01, 0x00];
372        let hdr = Header::parse(&buf).unwrap();
373        assert_eq!(hdr.payload_len_bits, 0x0100);
374    }
375
376    #[test]
377    fn payload_len_bytes_rounds_up() {
378        let hdr = Header {
379            packet_type: PacketType::BasebandFrame,
380            packet_count: 0,
381            superframe_idx: 0,
382            t2mi_stream_id: 0,
383            payload_len_bits: 13,
384        };
385        assert_eq!(hdr.payload_len_bytes(), 2); // ceil(13/8) = 2
386
387        let hdr2 = Header {
388            payload_len_bits: 16,
389            ..hdr
390        };
391        assert_eq!(hdr2.payload_len_bytes(), 2);
392
393        let hdr3 = Header {
394            payload_len_bits: 0,
395            ..hdr
396        };
397        assert_eq!(hdr3.payload_len_bytes(), 0);
398    }
399
400    // ── Serialize tests ──
401
402    #[test]
403    fn serialize_writes_6_bytes() {
404        let hdr = Header {
405            packet_type: PacketType::BasebandFrame,
406            packet_count: 42,
407            superframe_idx: 7,
408            t2mi_stream_id: 3,
409            payload_len_bits: 128,
410        };
411        let mut buf = [0u8; 256];
412        let written = hdr.serialize_into(&mut buf).unwrap();
413        assert_eq!(written, HEADER_LEN);
414        assert_eq!(buf[0], 0x00);
415        assert_eq!(buf[1], 42);
416        assert_eq!(buf[2], (7 << 4) | 3);
417        assert_eq!(buf[3], 0);
418        assert_eq!(buf[4], 0);
419        assert_eq!(buf[5], 128);
420    }
421
422    #[test]
423    fn serialize_round_trip_identity_for_every_packet_type() {
424        let types = [
425            0x00u8, 0x01, 0x02, 0x10, 0x11, 0x12, 0x20, 0x21, 0x30, 0x31, 0x32, 0x33,
426        ];
427        for &t in &types {
428            let original = Header {
429                packet_type: PacketType::try_from(t).unwrap(),
430                packet_count: 13,
431                superframe_idx: 7,
432                t2mi_stream_id: 2,
433                payload_len_bits: 512,
434            };
435            let mut buf = [0u8; HEADER_LEN];
436            original.serialize_into(&mut buf).unwrap();
437            let parsed = Header::parse(&buf).unwrap();
438            assert_eq!(original, parsed, "Round-trip failed for type {:#04x}", t);
439        }
440    }
441
442    #[test]
443    fn serialize_rejects_too_small_buffer() {
444        let hdr = Header {
445            packet_type: PacketType::BasebandFrame,
446            packet_count: 0,
447            superframe_idx: 0,
448            t2mi_stream_id: 0,
449            payload_len_bits: 0,
450        };
451        let mut buf = [0u8; 5];
452        let result = hdr.serialize_into(&mut buf);
453        assert!(result.is_err());
454    }
455
456    #[test]
457    fn serialize_rejects_t2mi_stream_id_above_7() {
458        let hdr = Header {
459            packet_type: PacketType::BasebandFrame,
460            packet_count: 0,
461            superframe_idx: 0,
462            t2mi_stream_id: 8,
463            payload_len_bits: 0,
464        };
465        let mut buf = [0u8; HEADER_LEN];
466        let result = hdr.serialize_into(&mut buf);
467        assert!(result.is_err());
468    }
469
470    #[test]
471    fn payload_bytes_slices_declared_payload() {
472        // payload_len_bits = 24 → 3 payload bytes after the 6-byte header.
473        let buf = [
474            0x00, 0x01, 0x10, 0x00, 0x00, 0x18, 0xAA, 0xBB, 0xCC, 0x00, 0x00, 0x00, 0x00,
475        ];
476        let hdr = Header::parse(&buf).unwrap();
477        assert_eq!(hdr.payload_bytes(&buf).unwrap(), &[0xAA, 0xBB, 0xCC]);
478    }
479
480    #[test]
481    fn payload_bytes_rejects_truncated_buffer() {
482        // Declares 3 payload bytes but only 1 follows the header.
483        let buf = [0x00, 0x01, 0x10, 0x00, 0x00, 0x18, 0xAA];
484        let hdr = Header::parse(&buf).unwrap();
485        assert!(matches!(
486            hdr.payload_bytes(&buf),
487            Err(crate::Error::PayloadLengthMismatch { .. })
488        ));
489    }
490}