1use super::descriptor_body;
8use crate::error::{Error, Result};
9use alloc::vec::Vec;
10use dvb_common::{Parse, Serialize};
11
12pub const TAG: u8 = 0x62;
14pub const HEADER_LEN: usize = 2;
16pub const CODING_BYTE_LEN: usize = 1;
18pub const ENTRY_LEN: usize = 4;
20pub const CODING_TYPE_MASK: u8 = 0x03;
22pub const RESERVED_BITS_MASK: u8 = 0xFC;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize))]
28#[non_exhaustive]
29pub enum CodingType {
30 Undefined,
32 Satellite,
34 Cable,
36 Terrestrial,
38}
39
40impl CodingType {
41 #[must_use]
43 pub fn name(self) -> &'static str {
44 match self {
45 Self::Undefined => "not defined",
46 Self::Satellite => "satellite",
47 Self::Cable => "cable",
48 Self::Terrestrial => "terrestrial",
49 }
50 }
51}
52dvb_common::impl_spec_display!(CodingType);
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize))]
57pub struct FrequencyListDescriptor {
58 pub coding_type: CodingType,
60 pub centre_frequencies_bcd: Vec<[u8; 4]>,
63}
64
65impl FrequencyListDescriptor {
66 fn hz_per_unit_bcd(&self) -> Option<u64> {
69 match self.coding_type {
70 CodingType::Satellite => Some(10_000),
71 CodingType::Cable => Some(100),
72 CodingType::Terrestrial | CodingType::Undefined => None,
73 }
74 }
75
76 #[must_use]
80 pub fn centre_frequencies_hz(&self) -> Vec<Option<u64>> {
81 match self.coding_type {
82 CodingType::Satellite | CodingType::Cable => {
83 let scale = self.hz_per_unit_bcd().unwrap();
84 self.centre_frequencies_bcd
85 .iter()
86 .map(|b| {
87 let value =
88 dvb_common::bcd::bcd_to_decimal(u64::from(u32::from_be_bytes(*b)), 8)?;
89 Some(value * scale)
90 })
91 .collect()
92 }
93 CodingType::Terrestrial => self
94 .centre_frequencies_bcd
95 .iter()
96 .map(|b| Some(u64::from(u32::from_be_bytes(*b)) * 10))
97 .collect(),
98 CodingType::Undefined => self.centre_frequencies_bcd.iter().map(|_| None).collect(),
99 }
100 }
101
102 pub fn set_centre_frequencies_hz(&mut self, frequencies_hz: &[u64]) -> crate::Result<()> {
110 match self.coding_type {
111 CodingType::Satellite | CodingType::Cable => {
112 let scale = self
113 .hz_per_unit_bcd()
114 .ok_or(crate::Error::ValueOutOfRange {
115 field: "FrequencyListDescriptor::centre_frequency",
116 reason: "coding_type is Undefined; cannot encode frequencies",
117 })?;
118 let mut out = Vec::with_capacity(frequencies_hz.len());
119 for &hz in frequencies_hz {
120 let bcd = super::encode_bcd_field(
121 hz / scale,
122 8,
123 "FrequencyListDescriptor::centre_frequency",
124 )?;
125 out.push((bcd as u32).to_be_bytes());
126 }
127 self.centre_frequencies_bcd = out;
128 Ok(())
129 }
130 CodingType::Terrestrial => {
131 let mut out = Vec::with_capacity(frequencies_hz.len());
132 for &hz in frequencies_hz {
133 let units = hz / 10;
134 if units > u64::from(u32::MAX) {
135 return Err(Error::ValueOutOfRange {
136 field: "frequency_list centre_frequency",
137 reason: "terrestrial frequency exceeds the 32-bit (×10 Hz) wire field",
138 });
139 }
140 out.push((units as u32).to_be_bytes());
141 }
142 self.centre_frequencies_bcd = out;
143 Ok(())
144 }
145 CodingType::Undefined => Err(crate::Error::ValueOutOfRange {
146 field: "FrequencyListDescriptor::centre_frequency",
147 reason: "coding_type is Undefined; cannot encode frequencies",
148 }),
149 }
150 }
151}
152
153impl<'a> Parse<'a> for FrequencyListDescriptor {
154 type Error = crate::error::Error;
155 fn parse(bytes: &'a [u8]) -> Result<Self> {
156 let body = descriptor_body(bytes, TAG, "FrequencyListDescriptor", "expected tag 0x62")?;
157
158 if body.len() < CODING_BYTE_LEN {
159 return Err(Error::InvalidDescriptor {
160 tag: TAG,
161 reason: "body too short (need at least coding_type byte)",
162 });
163 }
164
165 if (body.len() - CODING_BYTE_LEN) % ENTRY_LEN != 0 {
166 return Err(Error::InvalidDescriptor {
167 tag: TAG,
168 reason: "body length minus coding byte must be multiple of 4",
169 });
170 }
171
172 let coding_byte = body[0];
173 let coding_type_value = coding_byte & CODING_TYPE_MASK;
176 let coding_type = match coding_type_value {
177 0b00 => CodingType::Undefined,
178 0b01 => CodingType::Satellite,
179 0b10 => CodingType::Cable,
180 _ => CodingType::Terrestrial,
181 };
182
183 let entry_count = (body.len() - CODING_BYTE_LEN) / ENTRY_LEN;
184 let mut centre_frequencies_bcd = Vec::with_capacity(entry_count);
185
186 let mut offset = CODING_BYTE_LEN;
187 for _ in 0..entry_count {
188 let mut entry = [0u8; ENTRY_LEN];
189 entry.copy_from_slice(&body[offset..offset + ENTRY_LEN]);
190 centre_frequencies_bcd.push(entry);
191 offset += ENTRY_LEN;
192 }
193
194 Ok(FrequencyListDescriptor {
195 coding_type,
196 centre_frequencies_bcd,
197 })
198 }
199}
200
201impl Serialize for FrequencyListDescriptor {
202 type Error = crate::error::Error;
203 fn serialized_len(&self) -> usize {
204 HEADER_LEN + CODING_BYTE_LEN + self.centre_frequencies_bcd.len() * ENTRY_LEN
205 }
206
207 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
208 let need = self.serialized_len();
209 if buf.len() < need {
210 return Err(Error::OutputBufferTooSmall {
211 need,
212 have: buf.len(),
213 });
214 }
215
216 let coding_type_bits = match self.coding_type {
217 CodingType::Undefined => 0b00,
218 CodingType::Satellite => 0b01,
219 CodingType::Cable => 0b10,
220 CodingType::Terrestrial => 0b11,
221 };
222
223 let body_length = CODING_BYTE_LEN + self.centre_frequencies_bcd.len() * ENTRY_LEN;
224
225 buf[0] = TAG;
226 buf[1] = body_length as u8;
227 buf[HEADER_LEN] = RESERVED_BITS_MASK | coding_type_bits;
228
229 let mut offset = HEADER_LEN + CODING_BYTE_LEN;
230 for entry in &self.centre_frequencies_bcd {
231 buf[offset..offset + ENTRY_LEN].copy_from_slice(entry);
232 offset += ENTRY_LEN;
233 }
234
235 Ok(need)
236 }
237}
238impl<'a> crate::traits::DescriptorDef<'a> for FrequencyListDescriptor {
239 const TAG: u8 = TAG;
240 const NAME: &'static str = "FREQUENCY_LIST";
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
249 fn parse_empty_entries_is_valid() {
250 let raw: Vec<u8> = vec![TAG, 0x01, 0xFC];
251 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
252 assert!(desc.centre_frequencies_bcd.is_empty());
253 assert!(matches!(desc.coding_type, CodingType::Undefined));
254 }
255
256 #[test]
258 fn parse_extracts_coding_type_satellite() {
259 let raw: Vec<u8> = vec![TAG, 0x01, 0xFD];
260 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
261 assert!(matches!(desc.coding_type, CodingType::Satellite));
262 }
263
264 #[test]
266 fn parse_extracts_coding_type_cable() {
267 let raw: Vec<u8> = vec![TAG, 0x01, 0xFE];
268 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
269 assert!(matches!(desc.coding_type, CodingType::Cable));
270 }
271
272 #[test]
274 fn parse_extracts_coding_type_terrestrial() {
275 let raw: Vec<u8> = vec![TAG, 0x01, 0xFF];
276 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
277 assert!(matches!(desc.coding_type, CodingType::Terrestrial));
278 }
279
280 #[test]
282 fn parse_extracts_multiple_frequency_entries() {
283 let raw: Vec<u8> = vec![
284 TAG, 0x09, 0xFD, 0x00, 0x30, 0x12, 0x34, 0x00, 0x30, 0x00, 0x00, ];
289 let desc = FrequencyListDescriptor::parse(&raw).unwrap();
290 assert_eq!(desc.centre_frequencies_bcd.len(), 2);
291 assert_eq!(desc.centre_frequencies_bcd[0], [0x00, 0x30, 0x12, 0x34]);
292 assert_eq!(desc.centre_frequencies_bcd[1], [0x00, 0x30, 0x00, 0x00]);
293 }
294
295 #[test]
297 fn parse_rejects_wrong_tag() {
298 let raw: Vec<u8> = vec![0x63, 0x01, 0xFC];
299 let err = FrequencyListDescriptor::parse(&raw).unwrap_err();
300 assert!(
301 matches!(err, Error::InvalidDescriptor { tag: 0x63, .. }),
302 "expected InvalidDescriptor(tag=0x63), got {err:?}"
303 );
304 }
305
306 #[test]
308 fn parse_ignores_reserved_bits() {
309 let raw: Vec<u8> = vec![TAG, 0x01, 0x03];
311 let d = FrequencyListDescriptor::parse(&raw).unwrap();
312 assert_eq!(d.coding_type, CodingType::Terrestrial);
313 assert!(d.centre_frequencies_bcd.is_empty());
314 }
315
316 #[test]
318 fn parse_rejects_length_not_1_plus_multiple_of_4() {
319 let raw: Vec<u8> = vec![TAG, 0x03, 0xFC, 0x01, 0x02]; let err = FrequencyListDescriptor::parse(&raw).unwrap_err();
321 assert!(matches!(err, Error::InvalidDescriptor { .. }));
322 }
323
324 #[test]
326 fn parse_rejects_truncated_buffer() {
327 let raw: &[u8] = &[TAG];
328 let err = FrequencyListDescriptor::parse(raw).unwrap_err();
329 assert!(matches!(err, Error::BufferTooShort { need: 2, .. }));
330 }
331
332 #[test]
334 fn serialize_round_trip_empty() {
335 let desc = FrequencyListDescriptor {
336 coding_type: CodingType::Satellite,
337 centre_frequencies_bcd: vec![],
338 };
339 let raw: Vec<u8> = vec![TAG, 0x01, 0xFD];
340 let mut buf = vec![0u8; desc.serialized_len()];
341 let written = desc.serialize_into(&mut buf).unwrap();
342 assert_eq!(written, raw.len());
343 assert_eq!(buf, raw);
344
345 let reparsed = FrequencyListDescriptor::parse(&buf).unwrap();
346 assert_eq!(desc.coding_type, reparsed.coding_type);
347 assert_eq!(desc.centre_frequencies_bcd, reparsed.centre_frequencies_bcd);
348 }
349
350 #[test]
352 fn serialize_round_trip_many_entries() {
353 let desc = FrequencyListDescriptor {
354 coding_type: CodingType::Cable,
355 centre_frequencies_bcd: vec![
356 [0x03, 0x46, 0x00, 0x00],
357 [0x04, 0x74, 0x00, 0x10],
358 [0x01, 0x15, 0x50, 0x00],
359 [0x04, 0x90, 0x25, 0x00],
360 ],
361 };
362 let mut buf = vec![0u8; desc.serialized_len()];
363 desc.serialize_into(&mut buf).unwrap();
364 let reparsed = FrequencyListDescriptor::parse(&buf).unwrap();
365 assert_eq!(desc.coding_type, reparsed.coding_type);
366 assert_eq!(desc.centre_frequencies_bcd, reparsed.centre_frequencies_bcd);
367 }
368
369 #[test]
370 fn satellite_frequency_hz_decodes_correctly() {
371 let desc = FrequencyListDescriptor {
372 coding_type: CodingType::Satellite,
373 centre_frequencies_bcd: vec![[0x01, 0x17, 0x25, 0x00]], };
375 assert_eq!(desc.centre_frequencies_hz(), vec![Some(11_725_000_000)]);
376 }
377
378 #[test]
379 fn cable_frequency_hz_decodes_correctly() {
380 let desc = FrequencyListDescriptor {
381 coding_type: CodingType::Cable,
382 centre_frequencies_bcd: vec![[0x03, 0x46, 0x00, 0x00]], };
384 assert_eq!(desc.centre_frequencies_hz(), vec![Some(346_000_000)]);
385 }
386
387 #[test]
388 fn terrestrial_frequency_hz_decodes_binary() {
389 let desc = FrequencyListDescriptor {
390 coding_type: CodingType::Terrestrial,
391 centre_frequencies_bcd: vec![[0x04, 0xA8, 0x58, 0xF0]],
393 };
394 assert_eq!(desc.centre_frequencies_hz(), vec![Some(781_416_800)]);
395 }
396
397 #[test]
398 fn set_satellite_frequencies_hz_round_trips() {
399 let mut desc = FrequencyListDescriptor {
400 coding_type: CodingType::Satellite,
401 centre_frequencies_bcd: vec![],
402 };
403 desc.set_centre_frequencies_hz(&[11_725_000_000]).unwrap();
404 assert_eq!(desc.centre_frequencies_hz(), vec![Some(11_725_000_000)]);
405 assert_eq!(desc.centre_frequencies_bcd[0], [0x01, 0x17, 0x25, 0x00]);
406 }
407
408 #[test]
409 fn set_terrestrial_frequencies_hz_round_trips() {
410 let mut desc = FrequencyListDescriptor {
411 coding_type: CodingType::Terrestrial,
412 centre_frequencies_bcd: vec![],
413 };
414 desc.set_centre_frequencies_hz(&[781_416_800]).unwrap();
415 assert_eq!(desc.centre_frequencies_hz(), vec![Some(781_416_800)]);
416 assert_eq!(desc.centre_frequencies_bcd[0], [0x04, 0xA8, 0x58, 0xF0]);
417 }
418
419 #[test]
420 fn set_cable_frequencies_hz_round_trips() {
421 let mut desc = FrequencyListDescriptor {
422 coding_type: CodingType::Cable,
423 centre_frequencies_bcd: vec![],
424 };
425 desc.set_centre_frequencies_hz(&[346_000_000]).unwrap();
426 assert_eq!(desc.centre_frequencies_hz(), vec![Some(346_000_000)]);
427 }
428}