1use alloc::collections::BTreeMap;
16use alloc::sync::Arc;
17use alloc::vec;
18use alloc::vec::Vec;
19
20use crate::descriptors::{AnyDescriptor, DescriptorLoop, DescriptorRegistry};
21use crate::section::Section;
22use dvb_common::Parse;
23
24mod bat;
25mod eit;
26mod nit;
27mod sdt;
28
29pub use bat::*;
30pub use eit::*;
31pub use nit::*;
32pub use sdt::*;
33
34pub const DEFAULT_MAX_PARTIAL_KEYS: usize = 256;
43
44pub type CollectResult<T> = core::result::Result<T, CollectError>;
46
47#[derive(Debug, thiserror::Error)]
53#[non_exhaustive]
54pub enum CollectError {
55 #[error("section parse failed: {0}")]
57 Section(#[from] crate::Error),
58
59 #[error(
61 "table_id {table_id:#04x} is a short-form section and cannot be multi-section collected"
62 )]
63 ShortFormSection {
64 table_id: u8,
66 },
67
68 #[error(
70 "section_number {section_number} exceeds last_section_number {last_section_number} for table_id {table_id:#04x}"
71 )]
72 SectionNumberOutOfRange {
73 table_id: u8,
75 section_number: u8,
77 last_section_number: u8,
79 },
80
81 #[error("conflicting bytes for table_id {table_id:#04x} section {section_number}")]
83 ConflictingSection {
84 table_id: u8,
86 section_number: u8,
88 },
89
90 #[error(
92 "EIT schedule table_id {table_id:#04x} is outside advertised range {first_table_id:#04x}..={last_table_id:#04x}"
93 )]
94 EitTableIdOutOfRange {
95 table_id: u8,
97 first_table_id: u8,
99 last_table_id: u8,
101 },
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
109#[non_exhaustive]
110pub struct SectionSetKey {
111 pub pid: Option<u16>,
113 pub table_id: u8,
115 pub extension_id: u16,
117 pub current_next_indicator: bool,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123#[non_exhaustive]
124pub struct SectionSetMeta {
125 pub key: SectionSetKey,
127 pub version_number: u8,
129 pub last_section_number: u8,
131}
132
133#[derive(Debug)]
134struct PartialSectionSet {
135 meta: SectionSetMeta,
136 slots: Vec<Option<Arc<[u8]>>>,
137 filled: usize,
138 emitted: bool,
139}
140
141impl PartialSectionSet {
142 fn new(meta: SectionSetMeta) -> Self {
143 let len = meta.last_section_number as usize + 1;
144 Self {
145 meta,
146 slots: vec![None; len],
147 filled: 0,
148 emitted: false,
149 }
150 }
151
152 fn reset(&mut self, meta: SectionSetMeta) {
153 *self = Self::new(meta);
154 }
155
156 fn insert(&mut self, section_number: u8, bytes: Arc<[u8]>) -> CollectResult<bool> {
157 let index = section_number as usize;
158 if let Some(existing) = &self.slots[index] {
159 if existing.as_ref() == bytes.as_ref() {
160 return Ok(false);
161 }
162 return Err(CollectError::ConflictingSection {
163 table_id: self.meta.key.table_id,
164 section_number,
165 });
166 }
167
168 self.slots[index] = Some(bytes);
169 self.filled += 1;
170 self.emitted = false;
171 Ok(true)
172 }
173
174 fn complete(&self) -> bool {
175 self.filled == self.slots.len()
176 }
177
178 fn to_complete(&self) -> Option<CompleteSectionSet> {
179 if !self.complete() || self.emitted {
180 return None;
181 }
182
183 let sections = self
184 .slots
185 .iter()
186 .map(|slot| slot.as_ref().expect("complete set has no holes").clone())
187 .collect();
188 Some(CompleteSectionSet {
189 meta: self.meta,
190 sections,
191 })
192 }
193}
194
195#[derive(Debug)]
202pub struct SectionSetCollector {
203 partial: BTreeMap<SectionSetKey, PartialSectionSet>,
204 max_partial_keys: usize,
205}
206
207impl Default for SectionSetCollector {
208 fn default() -> Self {
209 Self {
210 partial: BTreeMap::new(),
211 max_partial_keys: DEFAULT_MAX_PARTIAL_KEYS,
212 }
213 }
214}
215
216impl SectionSetCollector {
217 #[must_use]
220 pub fn new() -> Self {
221 Self::default()
222 }
223
224 #[must_use]
228 pub fn with_max_partial_keys(mut self, max_partial_keys: usize) -> Self {
229 self.max_partial_keys = max_partial_keys;
230 self
231 }
232
233 pub fn push_section(
243 &mut self,
244 bytes: impl AsRef<[u8]>,
245 ) -> CollectResult<Option<CompleteSectionSet>> {
246 self.push_section_with_pid(None, bytes)
247 }
248
249 pub fn push_section_with_pid(
254 &mut self,
255 pid: Option<u16>,
256 bytes: impl AsRef<[u8]>,
257 ) -> CollectResult<Option<CompleteSectionSet>> {
258 let raw = bytes.as_ref();
259 let section = Section::parse(raw)?;
260 if !section.section_syntax_indicator {
261 return Err(CollectError::ShortFormSection {
262 table_id: section.table_id,
263 });
264 }
265 if section.section_number > section.last_section_number {
266 return Err(CollectError::SectionNumberOutOfRange {
267 table_id: section.table_id,
268 section_number: section.section_number,
269 last_section_number: section.last_section_number,
270 });
271 }
272 section.validate_crc(raw)?;
273
274 let key = SectionSetKey {
275 pid,
276 table_id: section.table_id,
277 extension_id: section.extension_id,
278 current_next_indicator: section.current_next_indicator,
279 };
280 let meta = SectionSetMeta {
281 key,
282 version_number: section.version_number,
283 last_section_number: section.last_section_number,
284 };
285 let bytes: Arc<[u8]> = Arc::from(raw);
286
287 if !self.partial.contains_key(&key) && self.partial.len() >= self.max_partial_keys {
289 return Ok(None);
290 }
291
292 let partial = self
293 .partial
294 .entry(key)
295 .or_insert_with(|| PartialSectionSet::new(meta));
296
297 if partial.meta.version_number != meta.version_number
298 || partial.meta.last_section_number != meta.last_section_number
299 {
300 partial.reset(meta);
301 }
302
303 partial.insert(section.section_number, bytes)?;
304 let complete = partial.to_complete();
305 if complete.is_some() {
306 partial.emitted = true;
307 }
308 Ok(complete)
309 }
310
311 pub fn clear(&mut self) {
313 self.partial.clear();
314 }
315
316 #[must_use]
318 pub fn len(&self) -> usize {
319 self.partial.len()
320 }
321
322 #[must_use]
324 pub fn is_empty(&self) -> bool {
325 self.partial.is_empty()
326 }
327}
328
329#[derive(Debug, Clone)]
332pub struct CompleteSectionSet {
333 meta: SectionSetMeta,
334 sections: Vec<Arc<[u8]>>,
335}
336
337#[derive(Debug)]
345pub struct CompleteTable<T> {
346 meta: SectionSetMeta,
347 sections: Vec<T>,
348}
349
350impl<T> CompleteTable<T> {
351 #[must_use]
353 pub const fn meta(&self) -> SectionSetMeta {
354 self.meta
355 }
356
357 #[must_use]
359 pub fn sections(&self) -> &[T] {
360 &self.sections
361 }
362
363 #[must_use]
365 pub fn into_sections(self) -> Vec<T> {
366 self.sections
367 }
368}
369
370impl CompleteSectionSet {
371 #[must_use]
373 pub const fn meta(&self) -> SectionSetMeta {
374 self.meta
375 }
376
377 #[must_use]
379 pub fn section_bytes(&self) -> impl ExactSizeIterator<Item = &[u8]> {
380 self.sections.iter().map(AsRef::as_ref)
381 }
382
383 pub fn parse_sections<'a, T>(&'a self) -> crate::Result<Vec<T>>
388 where
389 T: Parse<'a, Error = crate::Error>,
390 {
391 self.section_bytes().map(T::parse).collect()
392 }
393
394 pub fn table<'a, T>(&'a self) -> crate::Result<CompleteTable<T>>
399 where
400 T: Parse<'a, Error = crate::Error>,
401 {
402 Ok(CompleteTable {
403 meta: self.meta,
404 sections: self.parse_sections()?,
405 })
406 }
407
408 pub fn nit(&self) -> crate::Result<CompleteNit<'_>> {
410 CompleteNit::parse(self, None)
411 }
412
413 pub fn nit_with_registry<'a>(
415 &'a self,
416 registry: &'a DescriptorRegistry,
417 ) -> crate::Result<CompleteNit<'a>> {
418 CompleteNit::parse(self, Some(registry))
419 }
420
421 pub fn bat(&self) -> crate::Result<CompleteBat<'_>> {
423 CompleteBat::parse(self, None)
424 }
425
426 pub fn bat_with_registry<'a>(
428 &'a self,
429 registry: &'a DescriptorRegistry,
430 ) -> crate::Result<CompleteBat<'a>> {
431 CompleteBat::parse(self, Some(registry))
432 }
433
434 pub fn sdt(&self) -> crate::Result<CompleteSdt<'_>> {
436 CompleteSdt::parse(self, None)
437 }
438
439 pub fn sdt_with_registry<'a>(
441 &'a self,
442 registry: &'a DescriptorRegistry,
443 ) -> crate::Result<CompleteSdt<'a>> {
444 CompleteSdt::parse(self, Some(registry))
445 }
446
447 pub fn eit(&self) -> crate::Result<CompleteEit<'_>> {
449 CompleteEit::parse(self, None)
450 }
451
452 pub fn eit_with_registry<'a>(
454 &'a self,
455 registry: &'a DescriptorRegistry,
456 ) -> crate::Result<CompleteEit<'a>> {
457 CompleteEit::parse(self, Some(registry))
458 }
459}
460
461#[derive(Debug)]
464pub struct ParsedDescriptorLoop<'a> {
465 raw: DescriptorLoop<'a>,
466 descriptors: Vec<crate::Result<AnyDescriptor<'a>>>,
467}
468
469impl<'a> ParsedDescriptorLoop<'a> {
470 pub(crate) fn parse(raw: DescriptorLoop<'a>, registry: Option<&'a DescriptorRegistry>) -> Self {
471 let descriptors = match registry {
472 Some(registry) => registry.parse_loop(raw.raw()).collect(),
473 None => raw.iter().collect(),
474 };
475 Self { raw, descriptors }
476 }
477
478 #[must_use]
483 pub const fn raw(&self) -> DescriptorLoop<'a> {
484 self.raw
485 }
486
487 pub fn descriptors(&self) -> &[crate::Result<AnyDescriptor<'a>>] {
489 &self.descriptors
490 }
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496
497 const TEST_TABLE_ID: u8 = 0x42;
498
499 fn min_section(extension_id: u16) -> Vec<u8> {
500 let section_length: u16 = 9; let mut buf = vec![0u8; 12];
502 buf[0] = TEST_TABLE_ID;
503 buf[1] = 0xB0 | ((section_length >> 8) as u8 & 0x0F);
504 buf[2] = (section_length & 0xFF) as u8;
505 buf[3..5].copy_from_slice(&extension_id.to_be_bytes());
506 buf[5] = 0xC1;
507 buf[6] = 0;
508 buf[7] = 0;
509 let crc = dvb_common::crc32_mpeg2::compute(&buf[..8]);
510 buf[8..12].copy_from_slice(&crc.to_be_bytes());
511 buf
512 }
513
514 #[test]
515 fn collect_single_section_is_complete() {
516 let mut c = SectionSetCollector::new();
517 let sec = min_section(0);
518 let result = c.push_section(&sec).unwrap();
519 assert!(result.is_some());
520 assert_eq!(c.len(), 1);
521 }
522
523 #[test]
524 fn partial_keys_cap_skips_new_keys() {
525 let mut c = SectionSetCollector::new().with_max_partial_keys(3);
526
527 for eid in 0..3u16 {
529 let sec = min_section(eid);
530 let result = c.push_section(&sec).unwrap();
531 assert!(
532 result.is_some(),
533 "single-section set for eid {eid} completes"
534 );
535 }
536 assert_eq!(c.len(), 3);
537
538 let sec4 = min_section(3);
540 let result = c.push_section(&sec4).unwrap();
541 assert!(result.is_none(), "new key beyond cap must be skipped");
542 assert_eq!(c.len(), 3);
543
544 c.clear();
546 assert!(c.is_empty());
547 let result = c.push_section(&sec4).unwrap();
548 assert!(result.is_some());
549 assert_eq!(c.len(), 1);
550 }
551
552 #[test]
553 fn partial_keys_cap_does_not_skip_existing_key() {
554 let mut c = SectionSetCollector::new().with_max_partial_keys(1);
555
556 let sec0 = {
558 let mut buf = min_section(0xAB);
559 buf[7] = 1;
561 let crc = dvb_common::crc32_mpeg2::compute(&buf[..8]);
563 buf[8..12].copy_from_slice(&crc.to_be_bytes());
564 buf
565 };
566 let result = c.push_section(&sec0).unwrap();
567 assert!(result.is_none(), "incomplete section set yields None");
568
569 let mut sec1 = min_section(0xAB);
572 sec1[6] = 1; sec1[7] = 1; let crc = dvb_common::crc32_mpeg2::compute(&sec1[..8]);
575 sec1[8..12].copy_from_slice(&crc.to_be_bytes());
576
577 let result = c.push_section(&sec1).unwrap();
578 assert!(
579 result.is_some(),
580 "existing key must NOT be skipped when cap full"
581 );
582 assert_eq!(c.len(), 1);
583 }
584}