dvb-subtitle 0.5.0

DVB subtitling (bitmap) segment parser — ETSI EN 300 743, the segments carried in subtitle PES.
Documentation
//! Unified segment dispatch: [`AnySegment`] + segment parsing.
//!
//! [`AnySegment`] is generated from a single declarative list
//! (`declare_segments!`) — one line per crate-implemented segment type.
//! The list is the single source of truth: it produces the enum, the
//! `From<T>` conversions, and the segment_type → type dispatcher, and
//! a drift test pins each segment_type literal to the type's
//! [`crate::traits::SegmentDef::SEGMENT_TYPE`].

use broadcast_common::{Parse, Serialize};

/// Declares [`AnySegment`] + its dispatcher from one segment type list.
///
/// Each line is `Variant = 0xSEGMENT_TYPE => module::Type[<'a>]`.
macro_rules! declare_segments {
    (
        $lt:lifetime;
        $( $variant:ident = $seg_type:literal => $($path:ident)::+ $(<$plt:lifetime>)? ),+ $(,)?
    ) => {
        /// Every crate-implemented segment type, plus an `Unknown` fallthrough.
        ///
        /// serde uses external tagging with camelCase variant keys.
        #[derive(Debug, Clone, PartialEq, Eq)]
        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
        #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
        #[non_exhaustive]
        pub enum AnySegment<$lt> {
            $(
                #[allow(missing_docs)]
                $variant($($path)::+ $(<$plt>)?),
            )+
            /// Segment with an unrecognized or unsupported segment_type; `data` is the
            /// segment body sans the 6-byte header (4 bytes of generic segment framing).
            Unknown {
                /// The raw segment_type byte.
                segment_type: u8,
                /// The page_id from the segment header.
                page_id: u16,
                /// The segment body bytes (starting after the 4-byte sync_byte+segment_type+page_id+segment_length header).
                data: &$lt [u8],
            },
            /// Segment with a *recognized* segment_type whose typed parse
            /// returned `Err` (distinct from [`Self::Unknown`], whose
            /// segment_type has no typed implementation at all — issue
            /// #1108/DS-W1: a bug that makes every real instance of a typed
            /// parser fail could otherwise hide as `Unknown` passthrough and
            /// still round-trip byte-exact). Re-serializes the raw bytes
            /// verbatim, same as `Unknown`.
            Malformed {
                /// The raw segment_type byte.
                segment_type: u8,
                /// The page_id from the segment header.
                page_id: u16,
                /// The segment body bytes (same slicing as `Unknown::data`).
                data: &$lt [u8],
                /// Why the typed parse failed.
                #[cfg_attr(feature = "serde", serde(skip))]
                err: crate::error::Error,
            },
        }

        $(
            impl<$lt> From<$($path)::+ $(<$plt>)?> for AnySegment<$lt> {
                fn from(s: $($path)::+ $(<$plt>)?) -> Self {
                    Self::$variant(s)
                }
            }
        )+

        impl<$lt> AnySegment<$lt> {
            /// Every segment_type the generated dispatcher routes.
            pub const DISPATCHED_SEGMENT_TYPES: &'static [u8] = &[$($seg_type),+];

            /// Diagnostic name of the contained segment — the type's
            /// [`SegmentDef::NAME`](crate::traits::SegmentDef::NAME)
            /// (`"PAGE_COMPOSITION"`, `"OBJECT_DATA"`, …); `"UNKNOWN"` for
            /// [`AnySegment::Unknown`], `"MALFORMED"` for
            /// [`AnySegment::Malformed`].
            #[must_use]
            pub fn name(&self) -> &'static str {
                match self {
                    $(
                        Self::$variant(_) =>
                            <$($path)::+ as crate::traits::SegmentDef>::NAME,
                    )+
                    Self::Unknown { .. } => "UNKNOWN",
                    Self::Malformed { .. } => "MALFORMED",
                }
            }

            /// Parse one full segment (including 6-byte generic header) by its segment_type.
            ///
            /// `None` means no typed implementation exists for `segment_type` (the
            /// caller turns that into [`AnySegment::Unknown`]). `Some(Err)`
            /// is a typed parse failure for a recognized segment_type (the
            /// caller turns that into [`AnySegment::Malformed`]).
            pub(crate) fn dispatch(segment_type: u8, full: &$lt [u8]) -> Option<crate::Result<Self>> {
                match segment_type {
                    $(
                        $seg_type => Some(<$($path)::+>::parse(full).map(Self::$variant)),
                    )+
                    _ => None,
                }
            }

            pub(crate) fn serialized_len(&self) -> usize {
                match self {
                    $(
                        Self::$variant(s) => s.serialized_len(),
                    )+
                    Self::Unknown { data, .. } => (6 + data.len()),
                    Self::Malformed { data, .. } => (6 + data.len()),
                }
            }

            pub(crate) fn serialize_into(&self, buf: &mut [u8]) -> crate::Result<usize> {
                match self {
                    $(
                        Self::$variant(s) => s.serialize_into(buf),
                    )+
                    Self::Unknown { segment_type, page_id, data } => {
                        Self::serialize_raw_segment(*segment_type, *page_id, data, buf, "Unknown segment serialize")
                    }
                    Self::Malformed { segment_type, page_id, data, .. } => {
                        Self::serialize_raw_segment(*segment_type, *page_id, data, buf, "Malformed segment serialize")
                    }
                }
            }

            /// Shared raw re-serialization for `Unknown`/`Malformed`, both of
            /// which round-trip their body bytes verbatim.
            fn serialize_raw_segment(
                segment_type: u8,
                page_id: u16,
                data: &[u8],
                buf: &mut [u8],
                what: &'static str,
            ) -> crate::Result<usize> {
                let len = 6 + data.len();
                if buf.len() < len {
                    return Err(crate::error::Error::BufferTooShort {
                        need: len,
                        have: buf.len(),
                        what,
                    });
                }
                buf[0] = crate::pes_data_field::SYNC_BYTE;
                buf[1] = segment_type;
                buf[2..4].copy_from_slice(&page_id.to_be_bytes());
                let seg_len = crate::segments::check_segment_length(data.len())?;
                buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
                buf[6..len].copy_from_slice(data);
                Ok(len)
            }
        }

        #[cfg(test)]
        mod macro_drift {
            #[test]
            fn segment_type_literals_match_segment_def() {
                use crate::traits::SegmentDef;
                $(
                    assert_eq!(
                        $seg_type,
                        <$($path)::+ as SegmentDef>::SEGMENT_TYPE,
                        concat!("segment_type literal drift for ", stringify!($variant)),
                    );
                    assert!(
                        !<$($path)::+ as SegmentDef>::NAME.is_empty(),
                        concat!("empty NAME for ", stringify!($variant)),
                    );
                )+
            }
        }
    };
}

declare_segments! {'a;
    PageComposition = 0x10 => crate::segments::page_composition::PageCompositionSegment,
    RegionComposition = 0x11 => crate::segments::region_composition::RegionCompositionSegment,
    ClutDefinition = 0x12 => crate::segments::clut_definition::ClutDefinitionSegment,
    ObjectData = 0x13 => crate::segments::object_data::ObjectDataSegment<'a>,
    DisplayDefinition = 0x14 => crate::segments::display_definition::DisplayDefinitionSegment,
    DisparitySignalling = 0x15 => crate::segments::disparity_signalling::DisparitySignallingSegment,
    AlternativeClut = 0x16 => crate::segments::alternative_clut::AlternativeClutSegment,
    EndOfDisplaySet = 0x80 => crate::segments::end_of_display_set::EndOfDisplaySetSegment,
    Stuffing = 0xFF => crate::segments::stuffing::StuffingSegment<'a>,
}