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                #[doc = concat!(stringify!($variant), " segment.")]
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            /// Segment with a *recognized* segment_type whose typed parse
43            /// returned `Err` (distinct from [`Self::Unknown`], whose
44            /// segment_type has no typed implementation at all — issue
45            /// #1108/DS-W1: a bug that makes every real instance of a typed
46            /// parser fail could otherwise hide as `Unknown` passthrough and
47            /// still round-trip byte-exact). Re-serializes the raw bytes
48            /// verbatim, same as `Unknown`.
49            Malformed {
50                /// The raw segment_type byte.
51                segment_type: u8,
52                /// The page_id from the segment header.
53                page_id: u16,
54                /// The segment body bytes (same slicing as `Unknown::data`).
55                data: &$lt [u8],
56                /// Why the typed parse failed.
57                #[cfg_attr(feature = "serde", serde(skip))]
58                err: crate::error::Error,
59            },
60        }
61
62        $(
63            impl<$lt> From<$($path)::+ $(<$plt>)?> for AnySegment<$lt> {
64                fn from(s: $($path)::+ $(<$plt>)?) -> Self {
65                    Self::$variant(s)
66                }
67            }
68        )+
69
70        impl<$lt> AnySegment<$lt> {
71            /// Every segment_type the generated dispatcher routes.
72            pub const DISPATCHED_SEGMENT_TYPES: &'static [u8] = &[$($seg_type),+];
73
74            /// Diagnostic name of the contained segment — the type's
75            /// [`SegmentDef::NAME`](crate::traits::SegmentDef::NAME)
76            /// (`"PAGE_COMPOSITION"`, `"OBJECT_DATA"`, …); `"UNKNOWN"` for
77            /// [`AnySegment::Unknown`], `"MALFORMED"` for
78            /// [`AnySegment::Malformed`].
79            #[must_use]
80            pub fn name(&self) -> &'static str {
81                match self {
82                    $(
83                        Self::$variant(_) =>
84                            <$($path)::+ as crate::traits::SegmentDef>::NAME,
85                    )+
86                    Self::Unknown { .. } => "UNKNOWN",
87                    Self::Malformed { .. } => "MALFORMED",
88                }
89            }
90
91            /// Parse one full segment (including 6-byte generic header) by its segment_type.
92            ///
93            /// `None` means no typed implementation exists for `segment_type` (the
94            /// caller turns that into [`AnySegment::Unknown`]). `Some(Err)`
95            /// is a typed parse failure for a recognized segment_type (the
96            /// caller turns that into [`AnySegment::Malformed`]).
97            pub(crate) fn dispatch(segment_type: u8, full: &$lt [u8]) -> Option<crate::Result<Self>> {
98                match segment_type {
99                    $(
100                        $seg_type => Some(<$($path)::+>::parse(full).map(Self::$variant)),
101                    )+
102                    _ => None,
103                }
104            }
105
106            pub(crate) fn serialized_len(&self) -> usize {
107                match self {
108                    $(
109                        Self::$variant(s) => s.serialized_len(),
110                    )+
111                    Self::Unknown { data, .. } => (6 + data.len()),
112                    Self::Malformed { data, .. } => (6 + data.len()),
113                }
114            }
115
116            pub(crate) fn serialize_into(&self, buf: &mut [u8]) -> crate::Result<usize> {
117                match self {
118                    $(
119                        Self::$variant(s) => s.serialize_into(buf),
120                    )+
121                    Self::Unknown { segment_type, page_id, data } => {
122                        Self::serialize_raw_segment(*segment_type, *page_id, data, buf)
123                    }
124                    Self::Malformed { segment_type, page_id, data, .. } => {
125                        Self::serialize_raw_segment(*segment_type, *page_id, data, buf)
126                    }
127                }
128            }
129
130            /// Shared raw re-serialization for `Unknown`/`Malformed`, both of
131            /// which round-trip their body bytes verbatim.
132            fn serialize_raw_segment(
133                segment_type: u8,
134                page_id: u16,
135                data: &[u8],
136                buf: &mut [u8],
137            ) -> crate::Result<usize> {
138                let len = 6 + data.len();
139                broadcast_common::len::need_out(buf, len)?;
140                buf[0] = crate::pes_data_field::SYNC_BYTE;
141                buf[1] = segment_type;
142                buf[2..4].copy_from_slice(&page_id.to_be_bytes());
143                let seg_len = crate::segments::check_segment_length(data.len())?;
144                buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
145                buf[6..len].copy_from_slice(data);
146                Ok(len)
147            }
148        }
149
150        #[cfg(test)]
151        mod macro_drift {
152            #[test]
153            fn segment_type_literals_match_segment_def() {
154                use crate::traits::SegmentDef;
155                $(
156                    assert_eq!(
157                        $seg_type,
158                        <$($path)::+ as SegmentDef>::SEGMENT_TYPE,
159                        concat!("segment_type literal drift for ", stringify!($variant)),
160                    );
161                    assert!(
162                        !<$($path)::+ as SegmentDef>::NAME.is_empty(),
163                        concat!("empty NAME for ", stringify!($variant)),
164                    );
165                )+
166            }
167        }
168    };
169}
170
171declare_segments! {'a;
172    PageComposition = 0x10 => crate::segments::page_composition::PageCompositionSegment,
173    RegionComposition = 0x11 => crate::segments::region_composition::RegionCompositionSegment,
174    ClutDefinition = 0x12 => crate::segments::clut_definition::ClutDefinitionSegment,
175    ObjectData = 0x13 => crate::segments::object_data::ObjectDataSegment<'a>,
176    DisplayDefinition = 0x14 => crate::segments::display_definition::DisplayDefinitionSegment,
177    DisparitySignalling = 0x15 => crate::segments::disparity_signalling::DisparitySignallingSegment,
178    AlternativeClut = 0x16 => crate::segments::alternative_clut::AlternativeClutSegment,
179    EndOfDisplaySet = 0x80 => crate::segments::end_of_display_set::EndOfDisplaySetSegment,
180    Stuffing = 0xFF => crate::segments::stuffing::StuffingSegment<'a>,
181}