Skip to main content

dvb_t2mi/payload/
fef_composite.rs

1//! T2-MI payload type 0x32: FEF part — composite — §5.2.11.
2//!
3//! Carries composition information for FEF parts. The actual sub-parts
4//! are delivered via type 0x33 packets.
5
6use std::fmt;
7
8use dvb_common::{Parse, Serialize};
9
10use super::fef_null::S1Field;
11
12/// FEF part: composite payload (type 0x32) per ETSI TS 102 773 §5.2.11.
13///
14/// Layout (Figure 15):
15/// - byte 0: fef_idx (8 bits)
16/// - byte 1 \[7\]: rfu1 (1 bit) — must be 0
17/// - byte 1 \[6:4\]: s1_field (3 bits)
18/// - byte 1 \[3:0\]: s2_field (4 bits)
19/// - bytes 2-5: rfu2 (32 bits) — must be 0
20/// - bytes 6-7: num_subparts (16 bits)
21#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23pub struct FefCompositePayload {
24    /// FEF index within super-frame.
25    pub fef_idx: u8,
26    /// S1 field per EN 302 755 §7.2.1.
27    pub s1_field: S1Field,
28    /// S2 field per EN 302 755 §7.2.1.
29    pub s2_field: u8,
30    /// Total number of sub-parts P.
31    pub num_subparts: u16,
32}
33
34const FEF_COMPOSITE_LEN: usize = 8;
35
36impl<'a> Parse<'a> for FefCompositePayload {
37    type Error = crate::error::Error;
38
39    fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
40        if bytes.len() < FEF_COMPOSITE_LEN {
41            return Err(crate::Error::BufferTooShort {
42                need: FEF_COMPOSITE_LEN,
43                have: bytes.len(),
44                what: "FefCompositePayload header",
45            });
46        }
47
48        // byte 1 [7]: rfu1 — must be 0
49        if bytes[1] & 0x80 != 0 {
50            return Err(crate::Error::ReservedBitsViolation {
51                field: "byte 1 [7] rfu1",
52                reason: "Must be zero (ETSI TS 102 773 §5.2.11)",
53            });
54        }
55        // bytes 2-5: 32-bit rfu2 — must be 0
56        if bytes[2] != 0 || bytes[3] != 0 || bytes[4] != 0 || bytes[5] != 0 {
57            return Err(crate::Error::ReservedBitsViolation {
58                field: "32-bit RFU (rfu2)",
59                reason: "Must be zero (ETSI TS 102 773 §5.2.11)",
60            });
61        }
62
63        Ok(FefCompositePayload {
64            fef_idx: bytes[0],
65            s1_field: S1Field::try_from((bytes[1] >> 4) & 0x07)?,
66            s2_field: bytes[1] & 0x0F,
67            num_subparts: u16::from_be_bytes([bytes[6], bytes[7]]),
68        })
69    }
70}
71
72impl<'a> crate::traits::PayloadDef<'a> for FefCompositePayload {
73    const PACKET_TYPE: u8 = 0x32;
74    const NAME: &'static str = "FEF_COMPOSITE";
75}
76
77impl Serialize for FefCompositePayload {
78    type Error = crate::error::Error;
79
80    fn serialized_len(&self) -> usize {
81        FEF_COMPOSITE_LEN
82    }
83
84    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
85        if buf.len() < self.serialized_len() {
86            return Err(crate::Error::OutputBufferTooSmall {
87                need: self.serialized_len(),
88                have: buf.len(),
89            });
90        }
91
92        if self.s2_field > 0x0F {
93            return Err(crate::Error::ReservedBitsViolation {
94                field: "s2_field",
95                reason: "Must fit in 4 bits",
96            });
97        }
98
99        buf[0] = self.fef_idx;
100        buf[1] = ((u8::from(self.s1_field) << 4) & 0x70) | (self.s2_field & 0x0F);
101        // rfu2 = 0
102        buf[2] = 0;
103        buf[3] = 0;
104        buf[4] = 0;
105        buf[5] = 0;
106        let ns = self.num_subparts.to_be_bytes();
107        buf[6] = ns[0];
108        buf[7] = ns[1];
109
110        Ok(self.serialized_len())
111    }
112}
113
114impl fmt::Display for FefCompositePayload {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(
117            f,
118            "FEF Composite {{ fef_idx: {}, s1: {:?}, s2: {:04b}, subparts: {} }}",
119            self.fef_idx, self.s1_field, self.s2_field, self.num_subparts
120        )
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn parse_extracts_fields() {
130        let buf = [0x03u8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05];
131        let result = FefCompositePayload::parse(&buf).unwrap();
132        assert_eq!(result.fef_idx, 3);
133        assert_eq!(result.s1_field, S1Field::V1);
134        assert_eq!(result.s2_field, 0);
135        assert_eq!(result.num_subparts, 5);
136    }
137
138    #[test]
139    fn parse_rejects_nonzero_rfu1() {
140        let buf = [0x00u8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
141        assert!(FefCompositePayload::parse(&buf).is_err());
142    }
143
144    #[test]
145    fn parse_rejects_nonzero_32bit_rfu2() {
146        let buf = [0x00u8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00];
147        assert!(FefCompositePayload::parse(&buf).is_err());
148    }
149
150    #[test]
151    fn parse_rejects_short_buffer() {
152        let buf = [0x00u8; 7];
153        assert!(FefCompositePayload::parse(&buf).is_err());
154    }
155
156    #[test]
157    fn serialize_round_trip() {
158        let orig = FefCompositePayload {
159            fef_idx: 10,
160            s1_field: S1Field::V7,
161            s2_field: 0x0F,
162            num_subparts: 100,
163        };
164        let mut buf = [0u8; FEF_COMPOSITE_LEN];
165        orig.serialize_into(&mut buf).unwrap();
166        let parsed = FefCompositePayload::parse(&buf).unwrap();
167        assert_eq!(orig, parsed);
168    }
169
170    #[test]
171    fn serialize_rejects_too_small_buffer() {
172        let payload = FefCompositePayload {
173            fef_idx: 0,
174            s1_field: S1Field::V0,
175            s2_field: 0,
176            num_subparts: 0,
177        };
178        let mut buf = [0u8; 7];
179        assert!(payload.serialize_into(&mut buf).is_err());
180    }
181
182    #[test]
183    fn serialize_zeros_rfu_bits() {
184        let payload = FefCompositePayload {
185            fef_idx: 1,
186            s1_field: S1Field::V0,
187            s2_field: 0,
188            num_subparts: 1,
189        };
190        let mut buf = [0xFFu8; FEF_COMPOSITE_LEN];
191        payload.serialize_into(&mut buf).unwrap();
192        assert_eq!(buf[1] & 0x80, 0); // rfu1
193        assert_eq!(&buf[2..6], &[0u8; 4]); // rfu2
194    }
195}