dvb_t2mi/payload/
l1_current.rs1use num_enum::TryFromPrimitive;
7
8use broadcast_common::{Parse, Serialize};
9
10use super::l1::post::parse_l1_post_from_framed;
11use super::l1::pre::L1PRE_BYTES;
12use super::l1::{L1Post, L1Pre};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize))]
17#[repr(u8)]
18#[non_exhaustive]
19pub enum FrequencySource {
20 UseL1CurrentData = 0b00,
22 UseIndividualAddressing = 0b01,
24 ManualPerModulator = 0b10,
26}
27
28impl From<FrequencySource> for u8 {
29 fn from(fs: FrequencySource) -> Self {
30 fs as u8
31 }
32}
33
34impl From<num_enum::TryFromPrimitiveError<FrequencySource>> for crate::error::Error {
35 fn from(_: num_enum::TryFromPrimitiveError<FrequencySource>) -> Self {
36 crate::error::Error::ReservedBitsViolation {
37 field: "freq_source",
38 reason: "Must be 0b00, 0b01, or 0b10 (ETSI TS 102 773 §5.2.4)",
39 }
40 }
41}
42
43impl FrequencySource {
44 #[must_use]
46 pub fn name(&self) -> &'static str {
47 match self {
48 Self::UseL1CurrentData => "L1-current data",
49 Self::UseIndividualAddressing => "individual addressing",
50 Self::ManualPerModulator => "manual per modulator",
51 }
52 }
53}
54broadcast_common::impl_spec_display!(FrequencySource);
55
56#[derive(Debug, Clone, PartialEq, Eq)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize))]
65#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
66pub struct L1CurrentPayload<'a> {
67 pub frame_idx: u8,
69 pub freq_source: FrequencySource,
71 pub l1_current_data: &'a [u8],
73}
74
75const L1_CURRENT_HEADER_LEN: usize = 2;
76
77impl<'a> L1CurrentPayload<'a> {
78 pub fn l1_pre(&self) -> crate::error::Result<L1Pre> {
84 if self.l1_current_data.len() < L1PRE_BYTES {
85 return Err(crate::Error::BufferTooShort {
86 need: L1PRE_BYTES,
87 have: self.l1_current_data.len(),
88 what: "L1PRE in l1_current_data",
89 });
90 }
91 L1Pre::parse(&self.l1_current_data[..L1PRE_BYTES])
92 }
93
94 pub fn l1_post(&self) -> crate::error::Result<L1Post> {
103 let pre = self.l1_pre()?;
104 let num_rf = pre.num_rf;
105 let fef_present = (pre.s2 & 0x01) == 1;
106 let framed = &self.l1_current_data[L1PRE_BYTES..];
107 parse_l1_post_from_framed(framed, num_rf, fef_present)
108 }
109}
110
111impl<'a> Parse<'a> for L1CurrentPayload<'a> {
112 type Error = crate::error::Error;
113
114 fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
115 if bytes.len() < L1_CURRENT_HEADER_LEN {
116 return Err(crate::Error::BufferTooShort {
117 need: L1_CURRENT_HEADER_LEN,
118 have: bytes.len(),
119 what: "L1CurrentPayload header",
120 });
121 }
122
123 let frame_idx = bytes[0];
124 let freq_source = FrequencySource::try_from(bytes[1] >> 6)?;
125
126 let rfu = bytes[1] & 0x3F;
128 if rfu != 0 {
129 return Err(crate::Error::ReservedBitsViolation {
130 field: "6-bit RFU after freq_source",
131 reason: "Must be zero (ETSI TS 102 773 §5.2.4)",
132 });
133 }
134
135 Ok(L1CurrentPayload {
136 frame_idx,
137 freq_source,
138 l1_current_data: &bytes[L1_CURRENT_HEADER_LEN..],
139 })
140 }
141}
142
143impl<'a> crate::traits::PayloadDef<'a> for L1CurrentPayload<'a> {
144 const PACKET_TYPE: u8 = 0x10;
145 const NAME: &'static str = "L1_CURRENT";
146}
147
148impl Serialize for L1CurrentPayload<'_> {
149 type Error = crate::error::Error;
150
151 fn serialized_len(&self) -> usize {
152 L1_CURRENT_HEADER_LEN + self.l1_current_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 buf[0] = self.frame_idx;
164 buf[1] = (u8::from(self.freq_source) << 6) & 0xC0; if !self.l1_current_data.is_empty() {
167 buf[L1_CURRENT_HEADER_LEN..L1_CURRENT_HEADER_LEN + self.l1_current_data.len()]
168 .copy_from_slice(self.l1_current_data);
169 }
170
171 Ok(self.serialized_len())
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn frequency_source_try_from_valid() {
181 assert_eq!(
182 FrequencySource::try_from(0b00),
183 Ok(FrequencySource::UseL1CurrentData)
184 );
185 assert_eq!(
186 FrequencySource::try_from(0b01),
187 Ok(FrequencySource::UseIndividualAddressing)
188 );
189 assert_eq!(
190 FrequencySource::try_from(0b10),
191 Ok(FrequencySource::ManualPerModulator)
192 );
193 }
194
195 #[test]
196 fn frequency_source_try_from_rejects_11() {
197 assert!(FrequencySource::try_from(0b11).is_err());
198 }
199
200 #[test]
201 fn exhaustive_byte_sweep() {
202 let mut matched = 0u16;
203 for byte in 0u8..=0xFF {
204 if let Ok(v) = FrequencySource::try_from(byte) {
205 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
206 matched += 1;
207 }
208 }
209 assert_eq!(matched, 3, "expected 3 matched variants");
210 }
211
212 #[test]
213 fn parse_extracts_frame_idx_and_freq_source() {
214 let buf = [0x42u8, 0x80, 0xDE, 0xAD]; let result = L1CurrentPayload::parse(&buf).unwrap();
216 assert_eq!(result.frame_idx, 0x42);
217 assert_eq!(result.freq_source, FrequencySource::ManualPerModulator);
218 assert_eq!(result.l1_current_data, &[0xDE, 0xAD]);
219 }
220
221 #[test]
222 fn parse_rejects_nonzero_rfu() {
223 let buf = [0x00u8, 0x01, 0x00]; assert!(L1CurrentPayload::parse(&buf).is_err());
225 }
226
227 #[test]
228 fn serialize_round_trip() {
229 let orig = L1CurrentPayload {
230 frame_idx: 0xAB,
231 freq_source: FrequencySource::UseL1CurrentData,
232 l1_current_data: &[0x12, 0x34, 0x56],
233 };
234 let mut buf = vec![0u8; orig.serialized_len()];
235 orig.serialize_into(&mut buf).unwrap();
236 let parsed = L1CurrentPayload::parse(&buf).unwrap();
237 assert_eq!(orig, parsed);
238 }
239
240 #[test]
241 fn serialize_zeros_rfu_bits() {
242 let payload = L1CurrentPayload {
243 frame_idx: 0x10,
244 freq_source: FrequencySource::UseIndividualAddressing,
245 l1_current_data: &[],
246 };
247 let mut buf = [0xFFu8; 2];
248 payload.serialize_into(&mut buf).unwrap();
249 assert_eq!(buf[1] & 0x3F, 0x00);
250 }
251}