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