1use crate::descriptors::DescriptorLoop;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20#[non_exhaustive]
21pub enum IdentifierType {
22 None,
24 EventId,
26 TvaIdEit,
28 TvaIdPes,
30}
31
32impl IdentifierType {
33 #[must_use]
34 pub fn from_u8(v: u8) -> Self {
36 match v & 0x03 {
37 0x00 => Self::None,
38 0x01 => Self::EventId,
39 0x02 => Self::TvaIdEit,
40 0x03 => Self::TvaIdPes,
41 _ => unreachable!(),
43 }
44 }
45
46 #[must_use]
47 pub fn to_u8(self) -> u8 {
49 match self {
50 Self::None => 0x00,
51 Self::EventId => 0x01,
52 Self::TvaIdEit => 0x02,
53 Self::TvaIdPes => 0x03,
54 }
55 }
56
57 #[must_use]
58 pub fn name(self) -> &'static str {
60 match self {
61 Self::None => "none",
62 Self::EventId => "event_id",
63 Self::TvaIdEit => "TVA_id (EIT)",
64 Self::TvaIdPes => "TVA_id (PES)",
65 }
66 }
67}
68dvb_common::impl_spec_display!(IdentifierType);
69use crate::error::{Error, Result};
70use crate::text::{DvbText, LangCode};
71use alloc::vec::Vec;
72use dvb_common::{Parse, Serialize};
73
74pub const TABLE_ID: u8 = 0x76;
76
77pub const PID: u16 = 0x0000;
79
80const MIN_HEADER_LEN: usize = 3;
81const EXTENSION_HEADER_LEN: usize = 5;
82const POST_EXT_FIXED_LEN: usize = 3;
83const LINK_ENTRY_HEADER_LEN: usize = 2;
84const DESC_LOOP_LEN_FIELD: usize = 2;
85const CRC_LEN: usize = 4;
86const MIN_SECTION_LEN: usize =
87 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXT_FIXED_LEN + DESC_LOOP_LEN_FIELD + CRC_LEN;
88
89const LINK_TYPE_MASK: u8 = 0xF0;
90const LINK_TYPE_SHIFT: u8 = 4;
91const HOW_RELATED_HI_MASK: u8 = 0x03;
92const TERM_ID_HI_MASK: u8 = 0x0F;
93const TERM_ID_HI_SHIFT: u8 = 8;
94const GROUP_ID_MASK: u8 = 0xF0;
95const GROUP_ID_SHIFT: u8 = 4;
96const PRECEDENCE_MASK: u8 = 0x0F;
97
98const LOCATOR_ID_TYPE_MASK: u8 = 0xC0;
99const LOCATOR_ID_TYPE_SHIFT: u8 = 6;
100const LOCATOR_RELIABILITY_MASK: u8 = 0x20;
101const LOCATOR_INLINE_MASK: u8 = 0x10;
102const LOCATOR_START_DATE_HI_MASK: u8 = 0x07;
103const LOCATOR_START_DATE_HI_SHIFT: usize = 6;
104const LOCATOR_RESERVED_BITS: u8 = 0x08;
105
106const ITEM_RFU_MASK: u8 = 0xC0;
107const ITEM_COUNT_MASK: u8 = 0x3F;
108
109const LINK_INFO_HEADER_RFU: u8 = 0x0C;
110
111const ICON_FLAG_MASK: u8 = 0x80;
112const ICON_ID_MASK: u8 = 0x70;
113const ICON_ID_SHIFT: u8 = 4;
114const ICON_DESC_LEN_HI_MASK: u8 = 0x0F;
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize))]
119#[non_exhaustive]
120pub enum LinkType {
121 UriString,
123 BinaryLocator,
125 Both,
127 Descriptor,
129 DvbReserved(u8),
131}
132
133impl LinkType {
134 #[must_use]
135 pub fn from_u8(v: u8) -> Self {
137 match v & 0x0F {
138 0x0 => Self::UriString,
139 0x1 => Self::BinaryLocator,
140 0x2 => Self::Both,
141 0x3 => Self::Descriptor,
142 v => Self::DvbReserved(v),
143 }
144 }
145
146 #[must_use]
147 pub fn to_u8(self) -> u8 {
149 match self {
150 Self::UriString => 0x0,
151 Self::BinaryLocator => 0x1,
152 Self::Both => 0x2,
153 Self::Descriptor => 0x3,
154 Self::DvbReserved(v) => v,
155 }
156 }
157
158 #[must_use]
159 pub fn name(self) -> &'static str {
161 match self {
162 Self::UriString => "URI String",
163 Self::BinaryLocator => "Binary Locator",
164 Self::Both => "Both",
165 Self::Descriptor => "Descriptor",
166 Self::DvbReserved(_) => "DVB Reserved",
167 }
168 }
169}
170dvb_common::impl_spec_display!(LinkType, DvbReserved);
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174#[cfg_attr(feature = "serde", derive(serde::Serialize))]
175#[non_exhaustive]
176pub enum HowRelated {
177 Cs2004,
179 Cs2005,
181 Cs2007,
183 DvbReserved(u8),
185 UserPrivate(u8),
187}
188
189impl HowRelated {
190 #[must_use]
191 pub fn from_u8(v: u8) -> Self {
193 match v {
194 0x00 => Self::Cs2004,
195 0x01 => Self::Cs2005,
196 0x02 => Self::Cs2007,
197 v @ 0x03..0x30 => Self::DvbReserved(v),
198 _ => Self::UserPrivate(v),
199 }
200 }
201
202 #[must_use]
203 pub fn to_u8(self) -> u8 {
205 match self {
206 Self::Cs2004 => 0x00,
207 Self::Cs2005 => 0x01,
208 Self::Cs2007 => 0x02,
209 Self::DvbReserved(v) | Self::UserPrivate(v) => v,
210 }
211 }
212
213 #[must_use]
214 pub fn name(self) -> &'static str {
216 match self {
217 Self::Cs2004 => "HowRelatedCS:2004",
218 Self::Cs2005 => "HowRelatedCS:2005",
219 Self::Cs2007 => "HowRelatedCS:2007",
220 Self::DvbReserved(_) => "DVB Reserved",
221 Self::UserPrivate(_) => "User Private",
222 }
223 }
224}
225dvb_common::impl_spec_display!(HowRelated, DvbReserved, UserPrivate);
226
227#[derive(Debug, Clone, PartialEq, Eq)]
229#[cfg_attr(feature = "serde", derive(serde::Serialize))]
230pub struct LinkItem<'a> {
231 pub language_code: LangCode,
233 pub promotional_text: DvbText<'a>,
235}
236
237fn link_item_serialized_len(item: &LinkItem) -> usize {
238 3 + 1 + item.promotional_text.len()
239}
240
241#[derive(Debug, Clone, PartialEq, Eq)]
243#[cfg_attr(feature = "serde", derive(serde::Serialize))]
244#[non_exhaustive]
245pub enum DvbLocatorService {
246 Triplet {
248 dvb_service_triplet_id: u16,
250 },
251 Full {
253 transport_stream_id: u16,
255 original_network_id: u16,
257 service_id: u16,
259 },
260}
261
262#[derive(Debug, Clone, PartialEq, Eq)]
264#[cfg_attr(feature = "serde", derive(serde::Serialize))]
265#[non_exhaustive]
266pub enum DvbLocatorIdentifier {
267 None,
269 EventId {
271 event_id: u16,
273 },
274 TvaIdEit {
276 tva_id: u16,
278 },
279 TvaIdPes {
281 tva_id: u16,
283 component: u8,
285 },
286}
287
288#[derive(Debug, Clone, PartialEq, Eq)]
290#[cfg_attr(feature = "serde", derive(serde::Serialize))]
291pub struct DvbLocatorWindows {
292 pub early_start_window: u8,
294 pub late_end_window: u8,
296}
297
298#[derive(Debug, Clone, PartialEq, Eq)]
300#[cfg_attr(feature = "serde", derive(serde::Serialize))]
301pub struct DvbBinaryLocator {
302 pub identifier_type: IdentifierType,
304 pub scheduled_time_reliability: bool,
306 pub inline_service: bool,
308 pub start_date: u16,
310 pub service: DvbLocatorService,
312 pub start_time: u16,
314 pub duration: u16,
316 pub identifier: DvbLocatorIdentifier,
318 pub windows: Option<DvbLocatorWindows>,
320}
321
322fn locator_serialized_len(loc: &DvbBinaryLocator) -> usize {
323 let mut len = 2;
324 len += match &loc.service {
325 DvbLocatorService::Triplet { .. } => 1,
326 DvbLocatorService::Full { .. } => 6,
327 };
328 len += 4;
329 len += match &loc.identifier {
330 DvbLocatorIdentifier::None => 0,
331 DvbLocatorIdentifier::EventId { .. } => 2,
332 DvbLocatorIdentifier::TvaIdEit { .. } => 2,
333 DvbLocatorIdentifier::TvaIdPes { .. } => 3,
334 };
335 if loc.windows.is_some() {
336 len += 1;
337 }
338 len
339}
340
341#[derive(Debug, Clone, PartialEq, Eq)]
343#[cfg_attr(feature = "serde", derive(serde::Serialize))]
344pub struct LinkInfo<'a> {
345 pub link_type: LinkType,
347 pub how_related: HowRelated,
349 pub term_id: u16,
351 pub group_id: u8,
353 pub precedence: u8,
355 pub media_uri: Option<&'a [u8]>,
357 pub dvb_binary_locator: Option<DvbBinaryLocator>,
359 pub items: Vec<LinkItem<'a>>,
361 pub default_icon_flag: bool,
363 pub icon_id: u8,
365 pub descriptors: DescriptorLoop<'a>,
367}
368
369fn link_info_serialized_len(li: &LinkInfo) -> usize {
370 let mut len = 4;
371 len += li.media_uri.map_or(0, |u| 1 + u.len());
372 len += li
373 .dvb_binary_locator
374 .as_ref()
375 .map_or(0, locator_serialized_len);
376 len += 1;
377 len += li.items.iter().map(link_item_serialized_len).sum::<usize>();
378 len += 2;
379 len += li.descriptors.len();
380 len
381}
382
383#[derive(Debug, Clone, PartialEq, Eq)]
387#[cfg_attr(feature = "serde", derive(serde::Serialize))]
388#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
389pub struct RctSection<'a> {
390 pub table_id_extension_flag: bool,
392 pub service_id: u16,
394 pub version_number: u8,
396 pub current_next_indicator: bool,
398 pub section_number: u8,
400 pub last_section_number: u8,
402 pub year_offset: u16,
404 pub links: Vec<LinkInfo<'a>>,
406 pub descriptors: DescriptorLoop<'a>,
408}
409
410fn parse_locator(data: &[u8]) -> Result<(DvbBinaryLocator, usize)> {
411 if data.len() < 2 {
412 return Err(Error::BufferTooShort {
413 need: 2,
414 have: data.len(),
415 what: "RctSection dvb_binary_locator header",
416 });
417 }
418 let b0 = data[0];
419 let b1 = data[1];
420 let identifier_type =
421 IdentifierType::from_u8((b0 & LOCATOR_ID_TYPE_MASK) >> LOCATOR_ID_TYPE_SHIFT);
422 let scheduled_time_reliability = (b0 & LOCATOR_RELIABILITY_MASK) != 0;
423 let inline_service = (b0 & LOCATOR_INLINE_MASK) != 0;
424 let start_date = ((b0 & LOCATOR_START_DATE_HI_MASK) as u16) << LOCATOR_START_DATE_HI_SHIFT
425 | ((b1 >> 2) as u16 & 0x3F);
426
427 let mut pos = 2;
428 let service = if inline_service {
429 if pos + 6 > data.len() {
430 return Err(Error::BufferTooShort {
431 need: pos + 6,
432 have: data.len(),
433 what: "RctSection dvb_binary_locator full triplet",
434 });
435 }
436 let triplet = data[pos..pos + 6].first_chunk::<6>().unwrap();
437 let tsid = u16::from_be_bytes(*triplet[0..].first_chunk::<2>().unwrap());
438 let onid = u16::from_be_bytes(*triplet[2..].first_chunk::<2>().unwrap());
439 let sid = u16::from_be_bytes(*triplet[4..].first_chunk::<2>().unwrap());
440 pos += 6;
441 DvbLocatorService::Full {
442 transport_stream_id: tsid,
443 original_network_id: onid,
444 service_id: sid,
445 }
446 } else {
447 if data.len() < 3 {
448 return Err(Error::BufferTooShort {
449 need: 3,
450 have: data.len(),
451 what: "RctSection dvb_binary_locator triplet",
452 });
453 }
454 let triplet_id = ((b1 as u16 & 0x03) << 8) | data[2] as u16;
455 pos = 3;
456 DvbLocatorService::Triplet {
457 dvb_service_triplet_id: triplet_id,
458 }
459 };
460
461 if pos + 4 > data.len() {
462 return Err(Error::BufferTooShort {
463 need: pos + 4,
464 have: data.len(),
465 what: "RctSection dvb_binary_locator start_time/duration",
466 });
467 }
468 let (b2, rest) = data[pos..]
469 .split_first_chunk::<2>()
470 .ok_or(Error::BufferTooShort {
471 need: pos + 4,
472 have: data.len(),
473 what: "RctSection dvb_binary_locator start_time/duration",
474 })?;
475 let start_time = u16::from_be_bytes(*b2);
476 let (b2, _) = rest.split_first_chunk::<2>().ok_or(Error::BufferTooShort {
477 need: pos + 4,
478 have: data.len(),
479 what: "RctSection dvb_binary_locator start_time/duration",
480 })?;
481 let duration = u16::from_be_bytes(*b2);
482 pos += 4;
483
484 let identifier = match identifier_type {
485 IdentifierType::None => DvbLocatorIdentifier::None,
486 IdentifierType::EventId => {
487 if pos + 2 > data.len() {
488 return Err(Error::BufferTooShort {
489 need: pos + 2,
490 have: data.len(),
491 what: "RctSection dvb_binary_locator event_id",
492 });
493 }
494 let (b2, _) = data[pos..]
495 .split_first_chunk::<2>()
496 .ok_or(Error::BufferTooShort {
497 need: pos + 2,
498 have: data.len(),
499 what: "RctSection dvb_binary_locator event_id",
500 })?;
501 let event_id = u16::from_be_bytes(*b2);
502 pos += 2;
503 DvbLocatorIdentifier::EventId { event_id }
504 }
505 IdentifierType::TvaIdEit => {
506 if pos + 2 > data.len() {
507 return Err(Error::BufferTooShort {
508 need: pos + 2,
509 have: data.len(),
510 what: "RctSection dvb_binary_locator TVA_id (EIT)",
511 });
512 }
513 let (b2, _) = data[pos..]
514 .split_first_chunk::<2>()
515 .ok_or(Error::BufferTooShort {
516 need: pos + 2,
517 have: data.len(),
518 what: "RctSection dvb_binary_locator TVA_id (EIT)",
519 })?;
520 let tva_id = u16::from_be_bytes(*b2);
521 pos += 2;
522 DvbLocatorIdentifier::TvaIdEit { tva_id }
523 }
524 IdentifierType::TvaIdPes => {
525 if pos + 3 > data.len() {
526 return Err(Error::BufferTooShort {
527 need: pos + 3,
528 have: data.len(),
529 what: "RctSection dvb_binary_locator TVA_id (PES)",
530 });
531 }
532 let (b2, _) = data[pos..]
533 .split_first_chunk::<2>()
534 .ok_or(Error::BufferTooShort {
535 need: pos + 3,
536 have: data.len(),
537 what: "RctSection dvb_binary_locator TVA_id (PES)",
538 })?;
539 let tva_id = u16::from_be_bytes(*b2);
540 let component = data[pos + 2];
541 pos += 3;
542 DvbLocatorIdentifier::TvaIdPes { tva_id, component }
543 }
544 };
545
546 let windows = if identifier_type == IdentifierType::None && scheduled_time_reliability {
547 if pos >= data.len() {
548 return Err(Error::BufferTooShort {
549 need: pos + 1,
550 have: data.len(),
551 what: "RctSection dvb_binary_locator windows",
552 });
553 }
554 let wb = data[pos];
555 pos += 1;
556 Some(DvbLocatorWindows {
557 early_start_window: (wb >> 5) & 0x07,
558 late_end_window: wb & 0x1F,
559 })
560 } else {
561 None
562 };
563
564 Ok((
565 DvbBinaryLocator {
566 identifier_type,
567 scheduled_time_reliability,
568 inline_service,
569 start_date,
570 service,
571 start_time,
572 duration,
573 identifier,
574 windows,
575 },
576 pos,
577 ))
578}
579
580fn serialize_locator(loc: &DvbBinaryLocator, buf: &mut [u8]) -> usize {
581 let b0 = (loc.identifier_type.to_u8() << LOCATOR_ID_TYPE_SHIFT)
582 | (u8::from(loc.scheduled_time_reliability) << 5)
583 | (u8::from(loc.inline_service) << 4)
584 | LOCATOR_RESERVED_BITS
585 | ((loc.start_date >> LOCATOR_START_DATE_HI_SHIFT) as u8 & LOCATOR_START_DATE_HI_MASK);
586 buf[0] = b0;
587
588 let mut pos: usize;
589 match &loc.service {
590 DvbLocatorService::Triplet {
591 dvb_service_triplet_id,
592 } => {
593 let sd_lo = (loc.start_date & 0x3F) as u8;
594 buf[1] = (sd_lo << 2) | ((dvb_service_triplet_id >> 8) as u8 & 0x03);
595 buf[2] = (dvb_service_triplet_id & 0xFF) as u8;
596 pos = 3;
597 }
598 DvbLocatorService::Full {
599 transport_stream_id,
600 original_network_id,
601 service_id,
602 } => {
603 let sd_lo = (loc.start_date & 0x3F) as u8;
604 buf[1] = (sd_lo << 2) | 0x03;
605 buf[2..4].copy_from_slice(&transport_stream_id.to_be_bytes());
606 buf[4..6].copy_from_slice(&original_network_id.to_be_bytes());
607 buf[6..8].copy_from_slice(&service_id.to_be_bytes());
608 pos = 8;
609 }
610 }
611 buf[pos..pos + 2].copy_from_slice(&loc.start_time.to_be_bytes());
612 buf[pos + 2..pos + 4].copy_from_slice(&loc.duration.to_be_bytes());
613 pos += 4;
614
615 match &loc.identifier {
616 DvbLocatorIdentifier::None => {}
617 DvbLocatorIdentifier::EventId { event_id } => {
618 buf[pos..pos + 2].copy_from_slice(&event_id.to_be_bytes());
619 pos += 2;
620 }
621 DvbLocatorIdentifier::TvaIdEit { tva_id } => {
622 buf[pos..pos + 2].copy_from_slice(&tva_id.to_be_bytes());
623 pos += 2;
624 }
625 DvbLocatorIdentifier::TvaIdPes { tva_id, component } => {
626 buf[pos..pos + 2].copy_from_slice(&tva_id.to_be_bytes());
627 buf[pos + 2] = *component;
628 pos += 3;
629 }
630 }
631 if let Some(w) = &loc.windows {
632 buf[pos] = ((w.early_start_window & 0x07) << 5) | (w.late_end_window & 0x1F);
633 pos += 1;
634 }
635 pos
636}
637
638fn parse_link_info(data: &[u8], link_info_length: usize) -> Result<LinkInfo<'_>> {
639 if data.len() < 4 {
640 return Err(Error::BufferTooShort {
641 need: 4,
642 have: data.len(),
643 what: "RctSection link_info header",
644 });
645 }
646 let link_type = LinkType::from_u8((data[0] & LINK_TYPE_MASK) >> LINK_TYPE_SHIFT);
647 let how_related_raw = (data[0] & HOW_RELATED_HI_MASK) << 4 | (data[1] >> 4) & 0x0F;
648 let how_related = HowRelated::from_u8(how_related_raw);
649 let term_id = ((data[1] as u16 & TERM_ID_HI_MASK as u16) << TERM_ID_HI_SHIFT) | data[2] as u16;
650 let group_id = (data[3] & GROUP_ID_MASK) >> GROUP_ID_SHIFT;
651 let precedence = data[3] & PRECEDENCE_MASK;
652
653 let mut pos = 4;
654 let end = link_info_length;
655
656 let media_uri = if link_type == LinkType::UriString || link_type == LinkType::Both {
657 if pos >= end {
658 return Err(Error::BufferTooShort {
659 need: pos + 1,
660 have: end,
661 what: "RctSection media_uri_length",
662 });
663 }
664 let uri_len = data[pos] as usize;
665 pos += 1;
666 if pos + uri_len > end {
667 return Err(Error::BufferTooShort {
668 need: pos + uri_len,
669 have: end,
670 what: "RctSection media_uri",
671 });
672 }
673 let uri = &data[pos..pos + uri_len];
674 pos += uri_len;
675 Some(uri)
676 } else {
677 None
678 };
679
680 let dvb_binary_locator = if link_type == LinkType::BinaryLocator || link_type == LinkType::Both
681 {
682 if pos >= end {
683 return Err(Error::BufferTooShort {
684 need: pos + 1,
685 have: end,
686 what: "RctSection dvb_binary_locator",
687 });
688 }
689 let (loc, consumed) = parse_locator(&data[pos..end])?;
690 pos += consumed;
691 Some(loc)
692 } else {
693 None
694 };
695
696 if pos >= end {
697 return Err(Error::BufferTooShort {
698 need: pos + 1,
699 have: end,
700 what: "RctSection number_items",
701 });
702 }
703 let ni_byte = data[pos];
704 let number_items = (ni_byte & ITEM_COUNT_MASK) as usize;
705 pos += 1;
706
707 let mut items = Vec::with_capacity(number_items);
708 for _ in 0..number_items {
709 if pos + 4 > end {
710 return Err(Error::BufferTooShort {
711 need: pos + 4,
712 have: end,
713 what: "RctSection link_item",
714 });
715 }
716 let language_code = LangCode([data[pos], data[pos + 1], data[pos + 2]]);
717 let text_len = data[pos + 3] as usize;
718 pos += 4;
719 if pos + text_len > end {
720 return Err(Error::BufferTooShort {
721 need: pos + text_len,
722 have: end,
723 what: "RctSection promotional_text",
724 });
725 }
726 let promotional_text = DvbText::new(&data[pos..pos + text_len]);
727 pos += text_len;
728 items.push(LinkItem {
729 language_code,
730 promotional_text,
731 });
732 }
733
734 if pos + 2 > end {
735 return Err(Error::BufferTooShort {
736 need: pos + 2,
737 have: end,
738 what: "RctSection icon/desc",
739 });
740 }
741 let default_icon_flag = (data[pos] & ICON_FLAG_MASK) != 0;
742 let icon_id = (data[pos] & ICON_ID_MASK) >> ICON_ID_SHIFT;
743 let desc_len = (((data[pos] & ICON_DESC_LEN_HI_MASK) as usize) << 8) | data[pos + 1] as usize;
744 pos += 2;
745 let desc_start = pos;
746 let desc_end = desc_start + desc_len;
747 if desc_end > end {
748 return Err(Error::SectionLengthOverflow {
749 declared: desc_len,
750 available: end.saturating_sub(desc_start),
751 });
752 }
753 let descriptors = DescriptorLoop::new(&data[desc_start..desc_end]);
754
755 Ok(LinkInfo {
756 link_type,
757 how_related,
758 term_id,
759 group_id,
760 precedence,
761 media_uri,
762 dvb_binary_locator,
763 items,
764 default_icon_flag,
765 icon_id,
766 descriptors,
767 })
768}
769
770fn serialize_link_info(li: &LinkInfo, buf: &mut [u8]) -> usize {
771 let lt = li.link_type.to_u8();
772 let hr = li.how_related.to_u8();
773 buf[0] =
774 ((lt & 0x0F) << LINK_TYPE_SHIFT) | LINK_INFO_HEADER_RFU | ((hr >> 4) & HOW_RELATED_HI_MASK);
775 buf[1] = ((hr & 0x0F) << 4) | ((li.term_id >> TERM_ID_HI_SHIFT) as u8 & TERM_ID_HI_MASK);
776 buf[2] = li.term_id as u8;
777 buf[3] = ((li.group_id & 0x0F) << GROUP_ID_SHIFT) | (li.precedence & PRECEDENCE_MASK);
778
779 let mut pos = 4;
780 if let Some(uri) = li.media_uri {
781 buf[pos] = uri.len() as u8;
782 pos += 1;
783 buf[pos..pos + uri.len()].copy_from_slice(uri);
784 pos += uri.len();
785 }
786 if let Some(ref loc) = li.dvb_binary_locator {
787 let n = serialize_locator(loc, &mut buf[pos..]);
788 pos += n;
789 }
790 buf[pos] = ITEM_RFU_MASK | (li.items.len() as u8 & ITEM_COUNT_MASK);
791 pos += 1;
792 for item in &li.items {
793 buf[pos..pos + 3].copy_from_slice(&item.language_code.0);
794 buf[pos + 3] = item.promotional_text.len() as u8;
795 pos += 4;
796 buf[pos..pos + item.promotional_text.len()].copy_from_slice(item.promotional_text.raw());
797 pos += item.promotional_text.len();
798 }
799 let dll = li.descriptors.len() as u16;
800 buf[pos] = u8::from(li.default_icon_flag) << 7
801 | ((li.icon_id & 0x07) << ICON_ID_SHIFT)
802 | ((dll >> 8) as u8 & ICON_DESC_LEN_HI_MASK);
803 buf[pos + 1] = (dll & 0xFF) as u8;
804 pos += 2;
805 buf[pos..pos + li.descriptors.len()].copy_from_slice(li.descriptors.raw());
806 pos += li.descriptors.len();
807 pos
808}
809
810impl<'a> Parse<'a> for RctSection<'a> {
811 type Error = crate::error::Error;
812
813 fn parse(bytes: &'a [u8]) -> Result<Self> {
814 if bytes.len() < MIN_SECTION_LEN {
815 return Err(Error::BufferTooShort {
816 need: MIN_SECTION_LEN,
817 have: bytes.len(),
818 what: "RctSection",
819 });
820 }
821 if bytes[0] != TABLE_ID {
822 return Err(Error::UnexpectedTableId {
823 table_id: bytes[0],
824 what: "RctSection",
825 expected: &[TABLE_ID],
826 });
827 }
828
829 let table_id_extension_flag = (bytes[1] & 0x40) != 0;
830 let section_length = (((bytes[1] & 0x0F) as u16) << 8) | bytes[2] as u16;
831 let total = super::check_section_length(
832 bytes.len(),
833 MIN_HEADER_LEN,
834 section_length as usize,
835 MIN_SECTION_LEN,
836 )?;
837
838 let service_id = u16::from_be_bytes(*bytes[3..].first_chunk::<2>().unwrap());
839 let version_number = (bytes[5] >> 1) & 0x1F;
840 let current_next_indicator = (bytes[5] & 0x01) != 0;
841 let section_number = bytes[6];
842 let last_section_number = bytes[7];
843 let year_offset = u16::from_be_bytes(*bytes[8..].first_chunk::<2>().unwrap());
844 let link_count = bytes[10];
845
846 let payload_end = total - CRC_LEN;
847 let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXT_FIXED_LEN;
848
849 let mut links = Vec::with_capacity(link_count as usize);
850 for _ in 0..link_count {
851 if pos + LINK_ENTRY_HEADER_LEN > payload_end {
852 return Err(Error::BufferTooShort {
853 need: pos + LINK_ENTRY_HEADER_LEN,
854 have: payload_end,
855 what: "RctSection link_entry header",
856 });
857 }
858 let link_info_length = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
859 let link_data_start = pos + LINK_ENTRY_HEADER_LEN;
860 let link_data_end = link_data_start + link_info_length;
861 if link_data_end > payload_end {
862 return Err(Error::SectionLengthOverflow {
863 declared: link_info_length,
864 available: payload_end.saturating_sub(link_data_start),
865 });
866 }
867 let link_info =
868 parse_link_info(&bytes[link_data_start..link_data_end], link_info_length)?;
869 links.push(link_info);
870 pos = link_data_end;
871 }
872
873 if pos + DESC_LOOP_LEN_FIELD > payload_end {
874 return Err(Error::BufferTooShort {
875 need: pos + DESC_LOOP_LEN_FIELD,
876 have: payload_end,
877 what: "RctSection descriptor_loop_length field",
878 });
879 }
880 let descriptor_loop_length =
881 (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
882 let desc_start = pos + DESC_LOOP_LEN_FIELD;
883 let desc_end = desc_start + descriptor_loop_length;
884 if desc_end > payload_end {
885 return Err(Error::SectionLengthOverflow {
886 declared: descriptor_loop_length,
887 available: payload_end.saturating_sub(desc_start),
888 });
889 }
890 let descriptors = DescriptorLoop::new(&bytes[desc_start..desc_end]);
891
892 Ok(RctSection {
893 table_id_extension_flag,
894 service_id,
895 version_number,
896 current_next_indicator,
897 section_number,
898 last_section_number,
899 year_offset,
900 links,
901 descriptors,
902 })
903 }
904}
905
906impl Serialize for RctSection<'_> {
907 type Error = crate::error::Error;
908
909 fn serialized_len(&self) -> usize {
910 MIN_HEADER_LEN
911 + EXTENSION_HEADER_LEN
912 + POST_EXT_FIXED_LEN
913 + self
914 .links
915 .iter()
916 .map(|li| LINK_ENTRY_HEADER_LEN + link_info_serialized_len(li))
917 .sum::<usize>()
918 + DESC_LOOP_LEN_FIELD
919 + self.descriptors.len()
920 + CRC_LEN
921 }
922
923 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
924 let len = self.serialized_len();
925 if buf.len() < len {
926 return Err(Error::OutputBufferTooSmall {
927 need: len,
928 have: buf.len(),
929 });
930 }
931
932 let section_length_usize = len - MIN_HEADER_LEN;
933 if section_length_usize > 0x0FFF {
934 return Err(Error::SectionLengthOverflow {
935 declared: section_length_usize,
936 available: 0x0FFF,
937 });
938 }
939 let section_length = section_length_usize as u16;
940 buf[0] = TABLE_ID;
941 let tief_bit: u8 = if self.table_id_extension_flag {
942 0x40
943 } else {
944 0x00
945 };
946 buf[1] = super::SECTION_B1_SSI
947 | tief_bit
948 | super::SECTION_B1_RESERVED_HI
949 | ((section_length >> 8) as u8 & 0x0F);
950 buf[2] = (section_length & 0xFF) as u8;
951
952 buf[3..5].copy_from_slice(&self.service_id.to_be_bytes());
953 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
954 buf[6] = self.section_number;
955 buf[7] = self.last_section_number;
956 buf[8..10].copy_from_slice(&self.year_offset.to_be_bytes());
957 if self.links.len() > u8::MAX as usize {
958 return Err(Error::SectionLengthOverflow {
959 declared: self.links.len(),
960 available: u8::MAX as usize,
961 });
962 }
963 buf[10] = self.links.len() as u8;
964
965 let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXT_FIXED_LEN;
966 for li in &self.links {
967 let li_body_len = link_info_serialized_len(li) as u16;
968 buf[pos] = 0xF0 | ((li_body_len >> 8) as u8 & 0x0F);
969 buf[pos + 1] = (li_body_len & 0xFF) as u8;
970 pos += LINK_ENTRY_HEADER_LEN;
971 pos += serialize_link_info(li, &mut buf[pos..]);
972 }
973
974 let dll = self.descriptors.len() as u16;
975 buf[pos] = 0xF0 | ((dll >> 8) as u8 & 0x0F);
976 buf[pos + 1] = (dll & 0xFF) as u8;
977 pos += DESC_LOOP_LEN_FIELD;
978 buf[pos..pos + self.descriptors.len()].copy_from_slice(self.descriptors.raw());
979 pos += self.descriptors.len();
980
981 let crc = dvb_common::crc32_mpeg2::compute(&buf[..pos]);
982 buf[pos..pos + CRC_LEN].copy_from_slice(&crc.to_be_bytes());
983 Ok(len)
984 }
985}
986impl<'a> crate::traits::TableDef<'a> for RctSection<'a> {
987 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
988 const NAME: &'static str = "RELATED_CONTENT";
989}
990
991#[cfg(test)]
992mod tests {
993 use super::*;
994
995 #[test]
996 fn parse_no_links_no_descriptors() {
997 let rct = RctSection {
998 table_id_extension_flag: false,
999 service_id: 0x0064,
1000 version_number: 3,
1001 current_next_indicator: true,
1002 section_number: 0,
1003 last_section_number: 0,
1004 year_offset: 0x07D3,
1005 links: Vec::new(),
1006 descriptors: DescriptorLoop::new(&[]),
1007 };
1008 let mut buf = vec![0u8; rct.serialized_len()];
1009 rct.serialize_into(&mut buf).unwrap();
1010 let p = RctSection::parse(&buf).unwrap();
1011 assert!(!p.table_id_extension_flag);
1012 assert_eq!(p.service_id, 0x0064);
1013 assert_eq!(p.year_offset, 0x07D3);
1014 assert!(p.links.is_empty());
1015 }
1016
1017 #[test]
1018 fn parse_one_link_uri_only() {
1019 let li = LinkInfo {
1020 link_type: LinkType::UriString,
1021 how_related: HowRelated::Cs2005,
1022 term_id: 0x123,
1023 group_id: 0x5,
1024 precedence: 0x9,
1025 media_uri: Some(b"http://example.com"),
1026 dvb_binary_locator: None,
1027 items: Vec::new(),
1028 default_icon_flag: false,
1029 icon_id: 0,
1030 descriptors: DescriptorLoop::new(&[]),
1031 };
1032 let rct = RctSection {
1033 table_id_extension_flag: false,
1034 service_id: 0x1234,
1035 version_number: 7,
1036 current_next_indicator: true,
1037 section_number: 1,
1038 last_section_number: 3,
1039 year_offset: 2003,
1040 links: vec![li],
1041 descriptors: DescriptorLoop::new(&[]),
1042 };
1043 let mut buf = vec![0u8; rct.serialized_len()];
1044 rct.serialize_into(&mut buf).unwrap();
1045 let p = RctSection::parse(&buf).unwrap();
1046 assert_eq!(p.links.len(), 1);
1047 assert_eq!(p.links[0].link_type, LinkType::UriString);
1048 assert_eq!(p.links[0].how_related, HowRelated::Cs2005);
1049 assert_eq!(p.links[0].term_id, 0x123);
1050 assert_eq!(p.links[0].group_id, 0x5);
1051 assert_eq!(p.links[0].precedence, 0x9);
1052 assert_eq!(p.links[0].media_uri.unwrap(), b"http://example.com");
1053 }
1054
1055 #[test]
1056 fn parse_one_link_with_locator_and_items() {
1057 let loc = DvbBinaryLocator {
1058 identifier_type: IdentifierType::EventId,
1059 scheduled_time_reliability: false,
1060 inline_service: true,
1061 start_date: 0x0FF,
1062 service: DvbLocatorService::Full {
1063 transport_stream_id: 0x1000,
1064 original_network_id: 0x2000,
1065 service_id: 0x3000,
1066 },
1067 start_time: 0x8000,
1068 duration: 0x4000,
1069 identifier: DvbLocatorIdentifier::EventId { event_id: 0xBEEF },
1070 windows: None,
1071 };
1072 let li = LinkInfo {
1073 link_type: LinkType::BinaryLocator,
1074 how_related: HowRelated::Cs2007,
1075 term_id: 0x456,
1076 group_id: 0x1,
1077 precedence: 0x2,
1078 media_uri: None,
1079 dvb_binary_locator: Some(loc),
1080 items: vec![LinkItem {
1081 language_code: LangCode(*b"eng"),
1082 promotional_text: DvbText::new(b"Promo"),
1083 }],
1084 default_icon_flag: true,
1085 icon_id: 3,
1086 descriptors: DescriptorLoop::new(&[0x80, 0x00]),
1087 };
1088 let rct = RctSection {
1089 table_id_extension_flag: true,
1090 service_id: 0xABCD,
1091 version_number: 15,
1092 current_next_indicator: false,
1093 section_number: 2,
1094 last_section_number: 5,
1095 year_offset: 2024,
1096 links: vec![li],
1097 descriptors: DescriptorLoop::new(&[]),
1098 };
1099 let mut buf = vec![0u8; rct.serialized_len()];
1100 rct.serialize_into(&mut buf).unwrap();
1101 let mut buf2 = vec![0u8; rct.serialized_len()];
1102 rct.serialize_into(&mut buf2).unwrap();
1103 assert_eq!(buf, buf2, "byte-exact re-serialize");
1104 let p = RctSection::parse(&buf).unwrap();
1105 assert_eq!(p.links.len(), 1);
1106 assert_eq!(p.links[0].link_type, LinkType::BinaryLocator);
1107 let l = p.links[0].dvb_binary_locator.as_ref().unwrap();
1108 assert_eq!(l.identifier_type, IdentifierType::EventId);
1109 assert!(l.inline_service);
1110 assert_eq!(l.start_date, 0x0FF);
1111 assert_eq!(p.links[0].items.len(), 1);
1112 assert_eq!(p.links[0].items[0].language_code, LangCode(*b"eng"));
1113 assert!(p.links[0].default_icon_flag);
1114 assert_eq!(p.links[0].icon_id, 3);
1115 }
1116
1117 #[test]
1118 fn byte_exact_round_trip_simple() {
1119 let li = LinkInfo {
1120 link_type: LinkType::UriString,
1121 how_related: HowRelated::Cs2004,
1122 term_id: 0,
1123 group_id: 0,
1124 precedence: 0,
1125 media_uri: Some(b"uri"),
1126 dvb_binary_locator: None,
1127 items: Vec::new(),
1128 default_icon_flag: false,
1129 icon_id: 0,
1130 descriptors: DescriptorLoop::new(&[]),
1131 };
1132 let rct = RctSection {
1133 table_id_extension_flag: false,
1134 service_id: 0x0001,
1135 version_number: 0,
1136 current_next_indicator: true,
1137 section_number: 0,
1138 last_section_number: 0,
1139 year_offset: 0x07D3,
1140 links: vec![li],
1141 descriptors: DescriptorLoop::new(&[]),
1142 };
1143 let mut buf = vec![0u8; rct.serialized_len()];
1144 rct.serialize_into(&mut buf).unwrap();
1145 let parsed = RctSection::parse(&buf).unwrap();
1146 let mut buf2 = vec![0u8; parsed.serialized_len()];
1147 parsed.serialize_into(&mut buf2).unwrap();
1148 assert_eq!(buf, buf2);
1149 }
1150
1151 #[test]
1152 fn parse_rejects_wrong_table_id() {
1153 let rct = RctSection {
1154 table_id_extension_flag: false,
1155 service_id: 0x0001,
1156 version_number: 0,
1157 current_next_indicator: true,
1158 section_number: 0,
1159 last_section_number: 0,
1160 year_offset: 2024,
1161 links: Vec::new(),
1162 descriptors: DescriptorLoop::new(&[]),
1163 };
1164 let mut buf = vec![0u8; rct.serialized_len()];
1165 rct.serialize_into(&mut buf).unwrap();
1166 buf[0] = 0x4A;
1167 assert!(matches!(
1168 RctSection::parse(&buf).unwrap_err(),
1169 Error::UnexpectedTableId { table_id: 0x4A, .. }
1170 ));
1171 }
1172
1173 #[test]
1174 fn parse_rejects_buffer_too_short() {
1175 assert!(matches!(
1176 RctSection::parse(&[0x76, 0x80, 0x00]).unwrap_err(),
1177 Error::BufferTooShort { .. }
1178 ));
1179 }
1180
1181 #[test]
1182 fn serialize_rejects_output_buffer_too_small() {
1183 let rct = RctSection {
1184 table_id_extension_flag: false,
1185 service_id: 0x0001,
1186 version_number: 0,
1187 current_next_indicator: true,
1188 section_number: 0,
1189 last_section_number: 0,
1190 year_offset: 0,
1191 links: Vec::new(),
1192 descriptors: DescriptorLoop::new(&[]),
1193 };
1194 let mut buf = vec![0u8; 2];
1195 assert!(matches!(
1196 rct.serialize_into(&mut buf).unwrap_err(),
1197 Error::OutputBufferTooSmall { .. }
1198 ));
1199 }
1200
1201 #[test]
1202 fn parse_rejects_zero_section_length() {
1203 let mut buf = vec![0u8; 64];
1204 buf[0] = TABLE_ID;
1205 buf[1] = 0xF0;
1206 buf[2] = 0x00;
1207 for b in &mut buf[3..] {
1208 *b = 0xFF;
1209 }
1210 assert!(matches!(
1211 RctSection::parse(&buf).unwrap_err(),
1212 Error::SectionLengthOverflow { .. }
1213 ));
1214 }
1215
1216 #[test]
1217 fn parse_handwritten_rct_no_links() {
1218 let mut bytes: Vec<u8> = vec![
1219 0x76, 0x80, 0x0E, 0x00, 0x64, 0xC7, 0x00, 0x00, 0x07, 0xD3, 0x00, 0xF0, 0x00,
1220 ];
1221 let crc = dvb_common::crc32_mpeg2::compute(&bytes);
1222 bytes.extend_from_slice(&crc.to_be_bytes());
1223 let rct = RctSection::parse(&bytes).unwrap();
1224 assert_eq!(rct.service_id, 0x0064);
1225 assert_eq!(rct.year_offset, 0x07D3);
1226 assert!(rct.links.is_empty());
1227 }
1228
1229 #[test]
1230 fn link_type_full_range_round_trip() {
1231 for v in 0u8..=0x0F {
1232 let lt = LinkType::from_u8(v);
1233 assert_eq!(lt.to_u8(), v, "LinkType round-trip failed for {v:#04x}");
1234 }
1235 }
1236
1237 #[test]
1238 fn how_related_full_range_round_trip() {
1239 for v in 0u8..=0x3F {
1240 let hr = HowRelated::from_u8(v);
1241 assert_eq!(hr.to_u8(), v, "HowRelated round-trip failed for {v:#04x}");
1242 }
1243 }
1244
1245 #[test]
1246 fn identifier_type_full_range_round_trip() {
1247 for v in 0u8..=0x03 {
1248 let it = IdentifierType::from_u8(v);
1249 assert_eq!(
1250 it.to_u8(),
1251 v,
1252 "IdentifierType round-trip failed for 0x{v:02X}"
1253 );
1254 }
1255 }
1256
1257 #[test]
1258 fn identifier_type_known_values() {
1259 assert_eq!(IdentifierType::from_u8(0), IdentifierType::None);
1260 assert_eq!(IdentifierType::from_u8(1), IdentifierType::EventId);
1261 assert_eq!(IdentifierType::from_u8(2), IdentifierType::TvaIdEit);
1262 assert_eq!(IdentifierType::from_u8(3), IdentifierType::TvaIdPes);
1263 assert_eq!(IdentifierType::None.name(), "none");
1264 assert_eq!(IdentifierType::EventId.name(), "event_id");
1265 assert_eq!(IdentifierType::TvaIdEit.name(), "TVA_id (EIT)");
1266 }
1267}