Skip to main content

dvb_t2mi/payload/
l1_future.rs

1//! T2-MI payload type 0x11: L1-future signalling — §5.2.5.
2//!
3//! L1-future carries "dynamic, next frame" and "dynamic, next-but-one"
4//! signalling data. Per §5.5, this is always the **last** T2-MI packet
5//! for a given `frame_idx` (if used in current frame).
6
7use dvb_common::{Parse, Serialize};
8
9/// L1-future payload (type 0x11) per ETSI TS 102 773 §5.2.5.
10///
11/// Layout:
12/// - byte 0: frame_idx (8 bits)
13/// - byte 1: rfu (8 bits) — must be 0
14/// - bytes 2..: l1_future_data (variable)
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize))]
17#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
18pub struct L1FuturePayload<'a> {
19    /// FRAME_IDX of T2 frame.
20    pub frame_idx: u8,
21    /// L1-future data containing L1DYN_NEXT, L1DYN_NEXT2, and optional in-band loops.
22    pub l1_future_data: &'a [u8],
23}
24
25const L1_FUTURE_HEADER_LEN: usize = 2;
26
27impl<'a> Parse<'a> for L1FuturePayload<'a> {
28    type Error = crate::error::Error;
29
30    fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
31        if bytes.len() < L1_FUTURE_HEADER_LEN {
32            return Err(crate::Error::BufferTooShort {
33                need: L1_FUTURE_HEADER_LEN,
34                have: bytes.len(),
35                what: "L1FuturePayload header",
36            });
37        }
38
39        if bytes[1] != 0 {
40            return Err(crate::Error::ReservedBitsViolation {
41                field: "byte 1 RFU",
42                reason: "Must be zero (ETSI TS 102 773 §5.2.5)",
43            });
44        }
45
46        Ok(L1FuturePayload {
47            frame_idx: bytes[0],
48            l1_future_data: &bytes[L1_FUTURE_HEADER_LEN..],
49        })
50    }
51}
52
53impl<'a> crate::traits::PayloadDef<'a> for L1FuturePayload<'a> {
54    const PACKET_TYPE: u8 = 0x11;
55    const NAME: &'static str = "L1_FUTURE";
56}
57
58impl Serialize for L1FuturePayload<'_> {
59    type Error = crate::error::Error;
60
61    fn serialized_len(&self) -> usize {
62        L1_FUTURE_HEADER_LEN + self.l1_future_data.len()
63    }
64
65    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
66        if buf.len() < self.serialized_len() {
67            return Err(crate::Error::OutputBufferTooSmall {
68                need: self.serialized_len(),
69                have: buf.len(),
70            });
71        }
72
73        buf[0] = self.frame_idx;
74        buf[1] = 0; // RFU
75
76        if !self.l1_future_data.is_empty() {
77            buf[L1_FUTURE_HEADER_LEN..L1_FUTURE_HEADER_LEN + self.l1_future_data.len()]
78                .copy_from_slice(self.l1_future_data);
79        }
80
81        Ok(self.serialized_len())
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn parse_extracts_frame_idx() {
91        let buf = [0xABu8, 0x00, 0x12, 0x34];
92        let result = L1FuturePayload::parse(&buf).unwrap();
93        assert_eq!(result.frame_idx, 0xAB);
94        assert_eq!(result.l1_future_data, &[0x12, 0x34]);
95    }
96
97    #[test]
98    fn parse_rejects_nonzero_rfu() {
99        let buf = [0x00u8, 0x01, 0x00];
100        assert!(L1FuturePayload::parse(&buf).is_err());
101    }
102
103    #[test]
104    fn parse_rejects_short_buffer() {
105        assert!(L1FuturePayload::parse(&[0x00]).is_err());
106    }
107
108    #[test]
109    fn serialize_round_trip() {
110        let orig = L1FuturePayload {
111            frame_idx: 0x42,
112            l1_future_data: &[0xCA, 0xFE, 0xBA, 0xBE],
113        };
114        let mut buf = vec![0u8; orig.serialized_len()];
115        orig.serialize_into(&mut buf).unwrap();
116        let parsed = L1FuturePayload::parse(&buf).unwrap();
117        assert_eq!(orig, parsed);
118    }
119
120    #[test]
121    fn empty_future_data() {
122        let buf = [0x05u8, 0x00];
123        let result = L1FuturePayload::parse(&buf).unwrap();
124        assert!(result.l1_future_data.is_empty());
125    }
126}