Skip to main content

dvb_t2mi/
packet.rs

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