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