1use core::fmt;
12
13use num_enum::TryFromPrimitive;
14
15use broadcast_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
50impl SubpartVariety {
51 #[must_use]
53 pub fn name(&self) -> &'static str {
54 match self {
55 Self::Null => "Null",
56 Self::Iq => "IQ",
57 Self::Prbs => "PRBS",
58 Self::TxSigFef => "TX-SIG FEF",
59 }
60 }
61}
62broadcast_common::impl_spec_display!(SubpartVariety);
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize))]
67#[repr(u8)]
68#[non_exhaustive]
69pub enum PrbsType {
70 UserDefined = 0x00,
72}
73
74impl From<PrbsType> for u8 {
75 fn from(pt: PrbsType) -> Self {
76 pt as u8
77 }
78}
79
80impl PrbsType {
81 #[must_use]
83 pub fn name(&self) -> &'static str {
84 match self {
85 Self::UserDefined => "user-defined",
86 }
87 }
88}
89broadcast_common::impl_spec_display!(PrbsType);
90
91#[derive(Debug, Clone, PartialEq, Eq)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
104pub struct FefSubPartPayload<'a> {
105 pub fef_idx: u8,
107 pub tx_identifier: u16,
109 pub subpart_idx: u16,
111 pub subpart_variety: SubpartVariety,
113 pub subpart_length: u32,
115 pub subpart_data: &'a [u8],
117}
118
119const FEF_SUBPART_HEADER_LEN: usize = 15;
122
123const SUBPART_LENGTH_MASK: u32 = 0x003F_FFFF;
125
126impl<'a> Parse<'a> for FefSubPartPayload<'a> {
127 type Error = crate::error::Error;
128
129 fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
130 let (hdr, rest) = bytes.split_first_chunk::<FEF_SUBPART_HEADER_LEN>().ok_or(
131 crate::Error::BufferTooShort {
132 need: FEF_SUBPART_HEADER_LEN,
133 have: bytes.len(),
134 what: "FefSubPartPayload header",
135 },
136 )?;
137
138 if hdr[3] != 0 || hdr[4] != 0 || hdr[5] != 0 || hdr[6] != 0 {
140 return Err(crate::Error::ReservedBitsViolation {
141 field: "32-bit RFU (rfu1)",
142 reason: "Must be zero (ETSI TS 102 773 §5.2.12)",
143 });
144 }
145
146 let variety = SubpartVariety::try_from(u16::from_be_bytes([hdr[9], hdr[10]]))?;
147
148 if hdr[11] != 0 || (hdr[12] & 0xC0 != 0) {
150 return Err(crate::Error::ReservedBitsViolation {
151 field: "10-bit RFU (rfu2)",
152 reason: "Must be zero (ETSI TS 102 773 §5.2.12)",
153 });
154 }
155
156 let subpart_length =
158 ((hdr[12] & 0x3F) as u32) << 16 | (hdr[13] as u32) << 8 | (hdr[14] as u32);
159
160 Ok(FefSubPartPayload {
161 fef_idx: hdr[0],
162 tx_identifier: u16::from_be_bytes([hdr[1], hdr[2]]),
163 subpart_idx: u16::from_be_bytes([hdr[7], hdr[8]]),
164 subpart_variety: variety,
165 subpart_length,
166 subpart_data: rest,
167 })
168 }
169}
170
171impl<'a> crate::traits::PayloadDef<'a> for FefSubPartPayload<'a> {
172 const PACKET_TYPE: u8 = 0x33;
173 const NAME: &'static str = "FEF_SUBPART";
174}
175
176impl Serialize for FefSubPartPayload<'_> {
177 type Error = crate::error::Error;
178
179 fn serialized_len(&self) -> usize {
180 FEF_SUBPART_HEADER_LEN + self.subpart_data.len()
181 }
182
183 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
184 if buf.len() < self.serialized_len() {
185 return Err(crate::Error::OutputBufferTooSmall {
186 need: self.serialized_len(),
187 have: buf.len(),
188 });
189 }
190
191 if self.subpart_length > SUBPART_LENGTH_MASK {
192 return Err(crate::Error::ReservedBitsViolation {
193 field: "subpart_length",
194 reason: "Must fit in 22 bits",
195 });
196 }
197
198 buf[0] = self.fef_idx;
199 let tx_id = self.tx_identifier.to_be_bytes();
200 buf[1] = tx_id[0];
201 buf[2] = tx_id[1];
202 buf[3] = 0;
204 buf[4] = 0;
205 buf[5] = 0;
206 buf[6] = 0;
207 let sub_idx = self.subpart_idx.to_be_bytes();
208 buf[7] = sub_idx[0];
209 buf[8] = sub_idx[1];
210 let variety = u16::from(self.subpart_variety);
211 buf[9] = (variety >> 8) as u8;
212 buf[10] = (variety & 0xFF) as u8;
213 buf[11] = 0;
215 buf[12] = ((self.subpart_length >> 16) & 0x3F) as u8;
217 buf[13] = (self.subpart_length >> 8) as u8;
218 buf[14] = (self.subpart_length & 0xFF) as u8;
219
220 if !self.subpart_data.is_empty() {
221 buf[FEF_SUBPART_HEADER_LEN..FEF_SUBPART_HEADER_LEN + self.subpart_data.len()]
222 .copy_from_slice(self.subpart_data);
223 }
224
225 Ok(self.serialized_len())
226 }
227}
228
229impl fmt::Display for FefSubPartPayload<'_> {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 write!(
232 f,
233 "FEF SubPart {{ fef_idx: {}, tx_id: 0x{:04X}, subpart_idx: {}, variety: {:?}, length: {} }}",
234 self.fef_idx,
235 self.tx_identifier,
236 self.subpart_idx,
237 self.subpart_variety,
238 self.subpart_length
239 )
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn parse_extracts_fields() {
249 let mut buf = [0u8; 15];
254 buf[0] = 0x02; buf[1] = 0x00;
256 buf[2] = 0x01; buf[7] = 0x00;
259 buf[8] = 0x05; buf[9] = 0x00;
261 buf[10] = 0x01; buf[13] = 0x10;
266 let result = FefSubPartPayload::parse(&buf).unwrap();
270 assert_eq!(result.fef_idx, 2);
271 assert_eq!(result.tx_identifier, 0x0001);
272 assert_eq!(result.subpart_idx, 5);
273 assert_eq!(result.subpart_variety, SubpartVariety::Iq);
274 assert_eq!(result.subpart_length, 0x1000);
275 }
276
277 #[test]
278 fn parse_rejects_invalid_variety() {
279 let mut buf = [0u8; 15];
280 buf[9] = 0x00;
281 buf[10] = 0x04; assert!(FefSubPartPayload::parse(&buf).is_err());
283 }
284
285 #[test]
286 fn parse_rejects_nonzero_rfu1() {
287 let mut buf = [0u8; 15];
288 buf[3] = 0x01;
289 assert!(FefSubPartPayload::parse(&buf).is_err());
290 }
291
292 #[test]
293 fn parse_rejects_nonzero_rfu2_byte11() {
294 let mut buf = [0u8; 15];
295 buf[11] = 0x01;
296 assert!(FefSubPartPayload::parse(&buf).is_err());
297 }
298
299 #[test]
300 fn parse_rejects_nonzero_rfu2_byte12_top() {
301 let mut buf = [0u8; 15];
302 buf[12] = 0xC0;
303 assert!(FefSubPartPayload::parse(&buf).is_err());
304 }
305
306 #[test]
307 fn parse_rejects_short_buffer() {
308 let buf = [0u8; 14];
309 assert!(FefSubPartPayload::parse(&buf).is_err());
310 }
311
312 #[test]
313 fn serialize_round_trip() {
314 let orig = FefSubPartPayload {
315 fef_idx: 1,
316 tx_identifier: 0x0000,
317 subpart_idx: 3,
318 subpart_variety: SubpartVariety::Null,
319 subpart_length: 2048,
320 subpart_data: &[],
321 };
322 let mut buf = vec![0u8; orig.serialized_len()];
323 orig.serialize_into(&mut buf).unwrap();
324 let parsed = FefSubPartPayload::parse(&buf).unwrap();
325 assert_eq!(orig, parsed);
326 }
327
328 #[test]
329 fn broadcast_tx_identifier() {
330 let buf = [0u8; 15];
331 let result = FefSubPartPayload::parse(&buf).unwrap();
332 assert_eq!(result.tx_identifier, 0x0000);
333 }
334
335 #[test]
336 fn subpart_variety_try_from_all() {
337 assert_eq!(SubpartVariety::try_from(0x0000), Ok(SubpartVariety::Null));
338 assert_eq!(SubpartVariety::try_from(0x0001), Ok(SubpartVariety::Iq));
339 assert_eq!(SubpartVariety::try_from(0x0002), Ok(SubpartVariety::Prbs));
340 assert_eq!(
341 SubpartVariety::try_from(0x0003),
342 Ok(SubpartVariety::TxSigFef)
343 );
344 }
345
346 #[test]
347 fn subpart_variety_try_from_reserved() {
348 assert!(SubpartVariety::try_from(0x0004).is_err());
349 assert!(SubpartVariety::try_from(0xFFFF).is_err());
350 }
351
352 #[test]
353 fn exhaustive_subpart_variety_sweep() {
354 let mut matched = 0u32;
355 for value in 0u16..=0xFFFF {
356 if let Ok(v) = SubpartVariety::try_from(value) {
357 assert_eq!(v as u16, value, "round-trip failed for {value:#06x}");
358 matched += 1;
359 }
360 }
361 assert_eq!(matched, 4, "expected 4 matched variants");
362 }
363
364 #[test]
365 fn exhaustive_prbs_type_sweep() {
366 let mut matched = 0u16;
367 for byte in 0u8..=0xFF {
368 if let Ok(v) = PrbsType::try_from(byte) {
369 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
370 matched += 1;
371 }
372 }
373 assert_eq!(matched, 1, "expected 1 matched variant");
374 }
375}