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)]
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))]
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))]
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 pub subpart_data: &'a [u8],
89}
90
91const FEF_SUBPART_HEADER_LEN: usize = 15;
94
95const SUBPART_LENGTH_MASK: u32 = 0x003F_FFFF;
97
98impl<'a> Parse<'a> for FefSubPartPayload<'a> {
99 type Error = crate::error::Error;
100
101 fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
102 if bytes.len() < FEF_SUBPART_HEADER_LEN {
103 return Err(crate::Error::BufferTooShort {
104 need: FEF_SUBPART_HEADER_LEN,
105 have: bytes.len(),
106 what: "FefSubPartPayload header",
107 });
108 }
109
110 if bytes[3] != 0 || bytes[4] != 0 || bytes[5] != 0 || bytes[6] != 0 {
112 return Err(crate::Error::ReservedBitsViolation {
113 field: "32-bit RFU (rfu1)",
114 reason: "Must be zero (ETSI TS 102 773 §5.2.12)",
115 });
116 }
117
118 let variety = SubpartVariety::try_from(u16::from_be_bytes([bytes[9], bytes[10]]))?;
119
120 if bytes[11] != 0 || (bytes[12] & 0xC0 != 0) {
122 return Err(crate::Error::ReservedBitsViolation {
123 field: "10-bit RFU (rfu2)",
124 reason: "Must be zero (ETSI TS 102 773 §5.2.12)",
125 });
126 }
127
128 let subpart_length =
130 ((bytes[12] & 0x3F) as u32) << 16 | (bytes[13] as u32) << 8 | (bytes[14] as u32);
131
132 Ok(FefSubPartPayload {
133 fef_idx: bytes[0],
134 tx_identifier: u16::from_be_bytes([bytes[1], bytes[2]]),
135 subpart_idx: u16::from_be_bytes([bytes[7], bytes[8]]),
136 subpart_variety: variety,
137 subpart_length,
138 subpart_data: &bytes[FEF_SUBPART_HEADER_LEN..],
139 })
140 }
141}
142
143impl<'a> crate::traits::PayloadDef<'a> for FefSubPartPayload<'a> {
144 const PACKET_TYPE: u8 = 0x33;
145 const NAME: &'static str = "FEF_SUBPART";
146}
147
148impl Serialize for FefSubPartPayload<'_> {
149 type Error = crate::error::Error;
150
151 fn serialized_len(&self) -> usize {
152 FEF_SUBPART_HEADER_LEN + self.subpart_data.len()
153 }
154
155 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
156 if buf.len() < self.serialized_len() {
157 return Err(crate::Error::OutputBufferTooSmall {
158 need: self.serialized_len(),
159 have: buf.len(),
160 });
161 }
162
163 if self.subpart_length > SUBPART_LENGTH_MASK {
164 return Err(crate::Error::ReservedBitsViolation {
165 field: "subpart_length",
166 reason: "Must fit in 22 bits",
167 });
168 }
169
170 buf[0] = self.fef_idx;
171 let tx_id = self.tx_identifier.to_be_bytes();
172 buf[1] = tx_id[0];
173 buf[2] = tx_id[1];
174 buf[3] = 0;
176 buf[4] = 0;
177 buf[5] = 0;
178 buf[6] = 0;
179 let sub_idx = self.subpart_idx.to_be_bytes();
180 buf[7] = sub_idx[0];
181 buf[8] = sub_idx[1];
182 let variety = u16::from(self.subpart_variety);
183 buf[9] = (variety >> 8) as u8;
184 buf[10] = (variety & 0xFF) as u8;
185 buf[11] = 0;
187 buf[12] = ((self.subpart_length >> 16) & 0x3F) as u8;
189 buf[13] = (self.subpart_length >> 8) as u8;
190 buf[14] = (self.subpart_length & 0xFF) as u8;
191
192 if !self.subpart_data.is_empty() {
193 buf[FEF_SUBPART_HEADER_LEN..FEF_SUBPART_HEADER_LEN + self.subpart_data.len()]
194 .copy_from_slice(self.subpart_data);
195 }
196
197 Ok(self.serialized_len())
198 }
199}
200
201impl fmt::Display for FefSubPartPayload<'_> {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(
204 f,
205 "FEF SubPart {{ fef_idx: {}, tx_id: 0x{:04X}, subpart_idx: {}, variety: {:?}, length: {} }}",
206 self.fef_idx, self.tx_identifier, self.subpart_idx, self.subpart_variety, self.subpart_length
207 )
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn parse_extracts_fields() {
217 let mut buf = [0u8; 15];
222 buf[0] = 0x02; buf[1] = 0x00;
224 buf[2] = 0x01; buf[7] = 0x00;
227 buf[8] = 0x05; buf[9] = 0x00;
229 buf[10] = 0x01; buf[13] = 0x10;
234 let result = FefSubPartPayload::parse(&buf).unwrap();
238 assert_eq!(result.fef_idx, 2);
239 assert_eq!(result.tx_identifier, 0x0001);
240 assert_eq!(result.subpart_idx, 5);
241 assert_eq!(result.subpart_variety, SubpartVariety::Iq);
242 assert_eq!(result.subpart_length, 0x1000);
243 }
244
245 #[test]
246 fn parse_rejects_invalid_variety() {
247 let mut buf = [0u8; 15];
248 buf[9] = 0x00;
249 buf[10] = 0x04; assert!(FefSubPartPayload::parse(&buf).is_err());
251 }
252
253 #[test]
254 fn parse_rejects_nonzero_rfu1() {
255 let mut buf = [0u8; 15];
256 buf[3] = 0x01;
257 assert!(FefSubPartPayload::parse(&buf).is_err());
258 }
259
260 #[test]
261 fn parse_rejects_nonzero_rfu2_byte11() {
262 let mut buf = [0u8; 15];
263 buf[11] = 0x01;
264 assert!(FefSubPartPayload::parse(&buf).is_err());
265 }
266
267 #[test]
268 fn parse_rejects_nonzero_rfu2_byte12_top() {
269 let mut buf = [0u8; 15];
270 buf[12] = 0xC0;
271 assert!(FefSubPartPayload::parse(&buf).is_err());
272 }
273
274 #[test]
275 fn parse_rejects_short_buffer() {
276 let buf = [0u8; 14];
277 assert!(FefSubPartPayload::parse(&buf).is_err());
278 }
279
280 #[test]
281 fn serialize_round_trip() {
282 let orig = FefSubPartPayload {
283 fef_idx: 1,
284 tx_identifier: 0x0000,
285 subpart_idx: 3,
286 subpart_variety: SubpartVariety::Null,
287 subpart_length: 2048,
288 subpart_data: &[],
289 };
290 let mut buf = vec![0u8; orig.serialized_len()];
291 orig.serialize_into(&mut buf).unwrap();
292 let parsed = FefSubPartPayload::parse(&buf).unwrap();
293 assert_eq!(orig, parsed);
294 }
295
296 #[test]
297 fn broadcast_tx_identifier() {
298 let buf = [0u8; 15];
299 let result = FefSubPartPayload::parse(&buf).unwrap();
300 assert_eq!(result.tx_identifier, 0x0000);
301 }
302
303 #[test]
304 fn subpart_variety_try_from_all() {
305 assert_eq!(SubpartVariety::try_from(0x0000), Ok(SubpartVariety::Null));
306 assert_eq!(SubpartVariety::try_from(0x0001), Ok(SubpartVariety::Iq));
307 assert_eq!(SubpartVariety::try_from(0x0002), Ok(SubpartVariety::Prbs));
308 assert_eq!(
309 SubpartVariety::try_from(0x0003),
310 Ok(SubpartVariety::TxSigFef)
311 );
312 }
313
314 #[test]
315 fn subpart_variety_try_from_reserved() {
316 assert!(SubpartVariety::try_from(0x0004).is_err());
317 assert!(SubpartVariety::try_from(0xFFFF).is_err());
318 }
319
320 #[test]
321 fn exhaustive_subpart_variety_sweep() {
322 let mut matched = 0u32;
323 for value in 0u16..=0xFFFF {
324 if let Ok(v) = SubpartVariety::try_from(value) {
325 assert_eq!(v as u16, value, "round-trip failed for {value:#06x}");
326 matched += 1;
327 }
328 }
329 assert_eq!(matched, 4, "expected 4 matched variants");
330 }
331
332 #[test]
333 fn exhaustive_prbs_type_sweep() {
334 let mut matched = 0u16;
335 for byte in 0u8..=0xFF {
336 if let Ok(v) = PrbsType::try_from(byte) {
337 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
338 matched += 1;
339 }
340 }
341 assert_eq!(matched, 1, "expected 1 matched variant");
342 }
343}