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