1use alloc::collections::BTreeMap;
2use alloc::sync::Arc;
3use alloc::vec;
4use alloc::vec::Vec;
5
6use crate::descriptors::DescriptorRegistry;
7use crate::tables::RunningStatus;
8use crate::tables::eit;
9use broadcast_common::Parse;
10use mpeg_ts::section::Section;
11
12use super::{
13 CollectError, CollectResult, CompleteSectionSet, ParsedDescriptorLoop, SectionSetKey,
14 SectionSetMeta,
15};
16
17pub const DEFAULT_MAX_LOGICAL_KEYS: usize = 256;
29
30#[derive(Debug)]
44pub struct EitCollector {
45 sections: BTreeMap<EitSectionSetKey, PartialEitSectionSet>,
46 schedules: BTreeMap<EitLogicalKey, PartialEitSchedule>,
47 max_logical_keys: usize,
48}
49
50impl Default for EitCollector {
51 fn default() -> Self {
52 Self {
53 sections: BTreeMap::new(),
54 schedules: BTreeMap::new(),
55 max_logical_keys: DEFAULT_MAX_LOGICAL_KEYS,
56 }
57 }
58}
59
60impl EitCollector {
61 #[must_use]
64 pub fn new() -> Self {
65 Self::default()
66 }
67
68 #[must_use]
74 pub fn with_max_logical_keys(mut self, max_logical_keys: usize) -> Self {
75 self.max_logical_keys = max_logical_keys;
76 self
77 }
78
79 pub fn push_section(&mut self, bytes: impl AsRef<[u8]>) -> CollectResult<Option<CompletedEit>> {
91 self.push_section_with_pid(None, bytes)
92 }
93
94 pub fn push_section_with_pid(
96 &mut self,
97 pid: Option<u16>,
98 bytes: impl AsRef<[u8]>,
99 ) -> CollectResult<Option<CompletedEit>> {
100 let raw = bytes.as_ref();
101 let section = Section::parse(raw)?;
102 if !section.section_syntax_indicator {
103 return Err(CollectError::ShortFormSection {
104 table_id: section.table_id,
105 });
106 }
107 if section.section_number > section.last_section_number {
108 return Err(CollectError::SectionNumberOutOfRange {
109 table_id: section.table_id,
110 section_number: section.section_number,
111 last_section_number: section.last_section_number,
112 });
113 }
114 section.validate_crc(raw)?;
115
116 let eit = eit::EitSection::parse(raw)?;
117 let logical_key = EitLogicalKey {
118 pid,
119 kind: eit.kind,
120 service_id: eit.service_id,
121 transport_stream_id: eit.transport_stream_id,
122 original_network_id: eit.original_network_id,
123 current_next_indicator: eit.current_next_indicator,
124 };
125 let key = EitSectionSetKey {
126 logical_key,
127 table_id: eit.table_id,
128 };
129 let meta = EitSectionSetMeta {
130 key,
131 version_number: eit.version_number,
132 last_section_number: eit.last_section_number,
133 };
134 let bytes: Arc<[u8]> = Arc::from(raw);
135
136 if !self.sections.contains_key(&key) && self.sections.len() >= self.max_logical_keys {
138 return Ok(None);
139 }
140
141 let partial = self
142 .sections
143 .entry(key)
144 .or_insert_with(|| PartialEitSectionSet::new(meta));
145 if partial.meta.version_number != meta.version_number
146 || partial.meta.last_section_number != meta.last_section_number
147 {
148 partial.reset(meta);
149 }
150
151 partial.insert(eit.section_number, eit.segment_last_section_number, bytes)?;
152 let complete = match partial.to_complete() {
153 Some(complete) => complete,
154 None => return Ok(None),
155 };
156
157 match eit.kind {
158 eit::EitKind::PresentFollowingActual | eit::EitKind::PresentFollowingOther => {
159 partial.emitted = true;
160 Ok(Some(CompletedEit::PresentFollowing(complete)))
161 }
162 eit::EitKind::ScheduleActual | eit::EitKind::ScheduleOther => {
163 let first_table_id = match eit.kind {
164 eit::EitKind::ScheduleActual => eit::TABLE_ID_SCHEDULE_ACTUAL_FIRST,
165 eit::EitKind::ScheduleOther => eit::TABLE_ID_SCHEDULE_OTHER_FIRST,
166 _ => unreachable!("matched schedule kind above"),
167 };
168 if eit.table_id < first_table_id || eit.table_id > eit.last_table_id {
169 return Err(CollectError::EitTableIdOutOfRange {
170 table_id: eit.table_id,
171 first_table_id,
172 last_table_id: eit.last_table_id,
173 });
174 }
175
176 if !self.schedules.contains_key(&logical_key)
178 && self.schedules.len() >= self.max_logical_keys
179 {
180 return Ok(None);
181 }
182
183 partial.emitted = true;
184
185 let schedule_meta = EitScheduleMeta {
186 key: logical_key,
187 first_table_id,
188 last_table_id: eit.last_table_id,
189 };
190 let schedule = self
191 .schedules
192 .entry(logical_key)
193 .or_insert_with(|| PartialEitSchedule::new(schedule_meta));
194 if schedule.meta.last_table_id != schedule_meta.last_table_id {
195 schedule.reset(schedule_meta);
196 }
197 schedule.insert(eit.table_id, complete);
198 if let Some(complete) = schedule.to_complete() {
199 schedule.emitted = true;
200 Ok(Some(CompletedEit::Schedule(complete)))
201 } else {
202 Ok(None)
203 }
204 }
205 }
206 }
207
208 pub fn clear(&mut self) {
214 self.sections.clear();
215 self.schedules.clear();
216 }
217
218 pub fn retain_logical<F>(&mut self, mut keep: F)
224 where
225 F: FnMut(&EitLogicalKey) -> bool,
226 {
227 self.sections.retain(|key, _| keep(&key.logical_key));
228 self.schedules.retain(|key, _| keep(key));
229 }
230
231 #[must_use]
233 pub fn section_set_len(&self) -> usize {
234 self.sections.len()
235 }
236
237 #[must_use]
239 pub fn schedule_len(&self) -> usize {
240 self.schedules.len()
241 }
242}
243
244#[derive(Debug, Clone)]
246#[non_exhaustive]
247pub enum CompletedEit {
248 PresentFollowing(CompleteSectionSet),
250 Schedule(CompleteEitSchedule),
252}
253
254impl CompletedEit {
255 pub fn tables(&self) -> crate::Result<Vec<CompleteEit<'_>>> {
257 self.tables_with_registry(None)
258 }
259
260 pub fn tables_with_registry<'a>(
262 &'a self,
263 registry: Option<&'a DescriptorRegistry>,
264 ) -> crate::Result<Vec<CompleteEit<'a>>> {
265 match self {
266 Self::PresentFollowing(set) => Ok(vec![CompleteEit::parse(set, registry)?]),
267 Self::Schedule(schedule) => schedule.tables_with_registry(registry),
268 }
269 }
270}
271
272#[non_exhaustive]
274#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
275pub struct EitLogicalKey {
276 pub pid: Option<u16>,
278 pub kind: eit::EitKind,
280 pub service_id: u16,
282 pub transport_stream_id: u16,
284 pub original_network_id: u16,
286 pub current_next_indicator: bool,
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
291struct EitSectionSetKey {
292 logical_key: EitLogicalKey,
293 table_id: u8,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297struct EitScheduleMeta {
298 key: EitLogicalKey,
299 first_table_id: u8,
300 last_table_id: u8,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304struct EitSectionSetMeta {
305 key: EitSectionSetKey,
306 version_number: u8,
307 last_section_number: u8,
308}
309
310const EIT_SEGMENT_SIZE: u8 = 8;
313
314#[derive(Debug)]
315struct PartialEitSectionSet {
316 meta: EitSectionSetMeta,
317 slots: Vec<Option<Arc<[u8]>>>,
318 filled: usize,
319 emitted: bool,
320 segment_ends: BTreeMap<u8, u8>,
330}
331
332impl PartialEitSectionSet {
333 fn new(meta: EitSectionSetMeta) -> Self {
334 let len = meta.last_section_number as usize + 1;
335 Self {
336 meta,
337 slots: vec![None; len],
338 filled: 0,
339 emitted: false,
340 segment_ends: BTreeMap::new(),
341 }
342 }
343
344 fn reset(&mut self, meta: EitSectionSetMeta) {
345 *self = Self::new(meta);
346 }
347
348 fn insert(
349 &mut self,
350 section_number: u8,
351 segment_last_section_number: u8,
352 bytes: Arc<[u8]>,
353 ) -> CollectResult<bool> {
354 let index = section_number as usize;
355 if let Some(existing) = &self.slots[index] {
356 if existing.as_ref() == bytes.as_ref() {
357 return Ok(false);
358 }
359 return Err(CollectError::ConflictingSection {
360 table_id: self.meta.key.table_id,
361 section_number,
362 });
363 }
364
365 self.slots[index] = Some(bytes);
366 self.filled += 1;
367 self.emitted = false;
368 self.segment_ends.insert(
369 section_number / EIT_SEGMENT_SIZE,
370 segment_last_section_number,
371 );
372 Ok(true)
373 }
374
375 fn complete(&self) -> bool {
387 let last_segment = self.meta.last_section_number / EIT_SEGMENT_SIZE;
388 for segment in 0..=last_segment {
389 let Some(&segment_last_raw) = self.segment_ends.get(&segment) else {
390 return false;
393 };
394 let segment_start = segment * EIT_SEGMENT_SIZE;
395 let segment_last = segment_start
396 .saturating_add(segment_last_raw % EIT_SEGMENT_SIZE)
397 .min(self.meta.last_section_number);
398 for section_number in segment_start..=segment_last {
399 if self.slots[section_number as usize].is_none() {
400 return false;
401 }
402 }
403 }
404 true
405 }
406
407 fn to_complete(&self) -> Option<CompleteSectionSet> {
408 if !self.complete() || self.emitted {
409 return None;
410 }
411
412 let sections = self.slots.iter().filter_map(Clone::clone).collect();
416 Some(CompleteSectionSet {
417 meta: SectionSetMeta {
418 key: SectionSetKey {
419 pid: self.meta.key.logical_key.pid,
420 table_id: self.meta.key.table_id,
421 extension_id: self.meta.key.logical_key.service_id,
422 current_next_indicator: self.meta.key.logical_key.current_next_indicator,
423 },
424 version_number: self.meta.version_number,
425 last_section_number: self.meta.last_section_number,
426 },
427 sections,
428 })
429 }
430}
431
432#[derive(Debug)]
433struct PartialEitSchedule {
434 meta: EitScheduleMeta,
435 table_sets: BTreeMap<u8, CompleteSectionSet>,
436 emitted: bool,
437}
438
439impl PartialEitSchedule {
440 fn new(meta: EitScheduleMeta) -> Self {
441 Self {
442 meta,
443 table_sets: BTreeMap::new(),
444 emitted: false,
445 }
446 }
447
448 fn reset(&mut self, meta: EitScheduleMeta) {
449 *self = Self::new(meta);
450 }
451
452 fn insert(&mut self, table_id: u8, set: CompleteSectionSet) {
453 self.table_sets.insert(table_id, set);
454 self.emitted = false;
455 }
456
457 fn complete(&self) -> bool {
458 (self.meta.first_table_id..=self.meta.last_table_id)
459 .all(|table_id| self.table_sets.contains_key(&table_id))
460 }
461
462 fn to_complete(&self) -> Option<CompleteEitSchedule> {
463 if !self.complete() || self.emitted {
464 return None;
465 }
466 let table_sets = (self.meta.first_table_id..=self.meta.last_table_id)
467 .map(|table_id| {
468 self.table_sets
469 .get(&table_id)
470 .expect("complete EIT schedule has no missing table IDs")
471 .clone()
472 })
473 .collect();
474 Some(CompleteEitSchedule {
475 first_table_id: self.meta.first_table_id,
476 last_table_id: self.meta.last_table_id,
477 table_sets,
478 })
479 }
480}
481
482#[derive(Debug, Clone)]
485pub struct CompleteEitSchedule {
486 first_table_id: u8,
487 last_table_id: u8,
488 table_sets: Vec<CompleteSectionSet>,
489}
490
491impl CompleteEitSchedule {
492 #[must_use]
494 pub const fn first_table_id(&self) -> u8 {
495 self.first_table_id
496 }
497
498 #[must_use]
500 pub const fn last_table_id(&self) -> u8 {
501 self.last_table_id
502 }
503
504 #[must_use]
506 pub fn table_sets(&self) -> &[CompleteSectionSet] {
507 &self.table_sets
508 }
509
510 pub fn table_versions(&self) -> impl ExactSizeIterator<Item = (u8, u8)> + '_ {
515 self.table_sets
516 .iter()
517 .map(|set| (set.meta().key.table_id, set.meta().version_number))
518 }
519
520 pub fn tables(&self) -> crate::Result<Vec<CompleteEit<'_>>> {
522 self.tables_with_registry(None)
523 }
524
525 pub fn tables_with_registry<'a>(
528 &'a self,
529 registry: Option<&'a DescriptorRegistry>,
530 ) -> crate::Result<Vec<CompleteEit<'a>>> {
531 self.table_sets
532 .iter()
533 .map(|set| CompleteEit::parse(set, registry))
534 .collect()
535 }
536}
537
538#[derive(Debug)]
540#[non_exhaustive]
541pub struct CompleteEitEvent<'a> {
542 pub event_id: u16,
544 pub start_time_raw: [u8; 5],
546 pub duration_raw: [u8; 3],
548 pub running_status: RunningStatus,
550 pub free_ca_mode: bool,
552 pub descriptors: ParsedDescriptorLoop<'a>,
554}
555
556impl CompleteEitEvent<'_> {
557 #[must_use]
561 pub fn duration(&self) -> Option<core::time::Duration> {
562 broadcast_common::time::decode_bcd_duration(self.duration_raw)
563 }
564
565 #[cfg(feature = "chrono")]
570 #[must_use]
571 pub fn start_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
572 broadcast_common::time::decode_mjd_bcd_utc(self.start_time_raw)
573 }
574}
575
576#[derive(Debug)]
582#[non_exhaustive]
583pub struct CompleteEit<'a> {
584 pub kind: eit::EitKind,
586 pub table_id: u8,
588 pub service_id: u16,
590 pub version_number: u8,
592 pub current_next_indicator: bool,
594 pub transport_stream_id: u16,
596 pub original_network_id: u16,
598 pub segment_last_section_number: u8,
600 pub last_table_id: u8,
602 pub events: Vec<CompleteEitEvent<'a>>,
604}
605
606impl<'a> CompleteEit<'a> {
607 pub(crate) fn parse(
608 set: &'a CompleteSectionSet,
609 registry: Option<&'a DescriptorRegistry>,
610 ) -> crate::Result<Self> {
611 let sections: Vec<eit::EitSection<'a>> = set.parse_sections()?;
612 let first = sections.first().ok_or(crate::Error::BufferTooShort {
613 need: 1,
614 have: 0,
615 what: "CompleteEit sections",
616 })?;
617 let mut events = Vec::new();
618 for section in §ions {
619 events.extend(section.events.iter().map(|event| CompleteEitEvent {
620 event_id: event.event_id,
621 start_time_raw: event.start_time_raw,
622 duration_raw: event.duration_raw,
623 running_status: event.running_status,
624 free_ca_mode: event.free_ca_mode,
625 descriptors: ParsedDescriptorLoop::parse(event.descriptors, registry),
626 }));
627 }
628 Ok(Self {
629 kind: first.kind,
630 table_id: first.table_id,
631 service_id: first.service_id,
632 version_number: first.version_number,
633 current_next_indicator: first.current_next_indicator,
634 transport_stream_id: first.transport_stream_id,
635 original_network_id: first.original_network_id,
636 segment_last_section_number: first.segment_last_section_number,
637 last_table_id: first.last_table_id,
638 events,
639 })
640 }
641}