dvb_t2mi/payload/
fef_null.rs1use 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
49#[derive(Debug, Clone, PartialEq, Eq)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize))]
52pub struct FefNullPayload {
53 pub fef_idx: u8,
55 pub s1_field: S1Field,
57 pub s2_field: u8,
59}
60
61const FEF_NULL_LEN: usize = 3;
62
63impl<'a> Parse<'a> for FefNullPayload {
64 type Error = crate::error::Error;
65
66 fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
67 if bytes.len() < FEF_NULL_LEN {
68 return Err(crate::Error::BufferTooShort {
69 need: FEF_NULL_LEN,
70 have: bytes.len(),
71 what: "FefNullPayload",
72 });
73 }
74 if bytes[1] != 0 || bytes[2] & 0x80 != 0 {
77 return Err(crate::Error::ReservedBitsViolation {
78 field: "9-bit rfu",
79 reason: "Must be zero (ETSI TS 102 773 §5.2.9)",
80 });
81 }
82 Ok(FefNullPayload {
83 fef_idx: bytes[0],
84 s1_field: S1Field::try_from((bytes[2] >> 4) & 0x07)?,
86 s2_field: bytes[2] & 0x0F,
87 })
88 }
89}
90
91impl<'a> crate::traits::PayloadDef<'a> for FefNullPayload {
92 const PACKET_TYPE: u8 = 0x30;
93 const NAME: &'static str = "FEF_NULL";
94}
95
96impl Serialize for FefNullPayload {
97 type Error = crate::error::Error;
98
99 fn serialized_len(&self) -> usize {
100 FEF_NULL_LEN
101 }
102
103 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize, crate::error::Error> {
104 if buf.len() < self.serialized_len() {
105 return Err(crate::Error::OutputBufferTooSmall {
106 need: self.serialized_len(),
107 have: buf.len(),
108 });
109 }
110 if self.s2_field > 0x0F {
111 return Err(crate::Error::ReservedBitsViolation {
112 field: "s2_field",
113 reason: "Must fit in 4 bits",
114 });
115 }
116 buf[0] = self.fef_idx;
117 buf[1] = 0; buf[2] = ((u8::from(self.s1_field) & 0x07) << 4) | (self.s2_field & 0x0F);
119 Ok(self.serialized_len())
120 }
121}
122
123impl fmt::Display for FefNullPayload {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(
126 f,
127 "FEF Null {{ fef_idx: {}, s1: {:?}, s2: {:04b} }}",
128 self.fef_idx, self.s1_field, self.s2_field
129 )
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn parse_extracts_fields() {
139 let buf = [0x05u8, 0x00, 0x1A];
141 let result = FefNullPayload::parse(&buf).unwrap();
142 assert_eq!(result.fef_idx, 5);
143 assert_eq!(result.s1_field, S1Field::V1);
144 assert_eq!(result.s2_field, 0x0A);
145 }
146
147 #[test]
148 fn parse_rejects_nonzero_rfu() {
149 let buf = [0x00u8, 0x1F, 0x00];
150 assert!(FefNullPayload::parse(&buf).is_err());
151 }
152
153 #[test]
154 fn serialize_round_trip() {
155 let orig = FefNullPayload {
156 fef_idx: 3,
157 s1_field: S1Field::V4,
158 s2_field: 0x0C,
159 };
160 let mut buf = [0u8; FEF_NULL_LEN];
161 orig.serialize_into(&mut buf).unwrap();
162 let parsed = FefNullPayload::parse(&buf).unwrap();
163 assert_eq!(orig, parsed);
164 }
165
166 #[test]
167 fn display_output() {
168 let p = FefNullPayload {
169 fef_idx: 0,
170 s1_field: S1Field::V0,
171 s2_field: 0,
172 };
173 assert!(p.to_string().contains("FEF Null"));
174 }
175
176 #[test]
177 fn exhaustive_byte_sweep() {
178 let mut matched = 0u16;
179 for byte in 0u8..=0xFF {
180 if let Ok(v) = S1Field::try_from(byte) {
181 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
182 matched += 1;
183 }
184 }
185 assert_eq!(matched, 8, "expected 8 matched variants");
186 }
187}