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 if v < 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 tsid = u16::from_be_bytes([data[pos], data[pos + 1]]);
437 let onid = u16::from_be_bytes([data[pos + 2], data[pos + 3]]);
438 let sid = u16::from_be_bytes([data[pos + 4], data[pos + 5]]);
439 pos += 6;
440 DvbLocatorService::Full {
441 transport_stream_id: tsid,
442 original_network_id: onid,
443 service_id: sid,
444 }
445 } else {
446 if data.len() < 3 {
447 return Err(Error::BufferTooShort {
448 need: 3,
449 have: data.len(),
450 what: "RctSection dvb_binary_locator triplet",
451 });
452 }
453 let triplet_id = ((b1 as u16 & 0x03) << 8) | data[2] as u16;
454 pos = 3;
455 DvbLocatorService::Triplet {
456 dvb_service_triplet_id: triplet_id,
457 }
458 };
459
460 if pos + 4 > data.len() {
461 return Err(Error::BufferTooShort {
462 need: pos + 4,
463 have: data.len(),
464 what: "RctSection dvb_binary_locator start_time/duration",
465 });
466 }
467 let start_time = u16::from_be_bytes([data[pos], data[pos + 1]]);
468 let duration = u16::from_be_bytes([data[pos + 2], data[pos + 3]]);
469 pos += 4;
470
471 let identifier = match identifier_type {
472 IdentifierType::None => DvbLocatorIdentifier::None,
473 IdentifierType::EventId => {
474 if pos + 2 > data.len() {
475 return Err(Error::BufferTooShort {
476 need: pos + 2,
477 have: data.len(),
478 what: "RctSection dvb_binary_locator event_id",
479 });
480 }
481 let event_id = u16::from_be_bytes([data[pos], data[pos + 1]]);
482 pos += 2;
483 DvbLocatorIdentifier::EventId { event_id }
484 }
485 IdentifierType::TvaIdEit => {
486 if pos + 2 > data.len() {
487 return Err(Error::BufferTooShort {
488 need: pos + 2,
489 have: data.len(),
490 what: "RctSection dvb_binary_locator TVA_id (EIT)",
491 });
492 }
493 let tva_id = u16::from_be_bytes([data[pos], data[pos + 1]]);
494 pos += 2;
495 DvbLocatorIdentifier::TvaIdEit { tva_id }
496 }
497 IdentifierType::TvaIdPes => {
498 if pos + 3 > data.len() {
499 return Err(Error::BufferTooShort {
500 need: pos + 3,
501 have: data.len(),
502 what: "RctSection dvb_binary_locator TVA_id (PES)",
503 });
504 }
505 let tva_id = u16::from_be_bytes([data[pos], data[pos + 1]]);
506 let component = data[pos + 2];
507 pos += 3;
508 DvbLocatorIdentifier::TvaIdPes { tva_id, component }
509 }
510 };
511
512 let windows = if identifier_type == IdentifierType::None && scheduled_time_reliability {
513 if pos >= data.len() {
514 return Err(Error::BufferTooShort {
515 need: pos + 1,
516 have: data.len(),
517 what: "RctSection dvb_binary_locator windows",
518 });
519 }
520 let wb = data[pos];
521 pos += 1;
522 Some(DvbLocatorWindows {
523 early_start_window: (wb >> 5) & 0x07,
524 late_end_window: wb & 0x1F,
525 })
526 } else {
527 None
528 };
529
530 Ok((
531 DvbBinaryLocator {
532 identifier_type,
533 scheduled_time_reliability,
534 inline_service,
535 start_date,
536 service,
537 start_time,
538 duration,
539 identifier,
540 windows,
541 },
542 pos,
543 ))
544}
545
546fn serialize_locator(loc: &DvbBinaryLocator, buf: &mut [u8]) -> usize {
547 let b0 = (loc.identifier_type.to_u8() << LOCATOR_ID_TYPE_SHIFT)
548 | (u8::from(loc.scheduled_time_reliability) << 5)
549 | (u8::from(loc.inline_service) << 4)
550 | LOCATOR_RESERVED_BITS
551 | ((loc.start_date >> LOCATOR_START_DATE_HI_SHIFT) as u8 & LOCATOR_START_DATE_HI_MASK);
552 buf[0] = b0;
553
554 let mut pos: usize;
555 match &loc.service {
556 DvbLocatorService::Triplet {
557 dvb_service_triplet_id,
558 } => {
559 let sd_lo = (loc.start_date & 0x3F) as u8;
560 buf[1] = (sd_lo << 2) | ((dvb_service_triplet_id >> 8) as u8 & 0x03);
561 buf[2] = (dvb_service_triplet_id & 0xFF) as u8;
562 pos = 3;
563 }
564 DvbLocatorService::Full {
565 transport_stream_id,
566 original_network_id,
567 service_id,
568 } => {
569 let sd_lo = (loc.start_date & 0x3F) as u8;
570 buf[1] = (sd_lo << 2) | 0x03;
571 buf[2..4].copy_from_slice(&transport_stream_id.to_be_bytes());
572 buf[4..6].copy_from_slice(&original_network_id.to_be_bytes());
573 buf[6..8].copy_from_slice(&service_id.to_be_bytes());
574 pos = 8;
575 }
576 }
577 buf[pos..pos + 2].copy_from_slice(&loc.start_time.to_be_bytes());
578 buf[pos + 2..pos + 4].copy_from_slice(&loc.duration.to_be_bytes());
579 pos += 4;
580
581 match &loc.identifier {
582 DvbLocatorIdentifier::None => {}
583 DvbLocatorIdentifier::EventId { event_id } => {
584 buf[pos..pos + 2].copy_from_slice(&event_id.to_be_bytes());
585 pos += 2;
586 }
587 DvbLocatorIdentifier::TvaIdEit { tva_id } => {
588 buf[pos..pos + 2].copy_from_slice(&tva_id.to_be_bytes());
589 pos += 2;
590 }
591 DvbLocatorIdentifier::TvaIdPes { tva_id, component } => {
592 buf[pos..pos + 2].copy_from_slice(&tva_id.to_be_bytes());
593 buf[pos + 2] = *component;
594 pos += 3;
595 }
596 }
597 if let Some(w) = &loc.windows {
598 buf[pos] = ((w.early_start_window & 0x07) << 5) | (w.late_end_window & 0x1F);
599 pos += 1;
600 }
601 pos
602}
603
604fn parse_link_info(data: &[u8], link_info_length: usize) -> Result<LinkInfo<'_>> {
605 if data.len() < 4 {
606 return Err(Error::BufferTooShort {
607 need: 4,
608 have: data.len(),
609 what: "RctSection link_info header",
610 });
611 }
612 let link_type = LinkType::from_u8((data[0] & LINK_TYPE_MASK) >> LINK_TYPE_SHIFT);
613 let how_related_raw = (data[0] & HOW_RELATED_HI_MASK) << 4 | (data[1] >> 4) & 0x0F;
614 let how_related = HowRelated::from_u8(how_related_raw);
615 let term_id = ((data[1] as u16 & TERM_ID_HI_MASK as u16) << TERM_ID_HI_SHIFT) | data[2] as u16;
616 let group_id = (data[3] & GROUP_ID_MASK) >> GROUP_ID_SHIFT;
617 let precedence = data[3] & PRECEDENCE_MASK;
618
619 let mut pos = 4;
620 let end = link_info_length;
621
622 let media_uri = if link_type == LinkType::UriString || link_type == LinkType::Both {
623 if pos >= end {
624 return Err(Error::BufferTooShort {
625 need: pos + 1,
626 have: end,
627 what: "RctSection media_uri_length",
628 });
629 }
630 let uri_len = data[pos] as usize;
631 pos += 1;
632 if pos + uri_len > end {
633 return Err(Error::BufferTooShort {
634 need: pos + uri_len,
635 have: end,
636 what: "RctSection media_uri",
637 });
638 }
639 let uri = &data[pos..pos + uri_len];
640 pos += uri_len;
641 Some(uri)
642 } else {
643 None
644 };
645
646 let dvb_binary_locator = if link_type == LinkType::BinaryLocator || link_type == LinkType::Both
647 {
648 if pos >= end {
649 return Err(Error::BufferTooShort {
650 need: pos + 1,
651 have: end,
652 what: "RctSection dvb_binary_locator",
653 });
654 }
655 let (loc, consumed) = parse_locator(&data[pos..end])?;
656 pos += consumed;
657 Some(loc)
658 } else {
659 None
660 };
661
662 if pos >= end {
663 return Err(Error::BufferTooShort {
664 need: pos + 1,
665 have: end,
666 what: "RctSection number_items",
667 });
668 }
669 let ni_byte = data[pos];
670 let number_items = (ni_byte & ITEM_COUNT_MASK) as usize;
671 pos += 1;
672
673 let mut items = Vec::with_capacity(number_items);
674 for _ in 0..number_items {
675 if pos + 4 > end {
676 return Err(Error::BufferTooShort {
677 need: pos + 4,
678 have: end,
679 what: "RctSection link_item",
680 });
681 }
682 let language_code = LangCode([data[pos], data[pos + 1], data[pos + 2]]);
683 let text_len = data[pos + 3] as usize;
684 pos += 4;
685 if pos + text_len > end {
686 return Err(Error::BufferTooShort {
687 need: pos + text_len,
688 have: end,
689 what: "RctSection promotional_text",
690 });
691 }
692 let promotional_text = DvbText::new(&data[pos..pos + text_len]);
693 pos += text_len;
694 items.push(LinkItem {
695 language_code,
696 promotional_text,
697 });
698 }
699
700 if pos + 2 > end {
701 return Err(Error::BufferTooShort {
702 need: pos + 2,
703 have: end,
704 what: "RctSection icon/desc",
705 });
706 }
707 let default_icon_flag = (data[pos] & ICON_FLAG_MASK) != 0;
708 let icon_id = (data[pos] & ICON_ID_MASK) >> ICON_ID_SHIFT;
709 let desc_len = (((data[pos] & ICON_DESC_LEN_HI_MASK) as usize) << 8) | data[pos + 1] as usize;
710 pos += 2;
711 let desc_start = pos;
712 let desc_end = desc_start + desc_len;
713 if desc_end > end {
714 return Err(Error::SectionLengthOverflow {
715 declared: desc_len,
716 available: end.saturating_sub(desc_start),
717 });
718 }
719 let descriptors = DescriptorLoop::new(&data[desc_start..desc_end]);
720
721 Ok(LinkInfo {
722 link_type,
723 how_related,
724 term_id,
725 group_id,
726 precedence,
727 media_uri,
728 dvb_binary_locator,
729 items,
730 default_icon_flag,
731 icon_id,
732 descriptors,
733 })
734}
735
736fn serialize_link_info(li: &LinkInfo, buf: &mut [u8]) -> usize {
737 let lt = li.link_type.to_u8();
738 let hr = li.how_related.to_u8();
739 buf[0] =
740 ((lt & 0x0F) << LINK_TYPE_SHIFT) | LINK_INFO_HEADER_RFU | ((hr >> 4) & HOW_RELATED_HI_MASK);
741 buf[1] = ((hr & 0x0F) << 4) | ((li.term_id >> TERM_ID_HI_SHIFT) as u8 & TERM_ID_HI_MASK);
742 buf[2] = li.term_id as u8;
743 buf[3] = ((li.group_id & 0x0F) << GROUP_ID_SHIFT) | (li.precedence & PRECEDENCE_MASK);
744
745 let mut pos = 4;
746 if let Some(uri) = li.media_uri {
747 buf[pos] = uri.len() as u8;
748 pos += 1;
749 buf[pos..pos + uri.len()].copy_from_slice(uri);
750 pos += uri.len();
751 }
752 if let Some(ref loc) = li.dvb_binary_locator {
753 let n = serialize_locator(loc, &mut buf[pos..]);
754 pos += n;
755 }
756 buf[pos] = ITEM_RFU_MASK | (li.items.len() as u8 & ITEM_COUNT_MASK);
757 pos += 1;
758 for item in &li.items {
759 buf[pos..pos + 3].copy_from_slice(&item.language_code.0);
760 buf[pos + 3] = item.promotional_text.len() as u8;
761 pos += 4;
762 buf[pos..pos + item.promotional_text.len()].copy_from_slice(item.promotional_text.raw());
763 pos += item.promotional_text.len();
764 }
765 let dll = li.descriptors.len() as u16;
766 buf[pos] = u8::from(li.default_icon_flag) << 7
767 | ((li.icon_id & 0x07) << ICON_ID_SHIFT)
768 | ((dll >> 8) as u8 & ICON_DESC_LEN_HI_MASK);
769 buf[pos + 1] = (dll & 0xFF) as u8;
770 pos += 2;
771 buf[pos..pos + li.descriptors.len()].copy_from_slice(li.descriptors.raw());
772 pos += li.descriptors.len();
773 pos
774}
775
776impl<'a> Parse<'a> for RctSection<'a> {
777 type Error = crate::error::Error;
778
779 fn parse(bytes: &'a [u8]) -> Result<Self> {
780 if bytes.len() < MIN_SECTION_LEN {
781 return Err(Error::BufferTooShort {
782 need: MIN_SECTION_LEN,
783 have: bytes.len(),
784 what: "RctSection",
785 });
786 }
787 if bytes[0] != TABLE_ID {
788 return Err(Error::UnexpectedTableId {
789 table_id: bytes[0],
790 what: "RctSection",
791 expected: &[TABLE_ID],
792 });
793 }
794
795 let table_id_extension_flag = (bytes[1] & 0x40) != 0;
796 let section_length = (((bytes[1] & 0x0F) as u16) << 8) | bytes[2] as u16;
797 let total = super::check_section_length(
798 bytes.len(),
799 MIN_HEADER_LEN,
800 section_length as usize,
801 MIN_SECTION_LEN,
802 )?;
803
804 let service_id = u16::from_be_bytes([bytes[3], bytes[4]]);
805 let version_number = (bytes[5] >> 1) & 0x1F;
806 let current_next_indicator = (bytes[5] & 0x01) != 0;
807 let section_number = bytes[6];
808 let last_section_number = bytes[7];
809 let year_offset = u16::from_be_bytes([bytes[8], bytes[9]]);
810 let link_count = bytes[10];
811
812 let payload_end = total - CRC_LEN;
813 let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXT_FIXED_LEN;
814
815 let mut links = Vec::with_capacity(link_count as usize);
816 for _ in 0..link_count {
817 if pos + LINK_ENTRY_HEADER_LEN > payload_end {
818 return Err(Error::BufferTooShort {
819 need: pos + LINK_ENTRY_HEADER_LEN,
820 have: payload_end,
821 what: "RctSection link_entry header",
822 });
823 }
824 let link_info_length = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
825 let link_data_start = pos + LINK_ENTRY_HEADER_LEN;
826 let link_data_end = link_data_start + link_info_length;
827 if link_data_end > payload_end {
828 return Err(Error::SectionLengthOverflow {
829 declared: link_info_length,
830 available: payload_end.saturating_sub(link_data_start),
831 });
832 }
833 let link_info =
834 parse_link_info(&bytes[link_data_start..link_data_end], link_info_length)?;
835 links.push(link_info);
836 pos = link_data_end;
837 }
838
839 if pos + DESC_LOOP_LEN_FIELD > payload_end {
840 return Err(Error::BufferTooShort {
841 need: pos + DESC_LOOP_LEN_FIELD,
842 have: payload_end,
843 what: "RctSection descriptor_loop_length field",
844 });
845 }
846 let descriptor_loop_length =
847 (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
848 let desc_start = pos + DESC_LOOP_LEN_FIELD;
849 let desc_end = desc_start + descriptor_loop_length;
850 if desc_end > payload_end {
851 return Err(Error::SectionLengthOverflow {
852 declared: descriptor_loop_length,
853 available: payload_end.saturating_sub(desc_start),
854 });
855 }
856 let descriptors = DescriptorLoop::new(&bytes[desc_start..desc_end]);
857
858 Ok(RctSection {
859 table_id_extension_flag,
860 service_id,
861 version_number,
862 current_next_indicator,
863 section_number,
864 last_section_number,
865 year_offset,
866 links,
867 descriptors,
868 })
869 }
870}
871
872impl Serialize for RctSection<'_> {
873 type Error = crate::error::Error;
874
875 fn serialized_len(&self) -> usize {
876 MIN_HEADER_LEN
877 + EXTENSION_HEADER_LEN
878 + POST_EXT_FIXED_LEN
879 + self
880 .links
881 .iter()
882 .map(|li| LINK_ENTRY_HEADER_LEN + link_info_serialized_len(li))
883 .sum::<usize>()
884 + DESC_LOOP_LEN_FIELD
885 + self.descriptors.len()
886 + CRC_LEN
887 }
888
889 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
890 let len = self.serialized_len();
891 if buf.len() < len {
892 return Err(Error::OutputBufferTooSmall {
893 need: len,
894 have: buf.len(),
895 });
896 }
897
898 let section_length_usize = len - MIN_HEADER_LEN;
899 if section_length_usize > 0x0FFF {
900 return Err(Error::SectionLengthOverflow {
901 declared: section_length_usize,
902 available: 0x0FFF,
903 });
904 }
905 let section_length = section_length_usize as u16;
906 buf[0] = TABLE_ID;
907 let tief_bit: u8 = if self.table_id_extension_flag {
908 0x40
909 } else {
910 0x00
911 };
912 buf[1] = super::SECTION_B1_SSI
913 | tief_bit
914 | super::SECTION_B1_RESERVED_HI
915 | ((section_length >> 8) as u8 & 0x0F);
916 buf[2] = (section_length & 0xFF) as u8;
917
918 buf[3..5].copy_from_slice(&self.service_id.to_be_bytes());
919 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
920 buf[6] = self.section_number;
921 buf[7] = self.last_section_number;
922 buf[8..10].copy_from_slice(&self.year_offset.to_be_bytes());
923 if self.links.len() > u8::MAX as usize {
924 return Err(Error::SectionLengthOverflow {
925 declared: self.links.len(),
926 available: u8::MAX as usize,
927 });
928 }
929 buf[10] = self.links.len() as u8;
930
931 let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXT_FIXED_LEN;
932 for li in &self.links {
933 let li_body_len = link_info_serialized_len(li) as u16;
934 buf[pos] = 0xF0 | ((li_body_len >> 8) as u8 & 0x0F);
935 buf[pos + 1] = (li_body_len & 0xFF) as u8;
936 pos += LINK_ENTRY_HEADER_LEN;
937 pos += serialize_link_info(li, &mut buf[pos..]);
938 }
939
940 let dll = self.descriptors.len() as u16;
941 buf[pos] = 0xF0 | ((dll >> 8) as u8 & 0x0F);
942 buf[pos + 1] = (dll & 0xFF) as u8;
943 pos += DESC_LOOP_LEN_FIELD;
944 buf[pos..pos + self.descriptors.len()].copy_from_slice(self.descriptors.raw());
945 pos += self.descriptors.len();
946
947 let crc = dvb_common::crc32_mpeg2::compute(&buf[..pos]);
948 buf[pos..pos + CRC_LEN].copy_from_slice(&crc.to_be_bytes());
949 Ok(len)
950 }
951}
952impl<'a> crate::traits::TableDef<'a> for RctSection<'a> {
953 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
954 const NAME: &'static str = "RELATED_CONTENT";
955}
956
957#[cfg(test)]
958mod tests {
959 use super::*;
960
961 #[test]
962 fn parse_no_links_no_descriptors() {
963 let rct = RctSection {
964 table_id_extension_flag: false,
965 service_id: 0x0064,
966 version_number: 3,
967 current_next_indicator: true,
968 section_number: 0,
969 last_section_number: 0,
970 year_offset: 0x07D3,
971 links: Vec::new(),
972 descriptors: DescriptorLoop::new(&[]),
973 };
974 let mut buf = vec![0u8; rct.serialized_len()];
975 rct.serialize_into(&mut buf).unwrap();
976 let p = RctSection::parse(&buf).unwrap();
977 assert!(!p.table_id_extension_flag);
978 assert_eq!(p.service_id, 0x0064);
979 assert_eq!(p.year_offset, 0x07D3);
980 assert!(p.links.is_empty());
981 }
982
983 #[test]
984 fn parse_one_link_uri_only() {
985 let li = LinkInfo {
986 link_type: LinkType::UriString,
987 how_related: HowRelated::Cs2005,
988 term_id: 0x123,
989 group_id: 0x5,
990 precedence: 0x9,
991 media_uri: Some(b"http://example.com"),
992 dvb_binary_locator: None,
993 items: Vec::new(),
994 default_icon_flag: false,
995 icon_id: 0,
996 descriptors: DescriptorLoop::new(&[]),
997 };
998 let rct = RctSection {
999 table_id_extension_flag: false,
1000 service_id: 0x1234,
1001 version_number: 7,
1002 current_next_indicator: true,
1003 section_number: 1,
1004 last_section_number: 3,
1005 year_offset: 2003,
1006 links: vec![li],
1007 descriptors: DescriptorLoop::new(&[]),
1008 };
1009 let mut buf = vec![0u8; rct.serialized_len()];
1010 rct.serialize_into(&mut buf).unwrap();
1011 let p = RctSection::parse(&buf).unwrap();
1012 assert_eq!(p.links.len(), 1);
1013 assert_eq!(p.links[0].link_type, LinkType::UriString);
1014 assert_eq!(p.links[0].how_related, HowRelated::Cs2005);
1015 assert_eq!(p.links[0].term_id, 0x123);
1016 assert_eq!(p.links[0].group_id, 0x5);
1017 assert_eq!(p.links[0].precedence, 0x9);
1018 assert_eq!(p.links[0].media_uri.unwrap(), b"http://example.com");
1019 }
1020
1021 #[test]
1022 fn parse_one_link_with_locator_and_items() {
1023 let loc = DvbBinaryLocator {
1024 identifier_type: IdentifierType::EventId,
1025 scheduled_time_reliability: false,
1026 inline_service: true,
1027 start_date: 0x0FF,
1028 service: DvbLocatorService::Full {
1029 transport_stream_id: 0x1000,
1030 original_network_id: 0x2000,
1031 service_id: 0x3000,
1032 },
1033 start_time: 0x8000,
1034 duration: 0x4000,
1035 identifier: DvbLocatorIdentifier::EventId { event_id: 0xBEEF },
1036 windows: None,
1037 };
1038 let li = LinkInfo {
1039 link_type: LinkType::BinaryLocator,
1040 how_related: HowRelated::Cs2007,
1041 term_id: 0x456,
1042 group_id: 0x1,
1043 precedence: 0x2,
1044 media_uri: None,
1045 dvb_binary_locator: Some(loc),
1046 items: vec![LinkItem {
1047 language_code: LangCode(*b"eng"),
1048 promotional_text: DvbText::new(b"Promo"),
1049 }],
1050 default_icon_flag: true,
1051 icon_id: 3,
1052 descriptors: DescriptorLoop::new(&[0x80, 0x00]),
1053 };
1054 let rct = RctSection {
1055 table_id_extension_flag: true,
1056 service_id: 0xABCD,
1057 version_number: 15,
1058 current_next_indicator: false,
1059 section_number: 2,
1060 last_section_number: 5,
1061 year_offset: 2024,
1062 links: vec![li],
1063 descriptors: DescriptorLoop::new(&[]),
1064 };
1065 let mut buf = vec![0u8; rct.serialized_len()];
1066 rct.serialize_into(&mut buf).unwrap();
1067 let mut buf2 = vec![0u8; rct.serialized_len()];
1068 rct.serialize_into(&mut buf2).unwrap();
1069 assert_eq!(buf, buf2, "byte-exact re-serialize");
1070 let p = RctSection::parse(&buf).unwrap();
1071 assert_eq!(p.links.len(), 1);
1072 assert_eq!(p.links[0].link_type, LinkType::BinaryLocator);
1073 let l = p.links[0].dvb_binary_locator.as_ref().unwrap();
1074 assert_eq!(l.identifier_type, IdentifierType::EventId);
1075 assert!(l.inline_service);
1076 assert_eq!(l.start_date, 0x0FF);
1077 assert_eq!(p.links[0].items.len(), 1);
1078 assert_eq!(p.links[0].items[0].language_code, LangCode(*b"eng"));
1079 assert!(p.links[0].default_icon_flag);
1080 assert_eq!(p.links[0].icon_id, 3);
1081 }
1082
1083 #[test]
1084 fn byte_exact_round_trip_simple() {
1085 let li = LinkInfo {
1086 link_type: LinkType::UriString,
1087 how_related: HowRelated::Cs2004,
1088 term_id: 0,
1089 group_id: 0,
1090 precedence: 0,
1091 media_uri: Some(b"uri"),
1092 dvb_binary_locator: None,
1093 items: Vec::new(),
1094 default_icon_flag: false,
1095 icon_id: 0,
1096 descriptors: DescriptorLoop::new(&[]),
1097 };
1098 let rct = RctSection {
1099 table_id_extension_flag: false,
1100 service_id: 0x0001,
1101 version_number: 0,
1102 current_next_indicator: true,
1103 section_number: 0,
1104 last_section_number: 0,
1105 year_offset: 0x07D3,
1106 links: vec![li],
1107 descriptors: DescriptorLoop::new(&[]),
1108 };
1109 let mut buf = vec![0u8; rct.serialized_len()];
1110 rct.serialize_into(&mut buf).unwrap();
1111 let parsed = RctSection::parse(&buf).unwrap();
1112 let mut buf2 = vec![0u8; parsed.serialized_len()];
1113 parsed.serialize_into(&mut buf2).unwrap();
1114 assert_eq!(buf, buf2);
1115 }
1116
1117 #[test]
1118 fn parse_rejects_wrong_table_id() {
1119 let rct = RctSection {
1120 table_id_extension_flag: false,
1121 service_id: 0x0001,
1122 version_number: 0,
1123 current_next_indicator: true,
1124 section_number: 0,
1125 last_section_number: 0,
1126 year_offset: 2024,
1127 links: Vec::new(),
1128 descriptors: DescriptorLoop::new(&[]),
1129 };
1130 let mut buf = vec![0u8; rct.serialized_len()];
1131 rct.serialize_into(&mut buf).unwrap();
1132 buf[0] = 0x4A;
1133 assert!(matches!(
1134 RctSection::parse(&buf).unwrap_err(),
1135 Error::UnexpectedTableId { table_id: 0x4A, .. }
1136 ));
1137 }
1138
1139 #[test]
1140 fn parse_rejects_buffer_too_short() {
1141 assert!(matches!(
1142 RctSection::parse(&[0x76, 0x80, 0x00]).unwrap_err(),
1143 Error::BufferTooShort { .. }
1144 ));
1145 }
1146
1147 #[test]
1148 fn serialize_rejects_output_buffer_too_small() {
1149 let rct = RctSection {
1150 table_id_extension_flag: false,
1151 service_id: 0x0001,
1152 version_number: 0,
1153 current_next_indicator: true,
1154 section_number: 0,
1155 last_section_number: 0,
1156 year_offset: 0,
1157 links: Vec::new(),
1158 descriptors: DescriptorLoop::new(&[]),
1159 };
1160 let mut buf = vec![0u8; 2];
1161 assert!(matches!(
1162 rct.serialize_into(&mut buf).unwrap_err(),
1163 Error::OutputBufferTooSmall { .. }
1164 ));
1165 }
1166
1167 #[test]
1168 fn parse_rejects_zero_section_length() {
1169 let mut buf = vec![0u8; 64];
1170 buf[0] = TABLE_ID;
1171 buf[1] = 0xF0;
1172 buf[2] = 0x00;
1173 for b in &mut buf[3..] {
1174 *b = 0xFF;
1175 }
1176 assert!(matches!(
1177 RctSection::parse(&buf).unwrap_err(),
1178 Error::SectionLengthOverflow { .. }
1179 ));
1180 }
1181
1182 #[test]
1183 fn parse_handwritten_rct_no_links() {
1184 let mut bytes: Vec<u8> = vec![
1185 0x76, 0x80, 0x0E, 0x00, 0x64, 0xC7, 0x00, 0x00, 0x07, 0xD3, 0x00, 0xF0, 0x00,
1186 ];
1187 let crc = dvb_common::crc32_mpeg2::compute(&bytes);
1188 bytes.extend_from_slice(&crc.to_be_bytes());
1189 let rct = RctSection::parse(&bytes).unwrap();
1190 assert_eq!(rct.service_id, 0x0064);
1191 assert_eq!(rct.year_offset, 0x07D3);
1192 assert!(rct.links.is_empty());
1193 }
1194
1195 #[test]
1196 fn link_type_full_range_round_trip() {
1197 for v in 0u8..=0x0F {
1198 let lt = LinkType::from_u8(v);
1199 assert_eq!(lt.to_u8(), v, "LinkType round-trip failed for {v:#04x}");
1200 }
1201 }
1202
1203 #[test]
1204 fn how_related_full_range_round_trip() {
1205 for v in 0u8..=0x3F {
1206 let hr = HowRelated::from_u8(v);
1207 assert_eq!(hr.to_u8(), v, "HowRelated round-trip failed for {v:#04x}");
1208 }
1209 }
1210
1211 #[test]
1212 fn identifier_type_full_range_round_trip() {
1213 for v in 0u8..=0x03 {
1214 let it = IdentifierType::from_u8(v);
1215 assert_eq!(
1216 it.to_u8(),
1217 v,
1218 "IdentifierType round-trip failed for 0x{v:02X}"
1219 );
1220 }
1221 }
1222
1223 #[test]
1224 fn identifier_type_known_values() {
1225 assert_eq!(IdentifierType::from_u8(0), IdentifierType::None);
1226 assert_eq!(IdentifierType::from_u8(1), IdentifierType::EventId);
1227 assert_eq!(IdentifierType::from_u8(2), IdentifierType::TvaIdEit);
1228 assert_eq!(IdentifierType::from_u8(3), IdentifierType::TvaIdPes);
1229 assert_eq!(IdentifierType::None.name(), "none");
1230 assert_eq!(IdentifierType::EventId.name(), "event_id");
1231 assert_eq!(IdentifierType::TvaIdEit.name(), "TVA_id (EIT)");
1232 }
1233}