Skip to main content

dvb_subtitle/
any.rs

1//! Unified segment dispatch: [`AnySegment`] + segment parsing.
2//!
3//! [`AnySegment`] is generated from a single declarative list
4//! (`declare_segments!`) — one line per crate-implemented segment type.
5//! The list is the single source of truth: it produces the enum, the
6//! `From<T>` conversions, and the segment_type → type dispatcher, and
7//! a drift test pins each segment_type literal to the type's
8//! [`crate::traits::SegmentDef::SEGMENT_TYPE`].
9
10use broadcast_common::{Parse, Serialize};
11
12/// Declares [`AnySegment`] + its dispatcher from one segment type list.
13///
14/// Each line is `Variant = 0xSEGMENT_TYPE => module::Type[<'a>]`.
15macro_rules! declare_segments {
16    (
17        $lt:lifetime;
18        $( $variant:ident = $seg_type:literal => $($path:ident)::+ $(<$plt:lifetime>)? ),+ $(,)?
19    ) => {
20        /// Every crate-implemented segment type, plus an `Unknown` fallthrough.
21        ///
22        /// serde uses external tagging with camelCase variant keys.
23        #[derive(Debug, Clone, PartialEq, Eq)]
24        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
25        #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
26        #[non_exhaustive]
27        pub enum AnySegment<$lt> {
28            $(
29                #[allow(missing_docs)]
30                $variant($($path)::+ $(<$plt>)?),
31            )+
32            /// Segment with an unrecognized or unsupported segment_type; `data` is the
33            /// segment body sans the 6-byte header (4 bytes of generic segment framing).
34            Unknown {
35                /// The raw segment_type byte.
36                segment_type: u8,
37                /// The page_id from the segment header.
38                page_id: u16,
39                /// The segment body bytes (starting after the 4-byte sync_byte+segment_type+page_id+segment_length header).
40                data: &$lt [u8],
41            },
42        }
43
44        $(
45            impl<$lt> From<$($path)::+ $(<$plt>)?> for AnySegment<$lt> {
46                fn from(s: $($path)::+ $(<$plt>)?) -> Self {
47                    Self::$variant(s)
48                }
49            }
50        )+
51
52        impl<$lt> AnySegment<$lt> {
53            /// Every segment_type the generated dispatcher routes.
54            pub const DISPATCHED_SEGMENT_TYPES: &'static [u8] = &[$($seg_type),+];
55
56            /// Diagnostic name of the contained segment — the type's
57            /// [`SegmentDef::NAME`](crate::traits::SegmentDef::NAME)
58            /// (`"PAGE_COMPOSITION"`, `"OBJECT_DATA"`, …); `"UNKNOWN"` for
59            /// [`AnySegment::Unknown`].
60            #[must_use]
61            pub fn name(&self) -> &'static str {
62                match self {
63                    $(
64                        Self::$variant(_) =>
65                            <$($path)::+ as crate::traits::SegmentDef>::NAME,
66                    )+
67                    Self::Unknown { .. } => "UNKNOWN",
68                }
69            }
70
71            /// Parse one full segment (including 6-byte generic header) by its segment_type.
72            ///
73            /// `None` means no typed implementation exists for `segment_type` (the
74            /// caller turns that into [`AnySegment::Unknown`]). `Some(Err)`
75            /// is a typed parse failure for a recognized segment_type.
76            pub(crate) fn dispatch(segment_type: u8, full: &$lt [u8]) -> Option<crate::Result<Self>> {
77                match segment_type {
78                    $(
79                        $seg_type => Some(<$($path)::+>::parse(full).map(Self::$variant)),
80                    )+
81                    _ => None,
82                }
83            }
84
85            pub(crate) fn serialized_len(&self) -> usize {
86                match self {
87                    $(
88                        Self::$variant(s) => s.serialized_len(),
89                    )+
90                    Self::Unknown { data, .. } => (6 + data.len()),
91                }
92            }
93
94            pub(crate) fn serialize_into(&self, buf: &mut [u8]) -> crate::Result<usize> {
95                match self {
96                    $(
97                        Self::$variant(s) => s.serialize_into(buf),
98                    )+
99                    Self::Unknown { segment_type, page_id, data } => {
100                        let len = 6 + data.len();
101                        if buf.len() < len {
102                            return Err(crate::error::Error::BufferTooShort {
103                                need: len,
104                                have: buf.len(),
105                                what: "Unknown segment serialize",
106                            });
107                        }
108                        buf[0] = 0x0F;
109                        buf[1] = *segment_type;
110                        buf[2..4].copy_from_slice(&page_id.to_be_bytes());
111                        let seg_len = data.len() as u16;
112                        buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
113                        buf[6..len].copy_from_slice(data);
114                        Ok(len)
115                    }
116                }
117            }
118        }
119
120        #[cfg(test)]
121        mod macro_drift {
122            #[test]
123            fn segment_type_literals_match_segment_def() {
124                use crate::traits::SegmentDef;
125                $(
126                    assert_eq!(
127                        $seg_type,
128                        <$($path)::+ as SegmentDef>::SEGMENT_TYPE,
129                        concat!("segment_type literal drift for ", stringify!($variant)),
130                    );
131                    assert!(
132                        !<$($path)::+ as SegmentDef>::NAME.is_empty(),
133                        concat!("empty NAME for ", stringify!($variant)),
134                    );
135                )+
136            }
137        }
138    };
139}
140
141declare_segments! {'a;
142    PageComposition = 0x10 => crate::segments::page_composition::PageCompositionSegment,
143    RegionComposition = 0x11 => crate::segments::region_composition::RegionCompositionSegment,
144    ClutDefinition = 0x12 => crate::segments::clut_definition::ClutDefinitionSegment,
145    ObjectData = 0x13 => crate::segments::object_data::ObjectDataSegment<'a>,
146    DisplayDefinition = 0x14 => crate::segments::display_definition::DisplayDefinitionSegment,
147    DisparitySignalling = 0x15 => crate::segments::disparity_signalling::DisparitySignallingSegment,
148    AlternativeClut = 0x16 => crate::segments::alternative_clut::AlternativeClutSegment,
149    EndOfDisplaySet = 0x80 => crate::segments::end_of_display_set::EndOfDisplaySetSegment,
150    Stuffing = 0xFF => crate::segments::stuffing::StuffingSegment<'a>,
151}