Skip to main content

scte35_splice/
time.rs

1//! Time structures and 90 kHz helpers — ANSI/SCTE 35 2023r1 §9.8.1 (Table 14)
2//! and §9.8.2 (Table 15).
3//!
4//! `splice_time()` and `break_duration()` carry 33-bit / 40-bit counts of
5//! ticks of the program's 90 kHz clock. The decoded accessors here convert
6//! those tick counts to/from [`core::time::Duration`] (the 4.1.0 decoded-field
7//! pattern), so callers never re-derive the 90 kHz scaling by hand.
8
9use crate::error::{Error, Result};
10use broadcast_common::{Parse, Serialize};
11
12/// One second of the program clock in 90 kHz ticks.
13pub const TICKS_PER_SECOND: u64 = 90_000;
14
15/// Number of distinct values of a 33-bit field; the `pts_adjustment` /
16/// `pts_time` wrap modulus (§9.6.1: carry is ignored on overflow).
17pub const PTS_MODULUS: u64 = 1 << 33;
18
19/// Largest value a 33-bit field can hold (`2^33 - 1`).
20pub const PTS_MAX: u64 = PTS_MODULUS - 1;
21
22/// Largest value a 40-bit field can hold (`2^40 - 1`); the `break_duration`
23/// `duration` range.
24pub const DURATION_40_MAX: u64 = (1 << 40) - 1;
25
26/// Convert a 90 kHz tick count to a [`Duration`](core::time::Duration).
27///
28/// Nanosecond-exact: `ticks * 1e9 / 90_000`. Computed in `u128` so the full
29/// 40-bit `break_duration` range cannot overflow.
30#[must_use]
31pub fn ticks_to_duration(ticks: u64) -> core::time::Duration {
32    let nanos = (ticks as u128) * 1_000_000_000 / (TICKS_PER_SECOND as u128);
33    // nanos fits u64 for the whole 40-bit range (2^40/90000 s ≈ 12_725_000 s).
34    core::time::Duration::from_nanos(nanos as u64)
35}
36
37/// Convert a [`Duration`](core::time::Duration) to a 90 kHz tick count,
38/// truncating toward zero. Returns `None` if the result would exceed `max`
39/// (the field's wire capacity).
40#[must_use]
41pub fn duration_to_ticks(d: core::time::Duration, max: u64) -> Option<u64> {
42    let nanos = d.as_nanos();
43    let ticks = nanos * (TICKS_PER_SECOND as u128) / 1_000_000_000;
44    if ticks > max as u128 {
45        None
46    } else {
47        Some(ticks as u64)
48    }
49}
50
51/// Add two 33-bit PTS values modulo `2^33`, the carry-ignored wrap the spec
52/// defines for `pts_adjustment` applied to a `pts_time` (§9.6.1, §9.8.1).
53#[must_use]
54pub fn pts_add_wrapping(pts_time: u64, pts_adjustment: u64) -> u64 {
55    (pts_time.wrapping_add(pts_adjustment)) % PTS_MODULUS
56}
57
58/// `splice_time()` — §9.8.1, Table 14.
59///
60/// When `pts_time` is present it is a 33-bit count of 90 kHz ticks; absent
61/// (`time_specified_flag == 0`) it signals an immediate command.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize))]
64pub struct SpliceTime {
65    /// 33-bit `pts_time` in 90 kHz ticks, or `None` when
66    /// `time_specified_flag == 0`.
67    pub pts_time: Option<u64>,
68}
69
70impl SpliceTime {
71    /// Bytes this structure serializes to: 5 when a `pts_time` is present
72    /// (1 flag/reserved byte + the 33-bit field spread over 5 bytes), else 1.
73    pub const LEN_WITH_TIME: usize = 5;
74    /// Length when `time_specified_flag == 0`.
75    pub const LEN_NO_TIME: usize = 1;
76
77    /// A `splice_time()` carrying an explicit 33-bit `pts_time` (ticks).
78    #[must_use]
79    pub fn with_pts(pts_time: u64) -> Self {
80        Self {
81            pts_time: Some(pts_time & PTS_MAX),
82        }
83    }
84
85    /// The `pts_time` decoded to a [`Duration`](core::time::Duration), if present.
86    #[must_use]
87    pub fn pts_time_duration(&self) -> Option<core::time::Duration> {
88        self.pts_time.map(ticks_to_duration)
89    }
90
91    /// Set `pts_time` from a [`Duration`](core::time::Duration) (truncating to
92    /// 90 kHz ticks). Errors if the duration exceeds the 33-bit range.
93    pub fn set_pts_time_duration(&mut self, d: core::time::Duration) -> Result<()> {
94        let ticks = duration_to_ticks(d, PTS_MAX).ok_or(Error::InvalidValue {
95            field: "splice_time.pts_time",
96            reason: "duration exceeds 33-bit 90 kHz range",
97        })?;
98        self.pts_time = Some(ticks);
99        Ok(())
100    }
101}
102
103impl<'a> Parse<'a> for SpliceTime {
104    type Error = Error;
105    fn parse(bytes: &'a [u8]) -> Result<Self> {
106        if bytes.is_empty() {
107            return Err(Error::BufferTooShort {
108                need: 1,
109                have: 0,
110                what: "splice_time",
111            });
112        }
113        let time_specified = bytes[0] & 0x80 != 0;
114        if time_specified {
115            if bytes.len() < Self::LEN_WITH_TIME {
116                return Err(Error::BufferTooShort {
117                    need: Self::LEN_WITH_TIME,
118                    have: bytes.len(),
119                    what: "splice_time pts_time",
120                });
121            }
122            // 1 bit flag, 6 reserved, then 33 bits of pts_time.
123            let pts = ((u64::from(bytes[0] & 0x01)) << 32)
124                | (u64::from(bytes[1]) << 24)
125                | (u64::from(bytes[2]) << 16)
126                | (u64::from(bytes[3]) << 8)
127                | u64::from(bytes[4]);
128            Ok(Self {
129                pts_time: Some(pts),
130            })
131        } else {
132            Ok(Self { pts_time: None })
133        }
134    }
135}
136
137impl Serialize for SpliceTime {
138    type Error = Error;
139    fn serialized_len(&self) -> usize {
140        match self.pts_time {
141            Some(_) => Self::LEN_WITH_TIME,
142            None => Self::LEN_NO_TIME,
143        }
144    }
145
146    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
147        let need = self.serialized_len();
148        if buf.len() < need {
149            return Err(Error::OutputBufferTooSmall {
150                need,
151                have: buf.len(),
152            });
153        }
154        match self.pts_time {
155            Some(pts) => {
156                let pts = pts & PTS_MAX;
157                // time_specified_flag=1, 6 reserved bits = 1, top pts bit.
158                buf[0] = 0x80 | 0x7E | ((pts >> 32) as u8 & 0x01);
159                buf[1] = (pts >> 24) as u8;
160                buf[2] = (pts >> 16) as u8;
161                buf[3] = (pts >> 8) as u8;
162                buf[4] = pts as u8;
163            }
164            None => {
165                // time_specified_flag=0, 7 reserved bits = 1.
166                buf[0] = 0x7F;
167            }
168        }
169        Ok(need)
170    }
171}
172
173/// `break_duration()` — §9.8.2, Table 15.
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
175#[cfg_attr(feature = "serde", derive(serde::Serialize))]
176pub struct BreakDuration {
177    /// `auto_return`: when `true`, the splicer uses `duration` to know when to
178    /// return to the network feed (§9.9.2.2).
179    pub auto_return: bool,
180    /// 33-bit elapsed time of the break in 90 kHz ticks.
181    pub duration: u64,
182}
183
184impl BreakDuration {
185    /// Fixed wire length: 1 byte (auto_return + 6 reserved + top duration bit)
186    /// plus 4 bytes for the remaining 32 duration bits.
187    pub const LEN: usize = 5;
188
189    /// The break `duration` decoded to a [`Duration`](core::time::Duration).
190    #[must_use]
191    pub fn duration_value(&self) -> core::time::Duration {
192        ticks_to_duration(self.duration)
193    }
194
195    /// Set `duration` from a [`Duration`](core::time::Duration) (truncating to
196    /// 90 kHz ticks). Errors if it exceeds the 33-bit range.
197    pub fn set_duration_value(&mut self, d: core::time::Duration) -> Result<()> {
198        self.duration = duration_to_ticks(d, PTS_MAX).ok_or(Error::InvalidValue {
199            field: "break_duration.duration",
200            reason: "duration exceeds 33-bit 90 kHz range",
201        })?;
202        Ok(())
203    }
204}
205
206impl<'a> Parse<'a> for BreakDuration {
207    type Error = Error;
208    fn parse(bytes: &'a [u8]) -> Result<Self> {
209        if bytes.len() < Self::LEN {
210            return Err(Error::BufferTooShort {
211                need: Self::LEN,
212                have: bytes.len(),
213                what: "break_duration",
214            });
215        }
216        let auto_return = bytes[0] & 0x80 != 0;
217        let duration = ((u64::from(bytes[0] & 0x01)) << 32)
218            | (u64::from(bytes[1]) << 24)
219            | (u64::from(bytes[2]) << 16)
220            | (u64::from(bytes[3]) << 8)
221            | u64::from(bytes[4]);
222        Ok(Self {
223            auto_return,
224            duration,
225        })
226    }
227}
228
229impl Serialize for BreakDuration {
230    type Error = Error;
231    fn serialized_len(&self) -> usize {
232        Self::LEN
233    }
234
235    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
236        if buf.len() < Self::LEN {
237            return Err(Error::OutputBufferTooSmall {
238                need: Self::LEN,
239                have: buf.len(),
240            });
241        }
242        let d = self.duration & PTS_MAX;
243        // auto_return, 6 reserved bits = 1, top duration bit.
244        buf[0] = (u8::from(self.auto_return) << 7) | 0x7E | ((d >> 32) as u8 & 0x01);
245        buf[1] = (d >> 24) as u8;
246        buf[2] = (d >> 16) as u8;
247        buf[3] = (d >> 8) as u8;
248        buf[4] = d as u8;
249        Ok(Self::LEN)
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn ticks_duration_round_trip_exact() {
259        // 90_000 ticks = 1 s.
260        assert_eq!(
261            ticks_to_duration(90_000),
262            core::time::Duration::from_secs(1)
263        );
264        assert_eq!(
265            duration_to_ticks(core::time::Duration::from_secs(1), PTS_MAX),
266            Some(90_000)
267        );
268    }
269
270    #[test]
271    fn duration_to_ticks_rejects_over_range() {
272        // A duration well past the 33-bit tick capacity must fail to encode.
273        // PTS_MAX ticks ≈ 95443.7 s, so 95444 s is over the field's range.
274        let over = core::time::Duration::from_secs(95_444);
275        assert_eq!(duration_to_ticks(over, PTS_MAX), None);
276        // A whole-second duration just inside the range still encodes exactly.
277        assert_eq!(
278            duration_to_ticks(core::time::Duration::from_secs(95_443), PTS_MAX),
279            Some(95_443 * TICKS_PER_SECOND)
280        );
281    }
282
283    #[test]
284    fn pts_add_wraps_at_2pow33() {
285        // PTS_MAX + 1 wraps to 0 (carry ignored).
286        assert_eq!(pts_add_wrapping(PTS_MAX, 1), 0);
287        // Wrap by a large adjustment.
288        assert_eq!(pts_add_wrapping(PTS_MAX, PTS_MAX), PTS_MAX - 1);
289        // No wrap.
290        assert_eq!(pts_add_wrapping(10, 20), 30);
291    }
292
293    #[test]
294    fn splice_time_round_trip_with_and_without_pts() {
295        for st in [
296            SpliceTime::with_pts(0x1_2345_6789 & PTS_MAX),
297            SpliceTime::default(),
298        ] {
299            let bytes = st.to_bytes();
300            let back = SpliceTime::parse(&bytes).unwrap();
301            assert_eq!(st, back);
302            assert_eq!(back.to_bytes(), bytes);
303        }
304    }
305
306    #[test]
307    fn splice_time_max_pts_round_trips() {
308        let st = SpliceTime::with_pts(PTS_MAX);
309        let bytes = st.to_bytes();
310        assert_eq!(SpliceTime::parse(&bytes).unwrap().pts_time, Some(PTS_MAX));
311    }
312
313    #[test]
314    fn break_duration_round_trip() {
315        let bd = BreakDuration {
316            auto_return: true,
317            duration: PTS_MAX,
318        };
319        let bytes = bd.to_bytes();
320        let back = BreakDuration::parse(&bytes).unwrap();
321        assert_eq!(bd, back);
322        assert_eq!(back.to_bytes(), bytes);
323    }
324
325    #[test]
326    fn break_duration_decoded_accessor() {
327        let mut bd = BreakDuration::default();
328        bd.set_duration_value(core::time::Duration::from_secs(60))
329            .unwrap();
330        assert_eq!(bd.duration, 60 * 90_000);
331        assert_eq!(bd.duration_value(), core::time::Duration::from_secs(60));
332    }
333}