1use std::fmt;
6
7use num_enum::TryFromPrimitive;
8
9use dvb_common::{Parse, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14#[repr(u8)]
15pub enum S1Field {
16 V0 = 0,
18 V1 = 1,
20 V2 = 2,
22 V3 = 3,
24 V4 = 4,
26 V5 = 5,
28 V6 = 6,
30 V7 = 7,
32}
33
34impl From<S1Field> for u8 {
35 fn from(s: S1Field) -> Self {
36 s as u8
37 }
38}
39
40impl From<num_enum::TryFromPrimitiveError<S1Field>> for crate::error::Error {
41 fn from(_: num_enum::TryFromPrimitiveError<S1Field>) -> Self {
42 crate::error::Error::ReservedBitsViolation {
43 field: "s1_field",
44 reason: "Must be 0..=7",
45 }
46 }
47}
48
49impl S1Field {
50 #[must_use]
52 pub fn meaning(self) -> &'static str {
53 match self {
54 Self::V0 => "T2_SISO",
55 Self::V1 => "T2_MISO",
56 Self::V2 => "Non-T2",
57 Self::V3 => "T2_LITE_SISO",
58 Self::V4 => "T2_LITE_MISO",
59 Self::V5 => "reserved",
60 Self::V6 => "reserved",
61 Self::V7 => "reserved",
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize))]
71#[non_exhaustive]
72pub enum S2Field1 {
73 Fft1k,
75 Fft2k,
77 Fft4k,
79 Fft8k,
81 Fft16k,
83 Fft32k,
85 Reserved1,
87 Reserved2,
89}
90
91impl S2Field1 {
92 #[must_use]
95 pub fn from_u8(v: u8) -> Self {
96 match v & 0x07 {
97 0 => Self::Fft1k,
98 1 => Self::Fft2k,
99 2 => Self::Fft4k,
100 3 => Self::Fft8k,
101 4 => Self::Fft16k,
102 5 => Self::Fft32k,
103 6 => Self::Reserved1,
104 _ => Self::Reserved2,
105 }
106 }
107
108 #[must_use]
111 pub fn to_u8(self) -> u8 {
112 match self {
113 Self::Fft1k => 0,
114 Self::Fft2k => 1,
115 Self::Fft4k => 2,
116 Self::Fft8k => 3,
117 Self::Fft16k => 4,
118 Self::Fft32k => 5,
119 Self::Reserved1 => 6,
120 Self::Reserved2 => 7,
121 }
122 }
123
124 #[must_use]
125 pub fn fft_size(self) -> &'static str {
127 match self {
128 Self::Fft1k => "1k",
129 Self::Fft2k => "2k",
130 Self::Fft4k => "4k",
131 Self::Fft8k => "8k",
132 Self::Fft16k => "16k",
133 Self::Fft32k => "32k",
134 Self::Reserved1 | Self::Reserved2 => "reserved",
135 }
136 }
137
138 #[must_use]
139 pub fn guard_interval_set(self) -> &'static str {
141 match self {
142 Self::Fft1k => "1/128, 1/32, 1/16, 19/256, 1/8, 19/128, 1/4",
143 Self::Fft2k => "1/128, 1/32, 1/16, 19/256, 1/8, 19/128, 1/4",
144 Self::Fft4k => "1/128, 1/32, 1/16, 19/256, 1/8, 19/128, 1/4",
145 Self::Fft8k => "1/128, 1/32, 1/16, 19/256, 1/8, 19/128, 1/4",
146 Self::Fft16k => "1/128, 1/32, 1/16, 1/8, 19/128, 1/4",
147 Self::Fft32k => "1/128, 1/32, 1/16, 19/256",
148 Self::Reserved1 | Self::Reserved2 => "reserved",
149 }
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155#[cfg_attr(feature = "serde", derive(serde::Serialize))]
156pub struct FefNullPayload {
157 pub fef_idx: u8,
159 pub s1_field: S1Field,
161 pub s2_field: u8,
163}
164
165impl FefNullPayload {
166 #[must_use]
168 pub fn s2_field1(&self) -> S2Field1 {
169 S2Field1::from_u8(self.s2_field >> 1)
170 }
171
172 #[must_use]
174 pub fn is_mixed(&self) -> bool {
175 (self.s2_field & 0x01) != 0
176 }
177}
178
179const FEF_NULL_LEN: usize = 3;
180
181impl<'a> Parse<'a> for FefNullPayload {
182 type Error = crate::error::Error;
183
184 fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
185 if bytes.len() < FEF_NULL_LEN {
186 return Err(crate::Error::BufferTooShort {
187 need: FEF_NULL_LEN,
188 have: bytes.len(),
189 what: "FefNullPayload",
190 });
191 }
192 if bytes[1] != 0 || bytes[2] & 0x80 != 0 {
195 return Err(crate::Error::ReservedBitsViolation {
196 field: "9-bit rfu",
197 reason: "Must be zero (ETSI TS 102 773 §5.2.9)",
198 });
199 }
200 Ok(FefNullPayload {
201 fef_idx: bytes[0],
202 s1_field: S1Field::try_from((bytes[2] >> 4) & 0x07)?,
204 s2_field: bytes[2] & 0x0F,
205 })
206 }
207}
208
209impl<'a> crate::traits::PayloadDef<'a> for FefNullPayload {
210 const PACKET_TYPE: u8 = 0x30;
211 const NAME: &'static str = "FEF_NULL";
212}
213
214impl Serialize for FefNullPayload {
215 type Error = crate::error::Error;
216
217 fn serialized_len(&self) -> usize {
218 FEF_NULL_LEN
219 }
220
221 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
222 if buf.len() < self.serialized_len() {
223 return Err(crate::Error::OutputBufferTooSmall {
224 need: self.serialized_len(),
225 have: buf.len(),
226 });
227 }
228 if self.s2_field > 0x0F {
229 return Err(crate::Error::ReservedBitsViolation {
230 field: "s2_field",
231 reason: "Must fit in 4 bits",
232 });
233 }
234 buf[0] = self.fef_idx;
235 buf[1] = 0; buf[2] = ((u8::from(self.s1_field) & 0x07) << 4) | (self.s2_field & 0x0F);
237 Ok(self.serialized_len())
238 }
239}
240
241impl fmt::Display for FefNullPayload {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 write!(
244 f,
245 "FEF Null {{ fef_idx: {}, s1: {:?}({}), s2: {:04b} }}",
246 self.fef_idx,
247 self.s1_field,
248 self.s1_field.meaning(),
249 self.s2_field
250 )
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn parse_extracts_fields() {
260 let buf = [0x05u8, 0x00, 0x1A];
262 let result = FefNullPayload::parse(&buf).unwrap();
263 assert_eq!(result.fef_idx, 5);
264 assert_eq!(result.s1_field, S1Field::V1);
265 assert_eq!(result.s2_field, 0x0A);
266 }
267
268 #[test]
269 fn parse_rejects_nonzero_rfu() {
270 let buf = [0x00u8, 0x1F, 0x00];
271 assert!(FefNullPayload::parse(&buf).is_err());
272 }
273
274 #[test]
275 fn serialize_round_trip() {
276 let orig = FefNullPayload {
277 fef_idx: 3,
278 s1_field: S1Field::V4,
279 s2_field: 0x0C,
280 };
281 let mut buf = [0u8; FEF_NULL_LEN];
282 orig.serialize_into(&mut buf).unwrap();
283 let parsed = FefNullPayload::parse(&buf).unwrap();
284 assert_eq!(orig, parsed);
285 }
286
287 #[test]
288 fn display_output() {
289 let p = FefNullPayload {
290 fef_idx: 0,
291 s1_field: S1Field::V0,
292 s2_field: 0,
293 };
294 assert!(p.to_string().contains("FEF Null"));
295 }
296
297 #[test]
298 fn exhaustive_byte_sweep() {
299 let mut matched = 0u16;
300 for byte in 0u8..=0xFF {
301 if let Ok(v) = S1Field::try_from(byte) {
302 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
303 matched += 1;
304 }
305 }
306 assert_eq!(matched, 8, "expected 8 matched variants");
307 }
308
309 #[test]
310 fn s1_meaning_values() {
311 assert_eq!(S1Field::V0.meaning(), "T2_SISO");
312 assert_eq!(S1Field::V1.meaning(), "T2_MISO");
313 assert_eq!(S1Field::V2.meaning(), "Non-T2");
314 assert_eq!(S1Field::V3.meaning(), "T2_LITE_SISO");
315 assert_eq!(S1Field::V4.meaning(), "T2_LITE_MISO");
316 assert_eq!(S1Field::V5.meaning(), "reserved");
317 }
318
319 #[test]
320 fn s2_field1_decode() {
321 let p = FefNullPayload {
323 fef_idx: 0,
324 s1_field: S1Field::V0,
325 s2_field: 0x01,
326 };
327 assert_eq!(p.s2_field1(), S2Field1::Fft1k);
328 assert!(p.is_mixed());
329
330 let p = FefNullPayload {
332 fef_idx: 0,
333 s1_field: S1Field::V0,
334 s2_field: 0x0C,
335 };
336 assert_eq!(p.s2_field1(), S2Field1::Reserved1);
337 assert!(!p.is_mixed());
338 }
339
340 #[test]
341 fn s2_field1_round_trip() {
342 for v in 0u8..=7 {
343 let s2 = S2Field1::from_u8(v);
344 assert_eq!(s2.to_u8(), v, "S2Field1 round-trip failed for {v}");
345 }
346 }
347}