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