Skip to main content

media_plane/
ids.rs

1//! Distinct integer types for a trunk's segment/part numbering (issue #1188).
2//!
3//! `SegmentEntry::sequence_number`, `PartEntry::segment_number` and
4//! `PartEntry::part_index` used to be bare, adjacent `u32`s, so a swapped
5//! argument (`PartEntry::new(bytes, part_index, segment_number, ..)`) still
6//! compiled. Each is now its own newtype, so a swap is a compile error. The
7//! same types name the origin's resources in `hls-runtime` (which re-exports
8//! them), so there is one canonical definition below both crates.
9//!
10//! `Display` and `Debug` render the bare number (they feed wire names such as
11//! `seg-1-5-7.m4s`). Arithmetic is explicit: [`SegmentSeq::checked_next`]
12//! returns `None` at `u32::MAX`, [`SegmentSeq::wrapping_next`] wraps to `0`;
13//! there is deliberately no `Add`/`Sub` impl that could overflow silently.
14
15use core::fmt;
16
17macro_rules! id_newtype {
18    ($(#[$meta:meta])* $name:ident) => {
19        $(#[$meta])*
20        #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
21        pub struct $name(u32);
22
23        impl $name {
24            /// Wrap a raw value.
25            #[must_use]
26            pub const fn new(value: u32) -> Self {
27                Self(value)
28            }
29
30            /// The raw value.
31            #[must_use]
32            pub const fn value(self) -> u32 {
33                self.0
34            }
35
36            /// The next value, or `None` if this is `u32::MAX`.
37            #[must_use]
38            pub const fn checked_next(self) -> Option<Self> {
39                match self.0.checked_add(1) {
40                    Some(v) => Some(Self(v)),
41                    None => None,
42                }
43            }
44
45            /// The next value, wrapping `u32::MAX` to `0`.
46            #[must_use]
47            pub const fn wrapping_next(self) -> Self {
48                Self(self.0.wrapping_add(1))
49            }
50
51            /// The next value, saturating at `u32::MAX`.
52            #[must_use]
53            pub const fn saturating_next(self) -> Self {
54                Self(self.0.saturating_add(1))
55            }
56
57            /// `self + n`, or `None` on overflow.
58            #[must_use]
59            pub const fn checked_add(self, n: u32) -> Option<Self> {
60                match self.0.checked_add(n) {
61                    Some(v) => Some(Self(v)),
62                    None => None,
63                }
64            }
65
66            /// `self - n`, or `None` on underflow.
67            #[must_use]
68            pub const fn checked_sub(self, n: u32) -> Option<Self> {
69                match self.0.checked_sub(n) {
70                    Some(v) => Some(Self(v)),
71                    None => None,
72                }
73            }
74
75            /// The distance `self - earlier`, or `None` if `earlier > self`.
76            #[must_use]
77            pub const fn checked_distance_from(self, earlier: Self) -> Option<u32> {
78                self.0.checked_sub(earlier.0)
79            }
80        }
81
82        impl fmt::Debug for $name {
83            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84                fmt::Debug::fmt(&self.0, f)
85            }
86        }
87
88        impl fmt::Display for $name {
89            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90                fmt::Display::fmt(&self.0, f)
91            }
92        }
93    };
94}
95
96id_newtype! {
97    /// A segment sequence number: the `Trunk`'s own numbering of closed
98    /// segments (the `moof`/`mfhd` number, before any Media Sequence Number
99    /// offset is added). Also the parent-segment number of a part.
100    SegmentSeq
101}
102
103id_newtype! {
104    /// A part's 0-based index within its parent segment.
105    PartIndex
106}
107
108#[cfg(test)]
109mod tests {
110    extern crate alloc;
111    use super::*;
112    use alloc::format;
113
114    #[test]
115    fn checked_next_stops_at_max_wrapping_wraps() {
116        let max = SegmentSeq::new(u32::MAX);
117        assert_eq!(max.checked_next(), None);
118        assert_eq!(max.wrapping_next(), SegmentSeq::new(0));
119        assert_eq!(max.saturating_next(), max);
120        assert_eq!(SegmentSeq::new(6).checked_next(), Some(SegmentSeq::new(7)));
121        assert_eq!(PartIndex::new(u32::MAX).checked_next(), None);
122    }
123
124    #[test]
125    fn checked_add_sub_distance() {
126        let s = SegmentSeq::new(5);
127        assert_eq!(s.checked_add(u32::MAX), None);
128        assert_eq!(s.checked_sub(6), None);
129        assert_eq!(s.checked_sub(5), Some(SegmentSeq::new(0)));
130        assert_eq!(s.checked_distance_from(SegmentSeq::new(2)), Some(3));
131        assert_eq!(s.checked_distance_from(SegmentSeq::new(9)), None);
132    }
133
134    #[test]
135    fn display_and_debug_are_bare() {
136        assert_eq!(
137            format!("{} {:?}", PartIndex::new(7), SegmentSeq::new(9)),
138            "7 9"
139        );
140    }
141}