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