Skip to main content

dvb_si/tables/
any.rs

1//! Unified table-section dispatch: [`AnyTableSection`].
2//!
3//! [`AnyTableSection`] is generated from a single declarative list
4//! (`declare_tables!`) — one line per crate-implemented section type.
5//! The list is the single source of truth: it produces the enum, the
6//! `From<T>` conversions, the table_id range dispatcher, and a drift test
7//! that pins each range literal to the type's
8//! [`crate::traits::TableDef::TABLE_ID_RANGES`].
9//!
10//! [`AnyTableSection::parse`] dispatches on the first byte (table_id) using range
11//! patterns. An unrecognised table_id yields
12//! `AnyTableSection::Unknown { table_id, raw }` — the full section bytes are
13//! retained.
14//!
15//! [`AnyTableSection::parse_as`] is a type-keyed thin alias to `T::parse` — it
16//! bypasses dispatch entirely and lets callers obtain, for example, a
17//! [`crate::tables::mpe::MpeDatagramSection`] for a `0x3E` section that the
18//! default dispatcher would route to `DsmccSection`.
19//!
20//! ```
21//! use broadcast_common::Serialize;
22//! use dvb_si::tables::AnyTableSection;
23//! use mpeg_ts::pid::Pid;
24//! use dvb_si::tables::pat::{PatSection, PatEntry};
25//!
26//! // Serialize a small PAT, then dispatch the bytes back through AnyTableSection::parse.
27//! let pat = PatSection::new(1, 0, true, 0, 0, vec![PatEntry::new(1, Pid::new(0x0100))]);
28//! let mut section = vec![0u8; pat.serialized_len()];
29//! pat.serialize_into(&mut section).unwrap();
30//!
31//! match AnyTableSection::parse(&section).unwrap() {
32//!     AnyTableSection::PatSection(parsed) => assert_eq!(parsed.entries[0].pid, Pid::new(0x0100)),
33//!     other => panic!("expected PatSection, got {other:?}"),
34//! }
35//! ```
36//!
37//! # Adding a section parser
38//!
39//! 1. Create the module with the wire layout, a `pub const TABLE_ID: u8` (or
40//!    `TABLE_ID_FIRST`/`TABLE_ID_LAST` for a range), and the symmetric
41//!    [`broadcast_common::Parse`]/[`broadcast_common::Serialize`] impls + round-trip tests
42//!    (copy an existing module).
43//! 2. `impl TableDef` for the type (`TABLE_ID_RANGES` covering the full
44//!    table_id range, `NAME` in SCREAMING_SNAKE without the `_section` or
45//!    `_table` suffix).
46//! 3. Add one line to the `declare_tables!` invocation below — the enum
47//!    variant, dispatcher arm, and drift test entry are generated from it.
48//!    If the type should NOT be auto-dispatched (e.g. `MpeDatagramSection`,
49//!    whose `0x3E` id is claimed by the `DsmccSection` range), add it to the
50//!    `@no_dispatch` section instead.
51//! 4. The disjointness test in `declare_tables_tests` catches any overlapping
52//!    range entries automatically — no manual test edits needed.
53
54use alloc::boxed::Box;
55
56/// Declares [`AnyTableSection`] + its dispatcher from one range list.
57///
58/// Each dispatch line is `Variant = [lo..=hi, …] => module::Type[<'a>]`.
59/// The optional trailing `@no_dispatch …` section adds variants that are NOT
60/// reachable from the generated dispatcher — the variant exists for callers
61/// that obtain the type via `AnyTableSection::parse_as` or direct `T::parse`.
62macro_rules! declare_tables {
63    (
64        $lt:lifetime;
65        $( $variant:ident = [ $( $lo:literal ..= $hi:literal ),+ ] => $($path:ident)::+ $(<$plt:lifetime>)? ),+ $(,)?
66        $( ; @no_dispatch $( $nd_variant:ident => $($nd_path:ident)::+ $(<$nd_plt:lifetime>)? ),+ $(,)? )?
67    ) => {
68        /// Every crate-implemented table-section parser, plus an `Unknown`
69        /// fallthrough.
70        ///
71        /// serde uses external tagging with camelCase variant keys — a parsed
72        /// PAT section serializes as `{"patSection": {…}}`.
73        /// Variant names map 1:1 to section parser types; see each module for
74        /// the wire layout.
75        ///
76        /// `0x3E` (`datagram_section`) is routed to `DsmccSection` by the
77        /// default dispatcher. The typed MPE view is reachable via
78        /// `AnyTableSection::parse_as::<MpeDatagramSection>` or
79        /// `MpeDatagramSection::parse` directly; the `MpeDatagram` variant
80        /// exists in this enum for API completeness but is never produced by
81        /// `AnyTableSection::parse`.
82        #[derive(Debug)]
83        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
84        #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
85        // Covariant in `$lt`: every variant holds only lifetime-parametrised
86        // table views or `&$lt [u8]` (`Unknown`), so the derived impl is sound.
87        #[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
88        #[non_exhaustive]
89        pub enum AnyTableSection<$lt> {
90            $(
91                #[allow(missing_docs)]
92                $variant($($path)::+ $(<$plt>)?),
93            )+
94            $($(
95                #[allow(missing_docs)]
96                $nd_variant($($nd_path)::+ $(<$nd_plt>)?),
97            )+)?
98            /// Runtime-registered custom table section (see [`TableRegistry`]).
99            ///
100            /// [`TableRegistry`]: crate::tables::registry::TableRegistry
101            Other {
102                /// The raw table_id byte.
103                table_id: u8,
104                /// The parsed, type-erased table-section value. Call `downcast_ref`
105                /// on it (see [`TableObject`](crate::tables::registry::TableObject))
106                /// to recover the concrete type.
107                #[cfg_attr(
108                    feature = "serde",
109                    serde(serialize_with = "crate::tables::registry::serialize_erased")
110                )]
111                value: Box<dyn crate::tables::registry::TableObject>,
112            },
113            /// table_id with no typed implementation; `raw` is the full
114            /// section bytes including the table_id header.
115            Unknown {
116                /// The raw table_id byte.
117                table_id: u8,
118                /// The raw section bytes (full, header included).
119                raw: &$lt [u8],
120            },
121        }
122
123        $(
124            impl<$lt> From<$($path)::+ $(<$plt>)?> for AnyTableSection<$lt> {
125                fn from(t: $($path)::+ $(<$plt>)?) -> Self {
126                    Self::$variant(t)
127                }
128            }
129        )+
130        $($(
131            impl<$lt> From<$($nd_path)::+ $(<$nd_plt>)?> for AnyTableSection<$lt> {
132                fn from(t: $($nd_path)::+ $(<$nd_plt>)?) -> Self {
133                    Self::$nd_variant(t)
134                }
135            }
136        )+)?
137
138        impl<$lt> AnyTableSection<$lt> {
139            /// All table_id ranges covered by the auto-dispatcher (excludes
140            /// `@no_dispatch` variants). Each entry is `(lo, hi)` inclusive.
141            pub const DISPATCHED_RANGES: &'static [(u8, u8)] =
142                &[$( $( ($lo, $hi) ),+ ),+];
143
144            /// Diagnostic name of the contained table — the type's
145            /// [`TableDef::NAME`](crate::traits::TableDef::NAME)
146            /// (`"EVENT_INFORMATION"`, `"PROGRAM_ASSOCIATION"`, …);
147            /// `"UNKNOWN"` for [`AnyTableSection::Unknown`].
148            #[must_use]
149            pub fn name(&self) -> &'static str {
150                match self {
151                    $(
152                        Self::$variant(_) =>
153                            <$($path)::+ as crate::traits::TableDef>::NAME,
154                    )+
155                    $($(
156                        Self::$nd_variant(_) =>
157                            <$($nd_path)::+ as crate::traits::TableDef>::NAME,
158                    )+)?
159                    Self::Other { .. } => "CUSTOM",
160                    Self::Unknown { .. } => "UNKNOWN",
161                }
162            }
163
164            /// Dispatch one complete section by its table_id (byte 0).
165            ///
166            /// Returns `Err(BufferTooShort)` when `bytes` is empty.
167            /// Unknown table_ids produce `Ok(AnyTableSection::Unknown { … })`.
168            ///
169            /// # Errors
170            /// - [`crate::Error::BufferTooShort`] — `bytes` is empty.
171            /// - Any parse error from the dispatched type.
172            pub fn parse(bytes: &$lt [u8]) -> crate::Result<Self> {
173                let table_id = *bytes.first().ok_or(crate::Error::BufferTooShort(broadcast_common::error::BufferTooShort {
174                    need: 1,
175                    have: 0,
176                    what: "section table_id",
177                }))?;
178                match table_id {
179                    $(
180                        $( $lo..=$hi )|+ => {
181                            <$($path)::+ as broadcast_common::Parse>::parse(bytes).map(Self::$variant)
182                        }
183                    )+
184                    _ => Ok(Self::Unknown { table_id, raw: bytes }),
185                }
186            }
187
188            /// Type-keyed parse: bypass the dispatcher and parse `bytes`
189            /// directly as `T`. Useful for types excluded from the default
190            /// dispatch, e.g.:
191            ///
192            /// ```rust
193            /// use dvb_si::tables::AnyTableSection;
194            /// use dvb_si::tables::mpe::MpeDatagramSection;
195            ///
196            /// // A deliberately-too-short slice: parse_as propagates the
197            /// // BufferTooShort error from MpeDatagramSection::parse.
198            /// let err = AnyTableSection::parse_as::<MpeDatagramSection>(&[0x3E, 0x00]);
199            /// assert_eq!(
200            ///     err.unwrap_err(),
201            ///     dvb_si::Error::BufferTooShort(broadcast_common::error::BufferTooShort { need: 16, have: 2, what: "MpeDatagramSection" })
202            /// );
203            /// ```
204            ///
205            /// # Errors
206            /// Propagates `T::parse` errors.
207            pub fn parse_as<T>(bytes: &$lt [u8]) -> crate::Result<T>
208            where
209                T: crate::traits::TableDef<$lt>,
210            {
211                <T as broadcast_common::Parse>::parse(bytes)
212            }
213        }
214
215        impl<$lt> broadcast_common::Serialize for AnyTableSection<$lt> {
216            type Error = crate::Error;
217
218            /// Length of the contained section; an [`AnyTableSection::Other`]
219            /// (type-erased custom section) has no serializer and reports 0.
220            fn serialized_len(&self) -> usize {
221                match self {
222                    $( Self::$variant(t) => broadcast_common::Serialize::serialized_len(t), )+
223                    $($( Self::$nd_variant(t) => broadcast_common::Serialize::serialized_len(t), )+)?
224                    Self::Other { .. } => 0,
225                    Self::Unknown { raw, .. } => raw.len(),
226                }
227            }
228
229            /// Serialize the contained section; `Unknown` copies its raw bytes.
230            ///
231            /// # Errors
232            /// - [`crate::Error::OutputBufferTooSmall`] — `buf` is too short.
233            /// - [`crate::Error::ValueOutOfRange`] — `Other` (a type-erased custom
234            ///   section carries no serializer).
235            /// - Any serialize error from the contained type.
236            fn serialize_into(&self, buf: &mut [u8]) -> crate::Result<usize> {
237                match self {
238                    $( Self::$variant(t) => broadcast_common::Serialize::serialize_into(t, buf), )+
239                    $($( Self::$nd_variant(t) => broadcast_common::Serialize::serialize_into(t, buf), )+)?
240                    Self::Other { .. } => Err(crate::Error::ValueOutOfRange {
241                        field: "AnyTableSection::Other",
242                        reason: "type-erased custom table section has no serializer",
243                    }),
244                    Self::Unknown { raw, .. } => {
245                        let have = buf.len();
246                        let dst = buf.get_mut(..raw.len()).ok_or(
247                            broadcast_common::error::OutputBufferTooSmall {
248                                need: raw.len(),
249                                have,
250                            },
251                        )?;
252                        dst.copy_from_slice(raw);
253                        Ok(raw.len())
254                    }
255                }
256            }
257        }
258
259        #[cfg(test)]
260        mod macro_drift {
261            #[test]
262            fn ranges_match_tabledef() {
263                use crate::traits::TableDef;
264                $(
265                    assert_eq!(
266                        &[ $( ($lo, $hi) ),+ ][..],
267                        <$($path)::+ as TableDef>::TABLE_ID_RANGES,
268                        concat!("TABLE_ID_RANGES drift for ", stringify!($variant)),
269                    );
270                    assert!(
271                        !<$($path)::+ as TableDef>::NAME.is_empty(),
272                        concat!("empty NAME for ", stringify!($variant)),
273                    );
274                )+
275                $($(
276                    assert!(
277                        !<$($nd_path)::+ as TableDef>::NAME.is_empty(),
278                        concat!("empty NAME for no-dispatch ", stringify!($nd_variant)),
279                    );
280                )+)?
281            }
282
283            #[test]
284            fn dispatched_ranges_are_disjoint() {
285                // Collect all (lo, hi) pairs, sort by lo, then check no
286                // two adjacent entries overlap.
287                let mut ranges: Vec<(u8, u8)> = vec![
288                    $( $( ($lo, $hi), )+ )+
289                ];
290                ranges.sort_by_key(|r| r.0);
291                for w in ranges.windows(2) {
292                    let (_, prev_hi) = w[0];
293                    let (next_lo, _) = w[1];
294                    assert!(
295                        next_lo > prev_hi,
296                        "overlapping dispatch ranges: {w:?}",
297                    );
298                }
299            }
300        }
301    };
302}
303
304declare_tables! {'a;
305    // MPEG-2 systems tables (ISO/IEC 13818-1).
306    PatSection       = [0x00..=0x00] => crate::tables::pat::PatSection,
307    CatSection       = [0x01..=0x01] => crate::tables::cat::CatSection<'a>,
308    PmtSection       = [0x02..=0x02] => crate::tables::pmt::PmtSection<'a>,
309    TsdtSection      = [0x03..=0x03] => crate::tables::tsdt::TsdtSection<'a>,
310    // Carriage of ISO/IEC 14496, metadata and green access units (H.222.0 Tables 2-153, 2-158, 2-162).
311    Iso14496Section = [0x04..=0x05, 0x08..=0x08] => crate::tables::iso14496_section::Iso14496Section<'a>,
312    MetadataSection = [0x06..=0x06] => crate::tables::metadata_section::MetadataSection<'a>,
313    GreenAccessUnitSection = [0x09..=0x09] => crate::tables::green_access_unit_section::GreenAccessUnitSection<'a>,
314    // DSM-CC sections (ISO/IEC 13818-6) — 0x3E is included; the MPE typed
315    // view (`MpeDatagramSection`) is reachable via `AnyTableSection::parse_as` or
316    // `MpeDatagramSection::parse`.
317    DsmccSection = [0x3A..=0x3F] => crate::tables::dsmcc::DsmccSection<'a>,
318    // DVB tables (ETSI EN 300 468).
319    NitSection       = [0x40..=0x41] => crate::tables::nit::NitSection<'a>,
320    SdtSection       = [0x42..=0x42, 0x46..=0x46] => crate::tables::sdt::SdtSection<'a>,
321    BatSection       = [0x4A..=0x4A] => crate::tables::bat::BatSection<'a>,
322    UntSection       = [0x4B..=0x4B] => crate::tables::unt::UntSection<'a>,
323    IntSection       = [0x4C..=0x4C] => crate::tables::int::IntSection<'a>,
324    SatSection       = [0x4D..=0x4D] => crate::tables::sat::SatSection,
325    EitSection       = [0x4E..=0x6F] => crate::tables::eit::EitSection<'a>,
326    TdtSection       = [0x70..=0x70] => crate::tables::tdt::TdtSection,
327    RstSection       = [0x71..=0x71] => crate::tables::rst::RstSection,
328    StSection        = [0x72..=0x72] => crate::tables::st::StSection,
329    TotSection       = [0x73..=0x73] => crate::tables::tot::TotSection<'a>,
330    AitSection       = [0x74..=0x74] => crate::tables::ait::AitSection<'a>,
331    ContainerSection = [0x75..=0x75] => crate::tables::container::ContainerSection<'a>,
332    RctSection       = [0x76..=0x76] => crate::tables::rct::RctSection<'a>,
333    CitSection       = [0x77..=0x77] => crate::tables::cit::CitSection<'a>,
334    MpeFecSection    = [0x78..=0x78] => crate::tables::mpe_fec::MpeFecSection<'a>,
335    RntSection       = [0x79..=0x79] => crate::tables::rnt::RntSection<'a>,
336    MpeIfecSection   = [0x7A..=0x7A] => crate::tables::mpe_ifec::MpeIfecSection<'a>,
337    ProtectionMessage    = [0x7B..=0x7B] => crate::tables::protection_message::ProtectionMessageSection<'a>,
338    DownloadableFontInfo = [0x7C..=0x7C] => crate::tables::downloadable_font_info::DownloadableFontInfoSection<'a>,
339    DitSection       = [0x7E..=0x7E] => crate::tables::dit::DitSection,
340    SitSection       = [0x7F..=0x7F] => crate::tables::sit::SitSection<'a>;
341    // MPE datagram_section (ETSI EN 301 192 §7.1): table_id 0x3E overlaps
342    // the DsmccSection range above, so it is NOT auto-dispatched. Use
343    // `AnyTableSection::parse_as::<MpeDatagramSection>(bytes)` for the typed view.
344    @no_dispatch
345    MpeDatagram => crate::tables::mpe::MpeDatagramSection<'a>,
346}
347
348impl<'a> AnyTableSection<'a> {
349    /// Dispatch one complete section with custom-registry support.
350    ///
351    /// Precedence: (1) if the registry has a custom parser for the section's
352    /// table_id, use it → [`AnyTableSection::Other`]; (2) else delegate to
353    /// [`AnyTableSection::parse`] (built-in dispatch, which itself falls to
354    /// [`AnyTableSection::Unknown`]).
355    ///
356    /// # Errors
357    /// - [`crate::Error::BufferTooShort`] — `bytes` is empty.
358    /// - Any parse error from the dispatched type.
359    pub fn parse_with(
360        registry: &crate::tables::registry::TableRegistry,
361        bytes: &'a [u8],
362    ) -> crate::Result<Self> {
363        let table_id = *bytes.first().ok_or(crate::Error::BufferTooShort(
364            broadcast_common::error::BufferTooShort {
365                need: 1,
366                have: 0,
367                what: "section table_id",
368            },
369        ))?;
370        if let Some(parse_fn) = registry.lookup(table_id) {
371            let value = parse_fn(bytes)?;
372            return Ok(Self::Other { table_id, value });
373        }
374        Self::parse(bytes)
375    }
376}