1use super::descriptor_body;
8use crate::error::{Error, Result};
9use dvb_common::{Parse, Serialize};
10
11pub const TAG: u8 = 0x5A;
13const HEADER_LEN: usize = 2;
14const BODY_LEN: u8 = 11;
15
16const BW_SHIFT: u8 = 5;
17const PRIORITY_MASK: u8 = 0b0001_0000;
18const TIME_SLICING_MASK: u8 = 0b0000_1000;
19const MPE_FEC_MASK: u8 = 0b0000_0100;
20const RESERVED_FU_MASK: u8 = 0b0000_0011;
21const BW_MASK: u8 = 0b1110_0000;
22
23const CONSTELLATION_SHIFT: u8 = 6;
24const HIERARCHY_SHIFT: u8 = 3;
25const CONSTELLATION_MASK: u8 = 0b1100_0000;
26const HIERARCHY_MASK: u8 = 0b0011_1000;
27const CODE_RATE_HP_MASK: u8 = 0b0000_0111;
28
29const CODE_RATE_LP_SHIFT: u8 = 5;
30const GUARD_INTERVAL_SHIFT: u8 = 3;
31const TRANSMISSION_MODE_SHIFT: u8 = 1;
32const CODE_RATE_LP_MASK: u8 = 0b1110_0000;
33const GUARD_INTERVAL_MASK: u8 = 0b0001_1000;
34const TRANSMISSION_MODE_MASK: u8 = 0b0000_0110;
35const OTHER_FREQ_FLAG_MASK: u8 = 0b0000_0001;
36
37const TRAILING_RESERVED: u32 = 0xFFFF_FFFF;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize))]
42#[non_exhaustive]
43pub enum Bandwidth {
44 Mhz8,
46 Mhz7,
48 Mhz6,
50 Mhz5,
52 Reserved(u8),
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize))]
59#[non_exhaustive]
60pub enum Constellation {
61 Qpsk,
63 Qam16,
65 Qam64,
67 Reserved(u8),
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74#[non_exhaustive]
75pub enum Hierarchy {
76 NonHierarchicalNative,
78 Alpha1Native,
80 Alpha2Native,
82 Alpha4Native,
84 NonHierarchicalInDepth,
86 Alpha1InDepth,
88 Alpha2InDepth,
90 Alpha4InDepth,
92 Reserved(u8),
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98#[cfg_attr(feature = "serde", derive(serde::Serialize))]
99#[non_exhaustive]
100pub enum CodeRate {
101 Rate1_2,
103 Rate2_3,
105 Rate3_4,
107 Rate5_6,
109 Rate7_8,
111 Reserved(u8),
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117#[cfg_attr(feature = "serde", derive(serde::Serialize))]
118pub enum GuardInterval {
119 G1_32,
121 G1_16,
123 G1_8,
125 G1_4,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize))]
132#[non_exhaustive]
133pub enum TransmissionMode {
134 Mode2k,
136 Mode8k,
138 Mode4k,
140 Reserved(u8),
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize))]
147pub struct TerrestrialDeliverySystemDescriptor {
148 pub centre_frequency_10hz: u32,
150 pub bandwidth: Bandwidth,
152 pub priority: bool,
154 pub time_slicing_used: bool,
156 pub mpe_fec_used: bool,
158 pub constellation: Constellation,
160 pub hierarchy: Hierarchy,
162 pub code_rate_hp: CodeRate,
164 pub code_rate_lp: CodeRate,
166 pub guard_interval: GuardInterval,
168 pub transmission_mode: TransmissionMode,
170 pub other_frequency_flag: bool,
172}
173
174impl TerrestrialDeliverySystemDescriptor {
175 #[must_use]
178 pub fn centre_frequency_hz(&self) -> u64 {
179 u64::from(self.centre_frequency_10hz) * 10
180 }
181
182 pub fn set_centre_frequency_hz(&mut self, hz: u64) -> crate::Result<()> {
189 let units = hz / 10;
190 if units > u64::from(u32::MAX) {
191 return Err(crate::Error::ValueOutOfRange {
192 field: "TerrestrialDeliverySystemDescriptor::centre_frequency",
193 reason: "frequency exceeds the 32-bit (10 Hz) field",
194 });
195 }
196 self.centre_frequency_10hz = units as u32;
197 Ok(())
198 }
199}
200
201fn parse_bandwidth(raw: u8) -> Bandwidth {
202 match raw {
203 0 => Bandwidth::Mhz8,
204 1 => Bandwidth::Mhz7,
205 2 => Bandwidth::Mhz6,
206 3 => Bandwidth::Mhz5,
207 other => Bandwidth::Reserved(other),
208 }
209}
210
211fn parse_constellation(raw: u8) -> Constellation {
212 match raw {
213 0 => Constellation::Qpsk,
214 1 => Constellation::Qam16,
215 2 => Constellation::Qam64,
216 other => Constellation::Reserved(other),
217 }
218}
219
220fn parse_hierarchy(raw: u8) -> Hierarchy {
221 match raw {
222 0 => Hierarchy::NonHierarchicalNative,
223 1 => Hierarchy::Alpha1Native,
224 2 => Hierarchy::Alpha2Native,
225 3 => Hierarchy::Alpha4Native,
226 4 => Hierarchy::NonHierarchicalInDepth,
227 5 => Hierarchy::Alpha1InDepth,
228 6 => Hierarchy::Alpha2InDepth,
229 7 => Hierarchy::Alpha4InDepth,
230 other => Hierarchy::Reserved(other),
231 }
232}
233
234fn parse_code_rate(raw: u8) -> CodeRate {
235 match raw {
236 0 => CodeRate::Rate1_2,
237 1 => CodeRate::Rate2_3,
238 2 => CodeRate::Rate3_4,
239 3 => CodeRate::Rate5_6,
240 4 => CodeRate::Rate7_8,
241 other => CodeRate::Reserved(other),
242 }
243}
244
245fn parse_guard_interval(raw: u8) -> GuardInterval {
246 match raw {
247 0 => GuardInterval::G1_32,
248 1 => GuardInterval::G1_16,
249 2 => GuardInterval::G1_8,
250 3 => GuardInterval::G1_4,
251 _ => GuardInterval::G1_32,
252 }
253}
254
255fn parse_transmission_mode(raw: u8) -> TransmissionMode {
256 match raw {
257 0 => TransmissionMode::Mode2k,
258 1 => TransmissionMode::Mode8k,
259 2 => TransmissionMode::Mode4k,
260 other => TransmissionMode::Reserved(other),
261 }
262}
263
264fn serialize_bandwidth(bw: Bandwidth) -> u8 {
265 match bw {
266 Bandwidth::Mhz8 => 0,
267 Bandwidth::Mhz7 => 1,
268 Bandwidth::Mhz6 => 2,
269 Bandwidth::Mhz5 => 3,
270 Bandwidth::Reserved(v) => v,
271 }
272}
273
274fn serialize_constellation(c: Constellation) -> u8 {
275 match c {
276 Constellation::Qpsk => 0,
277 Constellation::Qam16 => 1,
278 Constellation::Qam64 => 2,
279 Constellation::Reserved(v) => v,
280 }
281}
282
283fn serialize_hierarchy(h: Hierarchy) -> u8 {
284 match h {
285 Hierarchy::NonHierarchicalNative => 0,
286 Hierarchy::Alpha1Native => 1,
287 Hierarchy::Alpha2Native => 2,
288 Hierarchy::Alpha4Native => 3,
289 Hierarchy::NonHierarchicalInDepth => 4,
290 Hierarchy::Alpha1InDepth => 5,
291 Hierarchy::Alpha2InDepth => 6,
292 Hierarchy::Alpha4InDepth => 7,
293 Hierarchy::Reserved(v) => v,
294 }
295}
296
297fn serialize_code_rate(cr: CodeRate) -> u8 {
298 match cr {
299 CodeRate::Rate1_2 => 0,
300 CodeRate::Rate2_3 => 1,
301 CodeRate::Rate3_4 => 2,
302 CodeRate::Rate5_6 => 3,
303 CodeRate::Rate7_8 => 4,
304 CodeRate::Reserved(v) => v,
305 }
306}
307
308fn serialize_guard_interval(gi: GuardInterval) -> u8 {
309 match gi {
310 GuardInterval::G1_32 => 0,
311 GuardInterval::G1_16 => 1,
312 GuardInterval::G1_8 => 2,
313 GuardInterval::G1_4 => 3,
314 }
315}
316
317fn serialize_transmission_mode(tm: TransmissionMode) -> u8 {
318 match tm {
319 TransmissionMode::Mode2k => 0,
320 TransmissionMode::Mode8k => 1,
321 TransmissionMode::Mode4k => 2,
322 TransmissionMode::Reserved(v) => v,
323 }
324}
325
326impl<'a> Parse<'a> for TerrestrialDeliverySystemDescriptor {
327 type Error = crate::error::Error;
328 fn parse(bytes: &'a [u8]) -> Result<Self> {
329 let body = descriptor_body(
330 bytes,
331 TAG,
332 "TerrestrialDeliverySystemDescriptor",
333 "unexpected tag for terrestrial_delivery_system_descriptor",
334 )?;
335 if body.len() != BODY_LEN as usize {
336 return Err(Error::InvalidDescriptor {
337 tag: TAG,
338 reason: "body length must equal 11",
339 });
340 }
341
342 let centre_frequency_10hz = u32::from_be_bytes(body[0..4].try_into().unwrap());
343
344 let byte4 = body[4];
345 let bw_raw = (byte4 & BW_MASK) >> BW_SHIFT;
346 let priority = (byte4 & PRIORITY_MASK) != 0;
347 let time_slicing_used = (byte4 & TIME_SLICING_MASK) == 0;
348 let mpe_fec_used = (byte4 & MPE_FEC_MASK) == 0;
349
350 let byte5 = body[5];
351 let constellation_raw = (byte5 & CONSTELLATION_MASK) >> CONSTELLATION_SHIFT;
352 let hierarchy_raw = (byte5 & HIERARCHY_MASK) >> HIERARCHY_SHIFT;
353 let code_rate_hp_raw = byte5 & CODE_RATE_HP_MASK;
354
355 let byte6 = body[6];
356 let code_rate_lp_raw = (byte6 & CODE_RATE_LP_MASK) >> CODE_RATE_LP_SHIFT;
357 let guard_interval_raw = (byte6 & GUARD_INTERVAL_MASK) >> GUARD_INTERVAL_SHIFT;
358 let transmission_mode_raw = (byte6 & TRANSMISSION_MODE_MASK) >> TRANSMISSION_MODE_SHIFT;
359 let other_frequency_flag = (byte6 & OTHER_FREQ_FLAG_MASK) != 0;
360
361 Ok(Self {
362 centre_frequency_10hz,
363 bandwidth: parse_bandwidth(bw_raw),
364 priority,
365 time_slicing_used,
366 mpe_fec_used,
367 constellation: parse_constellation(constellation_raw),
368 hierarchy: parse_hierarchy(hierarchy_raw),
369 code_rate_hp: parse_code_rate(code_rate_hp_raw),
370 code_rate_lp: parse_code_rate(code_rate_lp_raw),
371 guard_interval: parse_guard_interval(guard_interval_raw),
372 transmission_mode: parse_transmission_mode(transmission_mode_raw),
373 other_frequency_flag,
374 })
375 }
376}
377
378impl Serialize for TerrestrialDeliverySystemDescriptor {
379 type Error = crate::error::Error;
380 fn serialized_len(&self) -> usize {
381 HEADER_LEN + BODY_LEN as usize
382 }
383
384 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
385 let len = self.serialized_len();
386 if buf.len() < len {
387 return Err(Error::OutputBufferTooSmall {
388 need: len,
389 have: buf.len(),
390 });
391 }
392 buf[0] = TAG;
393 buf[1] = BODY_LEN;
394
395 buf[2..6].copy_from_slice(&self.centre_frequency_10hz.to_be_bytes());
396
397 let byte6 = (serialize_bandwidth(self.bandwidth) << BW_SHIFT)
398 | if self.priority { PRIORITY_MASK } else { 0 }
399 | if !self.time_slicing_used {
400 TIME_SLICING_MASK
401 } else {
402 0
403 }
404 | if !self.mpe_fec_used { MPE_FEC_MASK } else { 0 }
405 | RESERVED_FU_MASK;
406 buf[6] = byte6;
407
408 let byte7 = (serialize_constellation(self.constellation) << CONSTELLATION_SHIFT)
409 | (serialize_hierarchy(self.hierarchy) << HIERARCHY_SHIFT)
410 | serialize_code_rate(self.code_rate_hp);
411 buf[7] = byte7;
412
413 let byte8 = (serialize_code_rate(self.code_rate_lp) << CODE_RATE_LP_SHIFT)
414 | (serialize_guard_interval(self.guard_interval) << GUARD_INTERVAL_SHIFT)
415 | (serialize_transmission_mode(self.transmission_mode) << TRANSMISSION_MODE_SHIFT)
416 | if self.other_frequency_flag {
417 OTHER_FREQ_FLAG_MASK
418 } else {
419 0
420 };
421 buf[8] = byte8;
422
423 buf[9..13].copy_from_slice(&TRAILING_RESERVED.to_be_bytes());
424
425 Ok(len)
426 }
427}
428impl<'a> crate::traits::DescriptorDef<'a> for TerrestrialDeliverySystemDescriptor {
429 const TAG: u8 = TAG;
430 const NAME: &'static str = "TERRESTRIAL_DELIVERY_SYSTEM";
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn parse_extracts_centre_frequency_10hz() {
439 let raw = [
440 TAG, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
441 ];
442 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
443 assert_eq!(d.centre_frequency_10hz, 0x04A858F0);
444 }
445
446 #[test]
447 fn parse_extracts_bandwidth_8mhz() {
448 let raw = [
449 TAG, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
450 ];
451 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
452 assert_eq!(d.bandwidth, Bandwidth::Mhz8);
453 }
454
455 #[test]
456 fn parse_extracts_bandwidth_7mhz() {
457 let raw = [
458 TAG,
459 BODY_LEN,
460 0x04,
461 0xA8,
462 0x58,
463 0xF0,
464 (0b001 << BW_SHIFT),
465 0x00,
466 0x00,
467 0x00,
468 0xFF,
469 0xFF,
470 0xFF,
471 0xFF,
472 ];
473 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
474 assert_eq!(d.bandwidth, Bandwidth::Mhz7);
475 }
476
477 #[test]
478 fn parse_extracts_constellation_qam64() {
479 let raw = [
480 TAG,
481 BODY_LEN,
482 0x04,
483 0xA8,
484 0x58,
485 0xF0,
486 0x00,
487 (0b10 << CONSTELLATION_SHIFT),
488 0x00,
489 0xFF,
490 0xFF,
491 0xFF,
492 0xFF,
493 ];
494 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
495 assert_eq!(d.constellation, Constellation::Qam64);
496 }
497
498 #[test]
499 fn parse_extracts_code_rate_hp_and_lp() {
500 let raw = [
501 TAG,
502 BODY_LEN,
503 0x04,
504 0xA8,
505 0x58,
506 0xF0,
507 0x00,
508 0b10 << CONSTELLATION_SHIFT,
509 0b100 << CODE_RATE_LP_SHIFT,
510 0xFF,
511 0xFF,
512 0xFF,
513 0xFF,
514 ];
515 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
516 assert_eq!(d.code_rate_hp, CodeRate::Rate1_2);
517 assert_eq!(d.code_rate_lp, CodeRate::Rate7_8);
518 }
519
520 #[test]
521 fn parse_extracts_guard_interval_1_4() {
522 let raw = [
523 TAG,
524 BODY_LEN,
525 0x04,
526 0xA8,
527 0x58,
528 0xF0,
529 0x00,
530 0x00,
531 0b11 << GUARD_INTERVAL_SHIFT,
532 0xFF,
533 0xFF,
534 0xFF,
535 0xFF,
536 ];
537 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
538 assert_eq!(d.guard_interval, GuardInterval::G1_4);
539 }
540
541 #[test]
542 fn parse_extracts_transmission_mode_8k() {
543 let raw = [
544 TAG,
545 BODY_LEN,
546 0x04,
547 0xA8,
548 0x58,
549 0xF0,
550 0x00,
551 0x00,
552 0b01 << TRANSMISSION_MODE_SHIFT,
553 0xFF,
554 0xFF,
555 0xFF,
556 0xFF,
557 ];
558 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
559 assert_eq!(d.transmission_mode, TransmissionMode::Mode8k);
560 }
561
562 #[test]
563 fn parse_extracts_other_frequency_flag() {
564 let raw = [
565 TAG,
566 BODY_LEN,
567 0x04,
568 0xA8,
569 0x58,
570 0xF0,
571 0x00,
572 0x00,
573 OTHER_FREQ_FLAG_MASK,
574 0xFF,
575 0xFF,
576 0xFF,
577 0xFF,
578 ];
579 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
580 assert!(d.other_frequency_flag);
581 }
582
583 #[test]
584 fn parse_preserves_reserved_bandwidth_in_reserve_variant() {
585 let raw = [
586 TAG,
587 BODY_LEN,
588 0x04,
589 0xA8,
590 0x58,
591 0xF0,
592 (0b111 << BW_SHIFT),
593 0x00,
594 0x00,
595 0x00,
596 0xFF,
597 0xFF,
598 0xFF,
599 0xFF,
600 ];
601 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
602 assert_eq!(d.bandwidth, Bandwidth::Reserved(0b111));
603 }
604
605 #[test]
606 fn parse_rejects_wrong_tag() {
607 let raw = [
608 0x5B, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
609 ];
610 assert!(matches!(
611 TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap_err(),
612 Error::InvalidDescriptor { tag: 0x5B, .. }
613 ));
614 }
615
616 #[test]
617 fn parse_rejects_wrong_length() {
618 let raw = [
619 TAG, 12, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
620 ];
621 assert!(matches!(
622 TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap_err(),
623 Error::InvalidDescriptor { tag: TAG, .. }
624 ));
625 }
626
627 #[test]
628 fn serialize_round_trip_full_set() {
629 let d = TerrestrialDeliverySystemDescriptor {
630 centre_frequency_10hz: 0x04A858F0,
631 bandwidth: Bandwidth::Mhz8,
632 priority: true,
633 time_slicing_used: false,
634 mpe_fec_used: true,
635 constellation: Constellation::Qam64,
636 hierarchy: Hierarchy::Alpha2Native,
637 code_rate_hp: CodeRate::Rate3_4,
638 code_rate_lp: CodeRate::Rate7_8,
639 guard_interval: GuardInterval::G1_4,
640 transmission_mode: TransmissionMode::Mode8k,
641 other_frequency_flag: true,
642 };
643 let mut buf = vec![0u8; d.serialized_len()];
644 d.serialize_into(&mut buf).unwrap();
645 let parsed = TerrestrialDeliverySystemDescriptor::parse(&buf).unwrap();
646 assert_eq!(parsed, d);
647 }
648}