1use super::descriptor_body;
8use crate::error::{Error, Result};
9use dvb_common::{Parse, Serialize};
10
11pub const TAG: u8 = 0x62;
13pub const HEADER_LEN: usize = 2;
15pub const CODING_BYTE_LEN: usize = 1;
17pub const ENTRY_LEN: usize = 4;
19pub const CODING_TYPE_MASK: u8 = 0x03;
21pub const RESERVED_BITS_MASK: u8 = 0xFC;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27pub enum CodingType {
28 Undefined,
30 Satellite,
32 Cable,
34 Terrestrial,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize))]
41pub struct FrequencyListDescriptor {
42 pub coding_type: CodingType,
44 pub centre_frequencies_bcd: Vec<[u8; 4]>,
46}
47
48impl FrequencyListDescriptor {
49 fn hz_per_unit(&self) -> Option<u64> {
54 match self.coding_type {
55 CodingType::Satellite => Some(1_000),
56 CodingType::Cable | CodingType::Terrestrial => Some(100),
57 CodingType::Undefined => None,
58 }
59 }
60
61 #[must_use]
65 pub fn centre_frequencies_hz(&self) -> Vec<Option<u64>> {
66 let scale = self.hz_per_unit();
67 self.centre_frequencies_bcd
68 .iter()
69 .map(|b| {
70 let value = dvb_common::bcd::bcd_to_decimal(u64::from(u32::from_be_bytes(*b)), 8)?;
71 Some(value * scale?)
72 })
73 .collect()
74 }
75
76 pub fn set_centre_frequencies_hz(&mut self, frequencies_hz: &[u64]) -> crate::Result<()> {
83 let scale = self.hz_per_unit().ok_or(crate::Error::ValueOutOfRange {
84 field: "FrequencyListDescriptor::centre_frequency",
85 reason: "coding_type is Undefined; cannot encode frequencies",
86 })?;
87 let mut out = Vec::with_capacity(frequencies_hz.len());
88 for &hz in frequencies_hz {
89 let bcd = super::encode_bcd_field(
90 hz / scale,
91 8,
92 "FrequencyListDescriptor::centre_frequency",
93 )?;
94 out.push((bcd as u32).to_be_bytes());
95 }
96 self.centre_frequencies_bcd = out;
97 Ok(())
98 }
99}
100
101impl<'a> Parse<'a> for FrequencyListDescriptor {
102 type Error = crate::error::Error;
103 fn parse(bytes: &'a [u8]) -> Result<Self> {
104 let body = descriptor_body(bytes, TAG, "FrequencyListDescriptor", "expected tag 0x62")?;
105
106 if body.len() < CODING_BYTE_LEN {
107 return Err(Error::InvalidDescriptor {
108 tag: TAG,
109 reason: "body too short (need at least coding_type byte)",
110 });
111 }
112
113 if (body.len() - CODING_BYTE_LEN) % ENTRY_LEN != 0 {
114 return Err(Error::InvalidDescriptor {
115 tag: TAG,
116 reason: "body length minus coding byte must be multiple of 4",
117 });
118 }
119
120 let coding_byte = body[0];
121 let coding_type_value = coding_byte & CODING_TYPE_MASK;
124 let coding_type = match coding_type_value {
125 0b00 => CodingType::Undefined,
126 0b01 => CodingType::Satellite,
127 0b10 => CodingType::Cable,
128 _ => CodingType::Terrestrial,
129 };
130
131 let entry_count = (body.len() - CODING_BYTE_LEN) / ENTRY_LEN;
132 let mut centre_frequencies_bcd = Vec::with_capacity(entry_count);
133
134 let mut offset = CODING_BYTE_LEN;
135 for _ in 0..entry_count {
136 let mut entry = [0u8; ENTRY_LEN];
137 entry.copy_from_slice(&body[offset..offset + ENTRY_LEN]);
138 centre_frequencies_bcd.push(entry);
139 offset += ENTRY_LEN;
140 }
141
142 Ok(FrequencyListDescriptor {
143 coding_type,
144 centre_frequencies_bcd,
145 })
146 }
147}
148
149impl Serialize for FrequencyListDescriptor {
150 type Error = crate::error::Error;
151 fn serialized_len(&self) -> usize {
152 HEADER_LEN + CODING_BYTE_LEN + self.centre_frequencies_bcd.len() * ENTRY_LEN
153 }
154
155 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
156 let need = self.serialized_len();
157 if buf.len() < need {
158 return Err(Error::OutputBufferTooSmall {
159 need,
160 have: buf.len(),
161 });
162 }
163
164 let coding_type_bits = match self.coding_type {
165 CodingType::Undefined => 0b00,
166 CodingType::Satellite => 0b01,
167 CodingType::Cable => 0b10,
168 CodingType::Terrestrial => 0b11,
169 };
170
171 let body_length = CODING_BYTE_LEN + self.centre_frequencies_bcd.len() * ENTRY_LEN;
172
173 buf[0] = TAG;
174 buf[1] = body_length as u8;
175 buf[HEADER_LEN] = RESERVED_BITS_MASK | coding_type_bits;
176
177 let mut offset = HEADER_LEN + CODING_BYTE_LEN;
178 for entry in &self.centre_frequencies_bcd {
179 buf[offset..offset + ENTRY_LEN].copy_from_slice(entry);
180 offset += ENTRY_LEN;
181 }
182
183 Ok(need)
184 }
185}
186impl<'a> crate::traits::DescriptorDef<'a> for FrequencyListDescriptor {
187 const TAG: u8 = TAG;
188 const NAME: &'static str = "FREQUENCY_LIST";
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
197 fn parse_empty_entries_is_valid() {
198 let raw: Vec<u8> = vec![TAG, 0x01, 0xFC];
199 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
200 assert!(desc.centre_frequencies_bcd.is_empty());
201 assert!(matches!(desc.coding_type, CodingType::Undefined));
202 }
203
204 #[test]
206 fn parse_extracts_coding_type_satellite() {
207 let raw: Vec<u8> = vec![TAG, 0x01, 0xFD];
208 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
209 assert!(matches!(desc.coding_type, CodingType::Satellite));
210 }
211
212 #[test]
214 fn parse_extracts_coding_type_cable() {
215 let raw: Vec<u8> = vec![TAG, 0x01, 0xFE];
216 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
217 assert!(matches!(desc.coding_type, CodingType::Cable));
218 }
219
220 #[test]
222 fn parse_extracts_coding_type_terrestrial() {
223 let raw: Vec<u8> = vec![TAG, 0x01, 0xFF];
224 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
225 assert!(matches!(desc.coding_type, CodingType::Terrestrial));
226 }
227
228 #[test]
230 fn parse_extracts_multiple_frequency_entries() {
231 let raw: Vec<u8> = vec![
232 TAG, 0x09, 0xFD, 0x02, 0x75, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, ];
237 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
238 assert_eq!(desc.centre_frequencies_bcd.len(), 2);
239 assert_eq!(desc.centre_frequencies_bcd[0], [0x02, 0x75, 0x00, 0x00]);
240 assert_eq!(desc.centre_frequencies_bcd[1], [0x03, 0x00, 0x00, 0x00]);
241 }
242
243 #[test]
245 fn parse_rejects_wrong_tag() {
246 let raw: Vec<u8> = vec![0x63, 0x01, 0xFC];
247 let err = FrequencyListDescriptor::parse(&raw).unwrap_err();
248 assert!(
249 matches!(err, Error::InvalidDescriptor { tag: 0x63, .. }),
250 "expected InvalidDescriptor(tag=0x63), got {err:?}"
251 );
252 }
253
254 #[test]
256 fn parse_ignores_reserved_bits() {
257 let raw: Vec<u8> = vec![TAG, 0x01, 0x03];
259 let d = FrequencyListDescriptor::parse(&raw).unwrap();
260 assert_eq!(d.coding_type, CodingType::Terrestrial);
261 assert!(d.centre_frequencies_bcd.is_empty());
262 }
263
264 #[test]
266 fn parse_rejects_length_not_1_plus_multiple_of_4() {
267 let raw: Vec<u8> = vec![TAG, 0x03, 0xFC, 0x01, 0x02]; let err = FrequencyListDescriptor::parse(&raw).unwrap_err();
269 assert!(matches!(err, Error::InvalidDescriptor { .. }));
270 }
271
272 #[test]
274 fn parse_rejects_truncated_buffer() {
275 let raw: &[u8] = &[TAG];
276 let err = FrequencyListDescriptor::parse(raw).unwrap_err();
277 assert!(matches!(err, Error::BufferTooShort { need: 2, .. }));
278 }
279
280 #[test]
282 fn serialize_round_trip_empty() {
283 let desc = FrequencyListDescriptor {
284 coding_type: CodingType::Satellite,
285 centre_frequencies_bcd: vec![],
286 };
287 let raw: Vec<u8> = vec![TAG, 0x01, 0xFD];
288 let mut buf = vec![0u8; desc.serialized_len()];
289 let written = desc.serialize_into(&mut buf).unwrap();
290 assert_eq!(written, raw.len());
291 assert_eq!(buf, raw);
292
293 let reparsed = FrequencyListDescriptor::parse(&buf).unwrap();
294 assert_eq!(desc.coding_type, reparsed.coding_type);
295 assert_eq!(desc.centre_frequencies_bcd, reparsed.centre_frequencies_bcd);
296 }
297
298 #[test]
300 fn serialize_round_trip_many_entries() {
301 let desc = FrequencyListDescriptor {
302 coding_type: CodingType::Cable,
303 centre_frequencies_bcd: vec![
304 [0x02, 0x75, 0x00, 0x00],
305 [0x03, 0x00, 0x00, 0x00],
306 [0x01, 0x15, 0x50, 0x00],
307 [0x04, 0x90, 0x25, 0x00],
308 ],
309 };
310 let mut buf = vec![0u8; desc.serialized_len()];
311 desc.serialize_into(&mut buf).unwrap();
312 let reparsed = FrequencyListDescriptor::parse(&buf).unwrap();
313 assert_eq!(desc.coding_type, reparsed.coding_type);
314 assert_eq!(desc.centre_frequencies_bcd, reparsed.centre_frequencies_bcd);
315 }
316}