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<'a> crate::traits::PayloadDef<'a> for FefSubPartPayload<'a> {
145 const PACKET_TYPE: u8 = 0x33;
146 const NAME: &'static str = "FEF_SUBPART";
147}
148
149impl Serialize for FefSubPartPayload<'_> {
150 type Error = crate::error::Error;
151
152 fn serialized_len(&self) -> usize {
153 FEF_SUBPART_HEADER_LEN + self.subpart_data.len()
154 }
155
156 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
157 if buf.len() < self.serialized_len() {
158 return Err(crate::Error::OutputBufferTooSmall {
159 need: self.serialized_len(),
160 have: buf.len(),
161 });
162 }
163
164 if self.subpart_length > SUBPART_LENGTH_MASK {
165 return Err(crate::Error::ReservedBitsViolation {
166 field: "subpart_length",
167 reason: "Must fit in 22 bits",
168 });
169 }
170
171 buf[0] = self.fef_idx;
172 let tx_id = self.tx_identifier.to_be_bytes();
173 buf[1] = tx_id[0];
174 buf[2] = tx_id[1];
175 buf[3] = 0;
177 buf[4] = 0;
178 buf[5] = 0;
179 buf[6] = 0;
180 let sub_idx = self.subpart_idx.to_be_bytes();
181 buf[7] = sub_idx[0];
182 buf[8] = sub_idx[1];
183 let variety = u16::from(self.subpart_variety);
184 buf[9] = (variety >> 8) as u8;
185 buf[10] = (variety & 0xFF) as u8;
186 buf[11] = 0;
188 buf[12] = ((self.subpart_length >> 16) & 0x3F) as u8;
190 buf[13] = (self.subpart_length >> 8) as u8;
191 buf[14] = (self.subpart_length & 0xFF) as u8;
192
193 if !self.subpart_data.is_empty() {
194 buf[FEF_SUBPART_HEADER_LEN..FEF_SUBPART_HEADER_LEN + self.subpart_data.len()]
195 .copy_from_slice(self.subpart_data);
196 }
197
198 Ok(self.serialized_len())
199 }
200}
201
202impl fmt::Display for FefSubPartPayload<'_> {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 write!(
205 f,
206 "FEF SubPart {{ fef_idx: {}, tx_id: 0x{:04X}, subpart_idx: {}, variety: {:?}, length: {} }}",
207 self.fef_idx, self.tx_identifier, self.subpart_idx, self.subpart_variety, self.subpart_length
208 )
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn parse_extracts_fields() {
218 let mut buf = [0u8; 15];
223 buf[0] = 0x02; buf[1] = 0x00;
225 buf[2] = 0x01; buf[7] = 0x00;
228 buf[8] = 0x05; buf[9] = 0x00;
230 buf[10] = 0x01; buf[13] = 0x10;
235 let result = FefSubPartPayload::parse(&buf).unwrap();
239 assert_eq!(result.fef_idx, 2);
240 assert_eq!(result.tx_identifier, 0x0001);
241 assert_eq!(result.subpart_idx, 5);
242 assert_eq!(result.subpart_variety, SubpartVariety::Iq);
243 assert_eq!(result.subpart_length, 0x1000);
244 }
245
246 #[test]
247 fn parse_rejects_invalid_variety() {
248 let mut buf = [0u8; 15];
249 buf[9] = 0x00;
250 buf[10] = 0x04; assert!(FefSubPartPayload::parse(&buf).is_err());
252 }
253
254 #[test]
255 fn parse_rejects_nonzero_rfu1() {
256 let mut buf = [0u8; 15];
257 buf[3] = 0x01;
258 assert!(FefSubPartPayload::parse(&buf).is_err());
259 }
260
261 #[test]
262 fn parse_rejects_nonzero_rfu2_byte11() {
263 let mut buf = [0u8; 15];
264 buf[11] = 0x01;
265 assert!(FefSubPartPayload::parse(&buf).is_err());
266 }
267
268 #[test]
269 fn parse_rejects_nonzero_rfu2_byte12_top() {
270 let mut buf = [0u8; 15];
271 buf[12] = 0xC0;
272 assert!(FefSubPartPayload::parse(&buf).is_err());
273 }
274
275 #[test]
276 fn parse_rejects_short_buffer() {
277 let buf = [0u8; 14];
278 assert!(FefSubPartPayload::parse(&buf).is_err());
279 }
280
281 #[test]
282 fn serialize_round_trip() {
283 let orig = FefSubPartPayload {
284 fef_idx: 1,
285 tx_identifier: 0x0000,
286 subpart_idx: 3,
287 subpart_variety: SubpartVariety::Null,
288 subpart_length: 2048,
289 subpart_data: &[],
290 };
291 let mut buf = vec![0u8; orig.serialized_len()];
292 orig.serialize_into(&mut buf).unwrap();
293 let parsed = FefSubPartPayload::parse(&buf).unwrap();
294 assert_eq!(orig, parsed);
295 }
296
297 #[test]
298 fn broadcast_tx_identifier() {
299 let buf = [0u8; 15];
300 let result = FefSubPartPayload::parse(&buf).unwrap();
301 assert_eq!(result.tx_identifier, 0x0000);
302 }
303
304 #[test]
305 fn subpart_variety_try_from_all() {
306 assert_eq!(SubpartVariety::try_from(0x0000), Ok(SubpartVariety::Null));
307 assert_eq!(SubpartVariety::try_from(0x0001), Ok(SubpartVariety::Iq));
308 assert_eq!(SubpartVariety::try_from(0x0002), Ok(SubpartVariety::Prbs));
309 assert_eq!(
310 SubpartVariety::try_from(0x0003),
311 Ok(SubpartVariety::TxSigFef)
312 );
313 }
314
315 #[test]
316 fn subpart_variety_try_from_reserved() {
317 assert!(SubpartVariety::try_from(0x0004).is_err());
318 assert!(SubpartVariety::try_from(0xFFFF).is_err());
319 }
320
321 #[test]
322 fn exhaustive_subpart_variety_sweep() {
323 let mut matched = 0u32;
324 for value in 0u16..=0xFFFF {
325 if let Ok(v) = SubpartVariety::try_from(value) {
326 assert_eq!(v as u16, value, "round-trip failed for {value:#06x}");
327 matched += 1;
328 }
329 }
330 assert_eq!(matched, 4, "expected 4 matched variants");
331 }
332
333 #[test]
334 fn exhaustive_prbs_type_sweep() {
335 let mut matched = 0u16;
336 for byte in 0u8..=0xFF {
337 if let Ok(v) = PrbsType::try_from(byte) {
338 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
339 matched += 1;
340 }
341 }
342 assert_eq!(matched, 1, "expected 1 matched variant");
343 }
344}