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