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