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