dvb_t2mi/payload/
l1_current.rs1use num_enum::TryFromPrimitive;
7
8use dvb_common::{Parse, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[repr(u8)]
14pub enum FrequencySource {
15 UseL1CurrentData = 0b00,
17 UseIndividualAddressing = 0b01,
19 ManualPerModulator = 0b10,
21}
22
23impl From<FrequencySource> for u8 {
24 fn from(fs: FrequencySource) -> Self {
25 fs as u8
26 }
27}
28
29impl From<num_enum::TryFromPrimitiveError<FrequencySource>> for crate::error::Error {
30 fn from(_: num_enum::TryFromPrimitiveError<FrequencySource>) -> Self {
31 crate::error::Error::ReservedBitsViolation {
32 field: "freq_source",
33 reason: "Must be 0b00, 0b01, or 0b10 (ETSI TS 102 773 §5.2.4)",
34 }
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub struct L1CurrentPayload<'a> {
48 pub frame_idx: u8,
50 pub freq_source: FrequencySource,
52 #[cfg_attr(feature = "serde", serde(borrow))]
54 pub l1_current_data: &'a [u8],
55}
56
57const L1_CURRENT_HEADER_LEN: usize = 2;
58
59impl<'a> Parse<'a> for L1CurrentPayload<'a> {
60 type Error = crate::error::Error;
61
62 fn parse(bytes: &'a [u8]) -> Result<Self, crate::error::Error> {
63 if bytes.len() < L1_CURRENT_HEADER_LEN {
64 return Err(crate::Error::BufferTooShort {
65 need: L1_CURRENT_HEADER_LEN,
66 have: bytes.len(),
67 what: "L1CurrentPayload header",
68 });
69 }
70
71 let frame_idx = bytes[0];
72 let freq_source = FrequencySource::try_from(bytes[1] >> 6)?;
73
74 let rfu = bytes[1] & 0x3F;
76 if rfu != 0 {
77 return Err(crate::Error::ReservedBitsViolation {
78 field: "6-bit RFU after freq_source",
79 reason: "Must be zero (ETSI TS 102 773 §5.2.4)",
80 });
81 }
82
83 Ok(L1CurrentPayload {
84 frame_idx,
85 freq_source,
86 l1_current_data: &bytes[L1_CURRENT_HEADER_LEN..],
87 })
88 }
89}
90
91impl<'a> crate::traits::PayloadDef<'a> for L1CurrentPayload<'a> {
92 const PACKET_TYPE: u8 = 0x10;
93 const NAME: &'static str = "L1_CURRENT";
94}
95
96impl Serialize for L1CurrentPayload<'_> {
97 type Error = crate::error::Error;
98
99 fn serialized_len(&self) -> usize {
100 L1_CURRENT_HEADER_LEN + self.l1_current_data.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
111 buf[0] = self.frame_idx;
112 buf[1] = (u8::from(self.freq_source) << 6) & 0xC0; if !self.l1_current_data.is_empty() {
115 buf[L1_CURRENT_HEADER_LEN..L1_CURRENT_HEADER_LEN + self.l1_current_data.len()]
116 .copy_from_slice(self.l1_current_data);
117 }
118
119 Ok(self.serialized_len())
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn frequency_source_try_from_valid() {
129 assert_eq!(
130 FrequencySource::try_from(0b00),
131 Ok(FrequencySource::UseL1CurrentData)
132 );
133 assert_eq!(
134 FrequencySource::try_from(0b01),
135 Ok(FrequencySource::UseIndividualAddressing)
136 );
137 assert_eq!(
138 FrequencySource::try_from(0b10),
139 Ok(FrequencySource::ManualPerModulator)
140 );
141 }
142
143 #[test]
144 fn frequency_source_try_from_rejects_11() {
145 assert!(FrequencySource::try_from(0b11).is_err());
146 }
147
148 #[test]
149 fn exhaustive_byte_sweep() {
150 let mut matched = 0u16;
151 for byte in 0u8..=0xFF {
152 if let Ok(v) = FrequencySource::try_from(byte) {
153 assert_eq!(v as u8, byte, "round-trip failed for {byte:#04x}");
154 matched += 1;
155 }
156 }
157 assert_eq!(matched, 3, "expected 3 matched variants");
158 }
159
160 #[test]
161 fn parse_extracts_frame_idx_and_freq_source() {
162 let buf = [0x42u8, 0x80, 0xDE, 0xAD]; let result = L1CurrentPayload::parse(&buf).unwrap();
164 assert_eq!(result.frame_idx, 0x42);
165 assert_eq!(result.freq_source, FrequencySource::ManualPerModulator);
166 assert_eq!(result.l1_current_data, &[0xDE, 0xAD]);
167 }
168
169 #[test]
170 fn parse_rejects_nonzero_rfu() {
171 let buf = [0x00u8, 0x01, 0x00]; assert!(L1CurrentPayload::parse(&buf).is_err());
173 }
174
175 #[test]
176 fn serialize_round_trip() {
177 let orig = L1CurrentPayload {
178 frame_idx: 0xAB,
179 freq_source: FrequencySource::UseL1CurrentData,
180 l1_current_data: &[0x12, 0x34, 0x56],
181 };
182 let mut buf = vec![0u8; orig.serialized_len()];
183 orig.serialize_into(&mut buf).unwrap();
184 let parsed = L1CurrentPayload::parse(&buf).unwrap();
185 assert_eq!(orig, parsed);
186 }
187
188 #[test]
189 fn serialize_zeros_rfu_bits() {
190 let payload = L1CurrentPayload {
191 frame_idx: 0x10,
192 freq_source: FrequencySource::UseIndividualAddressing,
193 l1_current_data: &[],
194 };
195 let mut buf = [0xFFu8; 2];
196 payload.serialize_into(&mut buf).unwrap();
197 assert_eq!(buf[1] & 0x3F, 0x00);
198 }
199}