1use std::fmt;
12
13use num_enum::TryFromPrimitive;
14
15use dvb_common::{Parse, Serialize};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[repr(u16)]
21pub enum SubpartVariety {
22 Null = 0x0000,
24 Iq = 0x0001,
26 Prbs = 0x0002,
28 TxSigFef = 0x0003,
30}
31
32impl From<SubpartVariety> for u16 {
33 fn from(sv: SubpartVariety) -> Self {
34 sv as u16
35 }
36}
37
38impl From<num_enum::TryFromPrimitiveError<SubpartVariety>> for crate::error::Error {
39 fn from(_: num_enum::TryFromPrimitiveError<SubpartVariety>) -> Self {
40 crate::error::Error::ReservedBitsViolation {
43 field: "subpart_variety",
44 reason: "Must be 0x0000..=0x0003 per ETSI TS 102 773 §5.2.12 Table 13",
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52#[repr(u8)]
53pub enum PrbsType {
54 UserDefined = 0x00,
56}
57
58impl From<PrbsType> for u8 {
59 fn from(pt: PrbsType) -> Self {
60 pt as u8
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub struct FefSubPartPayload<'a> {
77 pub fef_idx: u8,
79 pub tx_identifier: u16,
81 pub subpart_idx: u16,
83 pub subpart_variety: SubpartVariety,
85 pub subpart_length: u32,
87 #[cfg_attr(feature = "serde", serde(borrow))]
89 pub subpart_data: &'a [u8],
90}
91
92const FEF_SUBPART_HEADER_LEN: usize = 15;
95
96const SUBPART_LENGTH_MASK: u32 = 0x003F_FFFF;
98
99impl<'a> Parse<'a> for FefSubPartPayload<'a> {
100 type Error = crate::error::Error;
101
102 fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
103 if bytes.len() < FEF_SUBPART_HEADER_LEN {
104 return Err(crate::Error::BufferTooShort {
105 need: FEF_SUBPART_HEADER_LEN,
106 have: bytes.len(),
107 what: "FefSubPartPayload header",
108 });
109 }
110
111 if bytes[3] != 0 || bytes[4] != 0 || bytes[5] != 0 || bytes[6] != 0 {
113 return Err(crate::Error::ReservedBitsViolation {
114 field: "32-bit RFU (rfu1)",
115 reason: "Must be zero (ETSI TS 102 773 §5.2.12)",
116 });
117 }
118
119 let variety = SubpartVariety::try_from(u16::from_be_bytes([bytes[9], bytes[10]]))?;
120
121 if bytes[11] != 0 || (bytes[12] & 0xC0 != 0) {
123 return Err(crate::Error::ReservedBitsViolation {
124 field: "10-bit RFU (rfu2)",
125 reason: "Must be zero (ETSI TS 102 773 §5.2.12)",
126 });
127 }
128
129 let subpart_length =
131 ((bytes[12] & 0x3F) as u32) << 16 | (bytes[13] as u32) << 8 | (bytes[14] as u32);
132
133 Ok(FefSubPartPayload {
134 fef_idx: bytes[0],
135 tx_identifier: u16::from_be_bytes([bytes[1], bytes[2]]),
136 subpart_idx: u16::from_be_bytes([bytes[7], bytes[8]]),
137 subpart_variety: variety,
138 subpart_length,
139 subpart_data: &bytes[FEF_SUBPART_HEADER_LEN..],
140 })
141 }
142}
143
144impl Serialize for FefSubPartPayload<'_> {
145 type Error = crate::error::Error;
146
147 fn serialized_len(&self) -> usize {
148 FEF_SUBPART_HEADER_LEN + self.subpart_data.len()
149 }
150
151 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
152 if buf.len() < self.serialized_len() {
153 return Err(crate::Error::OutputBufferTooSmall {
154 need: self.serialized_len(),
155 have: buf.len(),
156 });
157 }
158
159 if self.subpart_length > SUBPART_LENGTH_MASK {
160 return Err(crate::Error::ReservedBitsViolation {
161 field: "subpart_length",
162 reason: "Must fit in 22 bits",
163 });
164 }
165
166 buf[0] = self.fef_idx;
167 let tx_id = self.tx_identifier.to_be_bytes();
168 buf[1] = tx_id[0];
169 buf[2] = tx_id[1];
170 buf[3] = 0;
172 buf[4] = 0;
173 buf[5] = 0;
174 buf[6] = 0;
175 let sub_idx = self.subpart_idx.to_be_bytes();
176 buf[7] = sub_idx[0];
177 buf[8] = sub_idx[1];
178 let variety = u16::from(self.subpart_variety);
179 buf[9] = (variety >> 8) as u8;
180 buf[10] = (variety & 0xFF) as u8;
181 buf[11] = 0;
183 buf[12] = ((self.subpart_length >> 16) & 0x3F) as u8;
185 buf[13] = (self.subpart_length >> 8) as u8;
186 buf[14] = (self.subpart_length & 0xFF) as u8;
187
188 if !self.subpart_data.is_empty() {
189 buf[FEF_SUBPART_HEADER_LEN..FEF_SUBPART_HEADER_LEN + self.subpart_data.len()]
190 .copy_from_slice(self.subpart_data);
191 }
192
193 Ok(self.serialized_len())
194 }
195}
196
197impl fmt::Display for FefSubPartPayload<'_> {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(
200 f,
201 "FEF SubPart {{ fef_idx: {}, tx_id: 0x{:04X}, subpart_idx: {}, variety: {:?}, length: {} }}",
202 self.fef_idx, self.tx_identifier, self.subpart_idx, self.subpart_variety, self.subpart_length
203 )
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn parse_extracts_fields() {
213 let mut buf = [0u8; 15];
218 buf[0] = 0x02; buf[1] = 0x00;
220 buf[2] = 0x01; buf[7] = 0x00;
223 buf[8] = 0x05; buf[9] = 0x00;
225 buf[10] = 0x01; buf[13] = 0x10;
230 let result = FefSubPartPayload::parse(&buf).unwrap();
234 assert_eq!(result.fef_idx, 2);
235 assert_eq!(result.tx_identifier, 0x0001);
236 assert_eq!(result.subpart_idx, 5);
237 assert_eq!(result.subpart_variety, SubpartVariety::Iq);
238 assert_eq!(result.subpart_length, 0x1000);
239 }
240
241 #[test]
242 fn parse_rejects_invalid_variety() {
243 let mut buf = [0u8; 15];
244 buf[9] = 0x00;
245 buf[10] = 0x04; assert!(FefSubPartPayload::parse(&buf).is_err());
247 }
248
249 #[test]
250 fn parse_rejects_nonzero_rfu1() {
251 let mut buf = [0u8; 15];
252 buf[3] = 0x01;
253 assert!(FefSubPartPayload::parse(&buf).is_err());
254 }
255
256 #[test]
257 fn parse_rejects_nonzero_rfu2_byte11() {
258 let mut buf = [0u8; 15];
259 buf[11] = 0x01;
260 assert!(FefSubPartPayload::parse(&buf).is_err());
261 }
262
263 #[test]
264 fn parse_rejects_nonzero_rfu2_byte12_top() {
265 let mut buf = [0u8; 15];
266 buf[12] = 0xC0;
267 assert!(FefSubPartPayload::parse(&buf).is_err());
268 }
269
270 #[test]
271 fn parse_rejects_short_buffer() {
272 let buf = [0u8; 14];
273 assert!(FefSubPartPayload::parse(&buf).is_err());
274 }
275
276 #[test]
277 fn serialize_round_trip() {
278 let orig = FefSubPartPayload {
279 fef_idx: 1,
280 tx_identifier: 0x0000,
281 subpart_idx: 3,
282 subpart_variety: SubpartVariety::Null,
283 subpart_length: 2048,
284 subpart_data: &[],
285 };
286 let mut buf = vec![0u8; orig.serialized_len()];
287 orig.serialize_into(&mut buf).unwrap();
288 let parsed = FefSubPartPayload::parse(&buf).unwrap();
289 assert_eq!(orig, parsed);
290 }
291
292 #[test]
293 fn broadcast_tx_identifier() {
294 let buf = [0u8; 15];
295 let result = FefSubPartPayload::parse(&buf).unwrap();
296 assert_eq!(result.tx_identifier, 0x0000);
297 }
298
299 #[test]
300 fn subpart_variety_try_from_all() {
301 assert_eq!(SubpartVariety::try_from(0x0000), Ok(SubpartVariety::Null));
302 assert_eq!(SubpartVariety::try_from(0x0001), Ok(SubpartVariety::Iq));
303 assert_eq!(SubpartVariety::try_from(0x0002), Ok(SubpartVariety::Prbs));
304 assert_eq!(
305 SubpartVariety::try_from(0x0003),
306 Ok(SubpartVariety::TxSigFef)
307 );
308 }
309
310 #[test]
311 fn subpart_variety_try_from_reserved() {
312 assert!(SubpartVariety::try_from(0x0004).is_err());
313 assert!(SubpartVariety::try_from(0xFFFF).is_err());
314 }
315
316 #[test]
317 fn exhaustive_subpart_variety_sweep() {
318 let mut matched = 0u32;
319 for value in 0u16..=0xFFFF {
320 if let Ok(v) = SubpartVariety::try_from(value) {
321 assert_eq!(v as u16, value, "round-trip failed for {value:#06x}");
322 matched += 1;
323 }
324 }
325 assert_eq!(matched, 4, "expected 4 matched variants");
326 }
327
328 #[test]
329 fn exhaustive_prbs_type_sweep() {
330 let mut matched = 0u16;
331 for byte in 0u8..=0xFF {
332 if let Ok(v) = PrbsType::try_from(byte) {
333 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
334 matched += 1;
335 }
336 }
337 assert_eq!(matched, 1, "expected 1 matched variant");
338 }
339}