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
56impl Bandwidth {
57 #[must_use]
59 pub fn name(self) -> &'static str {
60 match self {
61 Self::Mhz8 => "8 MHz",
62 Self::Mhz7 => "7 MHz",
63 Self::Mhz6 => "6 MHz",
64 Self::Mhz5 => "5 MHz",
65 Self::Reserved(_) => "reserved",
66 }
67 }
68}
69dvb_common::impl_spec_display!(Bandwidth, Reserved);
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74#[non_exhaustive]
75pub enum Constellation {
76 Qpsk,
78 Qam16,
80 Qam64,
82 Reserved(u8),
84}
85
86impl Constellation {
87 #[must_use]
89 pub fn name(self) -> &'static str {
90 match self {
91 Self::Qpsk => "QPSK",
92 Self::Qam16 => "16-QAM",
93 Self::Qam64 => "64-QAM",
94 Self::Reserved(_) => "reserved",
95 }
96 }
97}
98dvb_common::impl_spec_display!(Constellation, Reserved);
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103#[non_exhaustive]
104pub enum Hierarchy {
105 NonHierarchicalNative,
107 Alpha1Native,
109 Alpha2Native,
111 Alpha4Native,
113 NonHierarchicalInDepth,
115 Alpha1InDepth,
117 Alpha2InDepth,
119 Alpha4InDepth,
121 Reserved(u8),
123}
124
125impl Hierarchy {
126 #[must_use]
128 pub fn name(self) -> &'static str {
129 match self {
130 Self::NonHierarchicalNative => "non-hierarchical, native interleaver",
131 Self::Alpha1Native => "α=1, native interleaver",
132 Self::Alpha2Native => "α=2, native interleaver",
133 Self::Alpha4Native => "α=4, native interleaver",
134 Self::NonHierarchicalInDepth => "non-hierarchical, in-depth interleaver",
135 Self::Alpha1InDepth => "α=1, in-depth interleaver",
136 Self::Alpha2InDepth => "α=2, in-depth interleaver",
137 Self::Alpha4InDepth => "α=4, in-depth interleaver",
138 Self::Reserved(_) => "reserved",
139 }
140 }
141}
142dvb_common::impl_spec_display!(Hierarchy, Reserved);
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize))]
147#[non_exhaustive]
148pub enum CodeRate {
149 Rate1_2,
151 Rate2_3,
153 Rate3_4,
155 Rate5_6,
157 Rate7_8,
159 Reserved(u8),
161}
162
163impl CodeRate {
164 #[must_use]
166 pub fn name(self) -> &'static str {
167 match self {
168 Self::Rate1_2 => "1/2",
169 Self::Rate2_3 => "2/3",
170 Self::Rate3_4 => "3/4",
171 Self::Rate5_6 => "5/6",
172 Self::Rate7_8 => "7/8",
173 Self::Reserved(_) => "reserved",
174 }
175 }
176}
177dvb_common::impl_spec_display!(CodeRate, Reserved);
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181#[cfg_attr(feature = "serde", derive(serde::Serialize))]
182#[non_exhaustive]
183pub enum GuardInterval {
184 G1_32,
186 G1_16,
188 G1_8,
190 G1_4,
192}
193
194impl GuardInterval {
195 #[must_use]
197 pub fn name(self) -> &'static str {
198 match self {
199 Self::G1_32 => "1/32",
200 Self::G1_16 => "1/16",
201 Self::G1_8 => "1/8",
202 Self::G1_4 => "1/4",
203 }
204 }
205}
206dvb_common::impl_spec_display!(GuardInterval);
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize))]
211#[non_exhaustive]
212pub enum TransmissionMode {
213 Mode2k,
215 Mode8k,
217 Mode4k,
219 Reserved(u8),
221}
222
223impl TransmissionMode {
224 #[must_use]
226 pub fn name(self) -> &'static str {
227 match self {
228 Self::Mode2k => "2k mode",
229 Self::Mode8k => "8k mode",
230 Self::Mode4k => "4k mode",
231 Self::Reserved(_) => "reserved",
232 }
233 }
234}
235dvb_common::impl_spec_display!(TransmissionMode, Reserved);
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize))]
240pub struct TerrestrialDeliverySystemDescriptor {
241 pub centre_frequency_10hz: u32,
243 pub bandwidth: Bandwidth,
245 pub priority: bool,
247 pub time_slicing_used: bool,
249 pub mpe_fec_used: bool,
251 pub constellation: Constellation,
253 pub hierarchy: Hierarchy,
255 pub code_rate_hp: CodeRate,
257 pub code_rate_lp: CodeRate,
259 pub guard_interval: GuardInterval,
261 pub transmission_mode: TransmissionMode,
263 pub other_frequency_flag: bool,
265}
266
267impl TerrestrialDeliverySystemDescriptor {
268 #[must_use]
271 pub fn centre_frequency_hz(&self) -> u64 {
272 u64::from(self.centre_frequency_10hz) * 10
273 }
274
275 pub fn set_centre_frequency_hz(&mut self, hz: u64) -> crate::Result<()> {
282 let units = hz / 10;
283 if units > u64::from(u32::MAX) {
284 return Err(crate::Error::ValueOutOfRange {
285 field: "TerrestrialDeliverySystemDescriptor::centre_frequency",
286 reason: "frequency exceeds the 32-bit (10 Hz) field",
287 });
288 }
289 self.centre_frequency_10hz = units as u32;
290 Ok(())
291 }
292}
293
294fn parse_bandwidth(raw: u8) -> Bandwidth {
295 match raw {
296 0 => Bandwidth::Mhz8,
297 1 => Bandwidth::Mhz7,
298 2 => Bandwidth::Mhz6,
299 3 => Bandwidth::Mhz5,
300 other => Bandwidth::Reserved(other),
301 }
302}
303
304fn parse_constellation(raw: u8) -> Constellation {
305 match raw {
306 0 => Constellation::Qpsk,
307 1 => Constellation::Qam16,
308 2 => Constellation::Qam64,
309 other => Constellation::Reserved(other),
310 }
311}
312
313fn parse_hierarchy(raw: u8) -> Hierarchy {
314 match raw {
315 0 => Hierarchy::NonHierarchicalNative,
316 1 => Hierarchy::Alpha1Native,
317 2 => Hierarchy::Alpha2Native,
318 3 => Hierarchy::Alpha4Native,
319 4 => Hierarchy::NonHierarchicalInDepth,
320 5 => Hierarchy::Alpha1InDepth,
321 6 => Hierarchy::Alpha2InDepth,
322 7 => Hierarchy::Alpha4InDepth,
323 other => Hierarchy::Reserved(other),
324 }
325}
326
327fn parse_code_rate(raw: u8) -> CodeRate {
328 match raw {
329 0 => CodeRate::Rate1_2,
330 1 => CodeRate::Rate2_3,
331 2 => CodeRate::Rate3_4,
332 3 => CodeRate::Rate5_6,
333 4 => CodeRate::Rate7_8,
334 other => CodeRate::Reserved(other),
335 }
336}
337
338fn parse_guard_interval(raw: u8) -> GuardInterval {
339 match raw {
340 0 => GuardInterval::G1_32,
341 1 => GuardInterval::G1_16,
342 2 => GuardInterval::G1_8,
343 3 => GuardInterval::G1_4,
344 _ => GuardInterval::G1_32,
345 }
346}
347
348fn parse_transmission_mode(raw: u8) -> TransmissionMode {
349 match raw {
350 0 => TransmissionMode::Mode2k,
351 1 => TransmissionMode::Mode8k,
352 2 => TransmissionMode::Mode4k,
353 other => TransmissionMode::Reserved(other),
354 }
355}
356
357fn serialize_bandwidth(bw: Bandwidth) -> u8 {
358 match bw {
359 Bandwidth::Mhz8 => 0,
360 Bandwidth::Mhz7 => 1,
361 Bandwidth::Mhz6 => 2,
362 Bandwidth::Mhz5 => 3,
363 Bandwidth::Reserved(v) => v,
364 }
365}
366
367fn serialize_constellation(c: Constellation) -> u8 {
368 match c {
369 Constellation::Qpsk => 0,
370 Constellation::Qam16 => 1,
371 Constellation::Qam64 => 2,
372 Constellation::Reserved(v) => v,
373 }
374}
375
376fn serialize_hierarchy(h: Hierarchy) -> u8 {
377 match h {
378 Hierarchy::NonHierarchicalNative => 0,
379 Hierarchy::Alpha1Native => 1,
380 Hierarchy::Alpha2Native => 2,
381 Hierarchy::Alpha4Native => 3,
382 Hierarchy::NonHierarchicalInDepth => 4,
383 Hierarchy::Alpha1InDepth => 5,
384 Hierarchy::Alpha2InDepth => 6,
385 Hierarchy::Alpha4InDepth => 7,
386 Hierarchy::Reserved(v) => v,
387 }
388}
389
390fn serialize_code_rate(cr: CodeRate) -> u8 {
391 match cr {
392 CodeRate::Rate1_2 => 0,
393 CodeRate::Rate2_3 => 1,
394 CodeRate::Rate3_4 => 2,
395 CodeRate::Rate5_6 => 3,
396 CodeRate::Rate7_8 => 4,
397 CodeRate::Reserved(v) => v,
398 }
399}
400
401fn serialize_guard_interval(gi: GuardInterval) -> u8 {
402 match gi {
403 GuardInterval::G1_32 => 0,
404 GuardInterval::G1_16 => 1,
405 GuardInterval::G1_8 => 2,
406 GuardInterval::G1_4 => 3,
407 }
408}
409
410fn serialize_transmission_mode(tm: TransmissionMode) -> u8 {
411 match tm {
412 TransmissionMode::Mode2k => 0,
413 TransmissionMode::Mode8k => 1,
414 TransmissionMode::Mode4k => 2,
415 TransmissionMode::Reserved(v) => v,
416 }
417}
418
419impl<'a> Parse<'a> for TerrestrialDeliverySystemDescriptor {
420 type Error = crate::error::Error;
421 fn parse(bytes: &'a [u8]) -> Result<Self> {
422 let body = descriptor_body(
423 bytes,
424 TAG,
425 "TerrestrialDeliverySystemDescriptor",
426 "unexpected tag for terrestrial_delivery_system_descriptor",
427 )?;
428 if body.len() != BODY_LEN as usize {
429 return Err(Error::InvalidDescriptor {
430 tag: TAG,
431 reason: "body length must equal 11",
432 });
433 }
434
435 let centre_frequency_10hz = u32::from_be_bytes(body[0..4].try_into().unwrap());
436
437 let byte4 = body[4];
438 let bw_raw = (byte4 & BW_MASK) >> BW_SHIFT;
439 let priority = (byte4 & PRIORITY_MASK) != 0;
440 let time_slicing_used = (byte4 & TIME_SLICING_MASK) == 0;
441 let mpe_fec_used = (byte4 & MPE_FEC_MASK) == 0;
442
443 let byte5 = body[5];
444 let constellation_raw = (byte5 & CONSTELLATION_MASK) >> CONSTELLATION_SHIFT;
445 let hierarchy_raw = (byte5 & HIERARCHY_MASK) >> HIERARCHY_SHIFT;
446 let code_rate_hp_raw = byte5 & CODE_RATE_HP_MASK;
447
448 let byte6 = body[6];
449 let code_rate_lp_raw = (byte6 & CODE_RATE_LP_MASK) >> CODE_RATE_LP_SHIFT;
450 let guard_interval_raw = (byte6 & GUARD_INTERVAL_MASK) >> GUARD_INTERVAL_SHIFT;
451 let transmission_mode_raw = (byte6 & TRANSMISSION_MODE_MASK) >> TRANSMISSION_MODE_SHIFT;
452 let other_frequency_flag = (byte6 & OTHER_FREQ_FLAG_MASK) != 0;
453
454 Ok(Self {
455 centre_frequency_10hz,
456 bandwidth: parse_bandwidth(bw_raw),
457 priority,
458 time_slicing_used,
459 mpe_fec_used,
460 constellation: parse_constellation(constellation_raw),
461 hierarchy: parse_hierarchy(hierarchy_raw),
462 code_rate_hp: parse_code_rate(code_rate_hp_raw),
463 code_rate_lp: parse_code_rate(code_rate_lp_raw),
464 guard_interval: parse_guard_interval(guard_interval_raw),
465 transmission_mode: parse_transmission_mode(transmission_mode_raw),
466 other_frequency_flag,
467 })
468 }
469}
470
471impl Serialize for TerrestrialDeliverySystemDescriptor {
472 type Error = crate::error::Error;
473 fn serialized_len(&self) -> usize {
474 HEADER_LEN + BODY_LEN as usize
475 }
476
477 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
478 let len = self.serialized_len();
479 if buf.len() < len {
480 return Err(Error::OutputBufferTooSmall {
481 need: len,
482 have: buf.len(),
483 });
484 }
485 buf[0] = TAG;
486 buf[1] = BODY_LEN;
487
488 buf[2..6].copy_from_slice(&self.centre_frequency_10hz.to_be_bytes());
489
490 let byte6 = (serialize_bandwidth(self.bandwidth) << BW_SHIFT)
491 | if self.priority { PRIORITY_MASK } else { 0 }
492 | if !self.time_slicing_used {
493 TIME_SLICING_MASK
494 } else {
495 0
496 }
497 | if !self.mpe_fec_used { MPE_FEC_MASK } else { 0 }
498 | RESERVED_FU_MASK;
499 buf[6] = byte6;
500
501 let byte7 = (serialize_constellation(self.constellation) << CONSTELLATION_SHIFT)
502 | (serialize_hierarchy(self.hierarchy) << HIERARCHY_SHIFT)
503 | serialize_code_rate(self.code_rate_hp);
504 buf[7] = byte7;
505
506 let byte8 = (serialize_code_rate(self.code_rate_lp) << CODE_RATE_LP_SHIFT)
507 | (serialize_guard_interval(self.guard_interval) << GUARD_INTERVAL_SHIFT)
508 | (serialize_transmission_mode(self.transmission_mode) << TRANSMISSION_MODE_SHIFT)
509 | if self.other_frequency_flag {
510 OTHER_FREQ_FLAG_MASK
511 } else {
512 0
513 };
514 buf[8] = byte8;
515
516 buf[9..13].copy_from_slice(&TRAILING_RESERVED.to_be_bytes());
517
518 Ok(len)
519 }
520}
521impl<'a> crate::traits::DescriptorDef<'a> for TerrestrialDeliverySystemDescriptor {
522 const TAG: u8 = TAG;
523 const NAME: &'static str = "TERRESTRIAL_DELIVERY_SYSTEM";
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529
530 #[test]
531 fn parse_extracts_centre_frequency_10hz() {
532 let raw = [
533 TAG, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
534 ];
535 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
536 assert_eq!(d.centre_frequency_10hz, 0x04A858F0);
537 }
538
539 #[test]
540 fn parse_extracts_bandwidth_8mhz() {
541 let raw = [
542 TAG, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
543 ];
544 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
545 assert_eq!(d.bandwidth, Bandwidth::Mhz8);
546 }
547
548 #[test]
549 fn parse_extracts_bandwidth_7mhz() {
550 let raw = [
551 TAG,
552 BODY_LEN,
553 0x04,
554 0xA8,
555 0x58,
556 0xF0,
557 (0b001 << BW_SHIFT),
558 0x00,
559 0x00,
560 0x00,
561 0xFF,
562 0xFF,
563 0xFF,
564 0xFF,
565 ];
566 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
567 assert_eq!(d.bandwidth, Bandwidth::Mhz7);
568 }
569
570 #[test]
571 fn parse_extracts_constellation_qam64() {
572 let raw = [
573 TAG,
574 BODY_LEN,
575 0x04,
576 0xA8,
577 0x58,
578 0xF0,
579 0x00,
580 (0b10 << CONSTELLATION_SHIFT),
581 0x00,
582 0xFF,
583 0xFF,
584 0xFF,
585 0xFF,
586 ];
587 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
588 assert_eq!(d.constellation, Constellation::Qam64);
589 }
590
591 #[test]
592 fn parse_extracts_code_rate_hp_and_lp() {
593 let raw = [
594 TAG,
595 BODY_LEN,
596 0x04,
597 0xA8,
598 0x58,
599 0xF0,
600 0x00,
601 0b10 << CONSTELLATION_SHIFT,
602 0b100 << CODE_RATE_LP_SHIFT,
603 0xFF,
604 0xFF,
605 0xFF,
606 0xFF,
607 ];
608 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
609 assert_eq!(d.code_rate_hp, CodeRate::Rate1_2);
610 assert_eq!(d.code_rate_lp, CodeRate::Rate7_8);
611 }
612
613 #[test]
614 fn parse_extracts_guard_interval_1_4() {
615 let raw = [
616 TAG,
617 BODY_LEN,
618 0x04,
619 0xA8,
620 0x58,
621 0xF0,
622 0x00,
623 0x00,
624 0b11 << GUARD_INTERVAL_SHIFT,
625 0xFF,
626 0xFF,
627 0xFF,
628 0xFF,
629 ];
630 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
631 assert_eq!(d.guard_interval, GuardInterval::G1_4);
632 }
633
634 #[test]
635 fn parse_extracts_transmission_mode_8k() {
636 let raw = [
637 TAG,
638 BODY_LEN,
639 0x04,
640 0xA8,
641 0x58,
642 0xF0,
643 0x00,
644 0x00,
645 0b01 << TRANSMISSION_MODE_SHIFT,
646 0xFF,
647 0xFF,
648 0xFF,
649 0xFF,
650 ];
651 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
652 assert_eq!(d.transmission_mode, TransmissionMode::Mode8k);
653 }
654
655 #[test]
656 fn parse_extracts_other_frequency_flag() {
657 let raw = [
658 TAG,
659 BODY_LEN,
660 0x04,
661 0xA8,
662 0x58,
663 0xF0,
664 0x00,
665 0x00,
666 OTHER_FREQ_FLAG_MASK,
667 0xFF,
668 0xFF,
669 0xFF,
670 0xFF,
671 ];
672 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
673 assert!(d.other_frequency_flag);
674 }
675
676 #[test]
677 fn parse_preserves_reserved_bandwidth_in_reserve_variant() {
678 let raw = [
679 TAG,
680 BODY_LEN,
681 0x04,
682 0xA8,
683 0x58,
684 0xF0,
685 (0b111 << BW_SHIFT),
686 0x00,
687 0x00,
688 0x00,
689 0xFF,
690 0xFF,
691 0xFF,
692 0xFF,
693 ];
694 let d = TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap();
695 assert_eq!(d.bandwidth, Bandwidth::Reserved(0b111));
696 }
697
698 #[test]
699 fn parse_rejects_wrong_tag() {
700 let raw = [
701 0x5B, BODY_LEN, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
702 ];
703 assert!(matches!(
704 TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap_err(),
705 Error::InvalidDescriptor { tag: 0x5B, .. }
706 ));
707 }
708
709 #[test]
710 fn parse_rejects_wrong_length() {
711 let raw = [
712 TAG, 12, 0x04, 0xA8, 0x58, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
713 ];
714 assert!(matches!(
715 TerrestrialDeliverySystemDescriptor::parse(&raw).unwrap_err(),
716 Error::InvalidDescriptor { tag: TAG, .. }
717 ));
718 }
719
720 #[test]
721 fn serialize_round_trip_full_set() {
722 let d = TerrestrialDeliverySystemDescriptor {
723 centre_frequency_10hz: 0x04A858F0,
724 bandwidth: Bandwidth::Mhz8,
725 priority: true,
726 time_slicing_used: false,
727 mpe_fec_used: true,
728 constellation: Constellation::Qam64,
729 hierarchy: Hierarchy::Alpha2Native,
730 code_rate_hp: CodeRate::Rate3_4,
731 code_rate_lp: CodeRate::Rate7_8,
732 guard_interval: GuardInterval::G1_4,
733 transmission_mode: TransmissionMode::Mode8k,
734 other_frequency_flag: true,
735 };
736 let mut buf = vec![0u8; d.serialized_len()];
737 d.serialize_into(&mut buf).unwrap();
738 let parsed = TerrestrialDeliverySystemDescriptor::parse(&buf).unwrap();
739 assert_eq!(parsed, d);
740 }
741}