Skip to main content

scte35_splice/commands/
private_command.rs

1//! private_command() — ANSI/SCTE 35 2023r1 §9.7.6, Table 13
2//! (splice_command_type 0xFF).
3//!
4//! A 32-bit registered `identifier` followed by an opaque `private_byte`
5//! payload (the rest of the command body). The payload length is determined by
6//! `splice_command_length` in the enclosing section, so this struct borrows the
7//! whole remaining command body after the identifier.
8
9use crate::error::{Error, Result};
10use crate::traits::CommandDef;
11use broadcast_common::{Parse, Serialize};
12
13/// `splice_command_type` for private_command (§9.6.1, Table 7).
14pub const COMMAND_TYPE: u8 = 0xFF;
15
16/// Bytes of the fixed `identifier` field.
17const IDENTIFIER_LEN: usize = 4;
18
19/// private_command() — §9.7.6, Table 13.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22pub struct PrivateCommand<'a> {
23    /// 32-bit registration identifier (SMPTE Registration Authority format).
24    pub identifier: u32,
25    /// The remaining `private_byte` payload, verbatim.
26    pub private_bytes: &'a [u8],
27}
28
29impl<'a> Parse<'a> for PrivateCommand<'a> {
30    type Error = Error;
31    fn parse(bytes: &'a [u8]) -> Result<Self> {
32        let (id_bytes, private_bytes) =
33            bytes
34                .split_first_chunk::<IDENTIFIER_LEN>()
35                .ok_or(Error::BufferTooShort {
36                    need: IDENTIFIER_LEN,
37                    have: bytes.len(),
38                    what: "private_command identifier",
39                })?;
40        let identifier = u32::from_be_bytes(*id_bytes);
41        Ok(Self {
42            identifier,
43            private_bytes,
44        })
45    }
46}
47
48impl Serialize for PrivateCommand<'_> {
49    type Error = Error;
50    fn serialized_len(&self) -> usize {
51        IDENTIFIER_LEN + self.private_bytes.len()
52    }
53    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
54        let need = self.serialized_len();
55        if buf.len() < need {
56            return Err(Error::OutputBufferTooSmall {
57                need,
58                have: buf.len(),
59            });
60        }
61        buf[0..IDENTIFIER_LEN].copy_from_slice(&self.identifier.to_be_bytes());
62        buf[IDENTIFIER_LEN..need].copy_from_slice(self.private_bytes);
63        Ok(need)
64    }
65}
66
67impl<'a> CommandDef<'a> for PrivateCommand<'a> {
68    const COMMAND_TYPE: u8 = COMMAND_TYPE;
69    const NAME: &'static str = "PRIVATE_COMMAND";
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn round_trip() {
78        let cmd = PrivateCommand {
79            identifier: 0x43554549,
80            private_bytes: &[0xDE, 0xAD, 0xBE, 0xEF],
81        };
82        let bytes = cmd.to_bytes();
83        assert_eq!(&bytes[0..4], &[0x43, 0x55, 0x45, 0x49]);
84        let back = PrivateCommand::parse(&bytes).unwrap();
85        assert_eq!(cmd, back);
86        assert_eq!(back.to_bytes(), bytes);
87    }
88
89    #[test]
90    fn empty_private_bytes() {
91        let cmd = PrivateCommand {
92            identifier: 0x01020304,
93            private_bytes: &[],
94        };
95        let bytes = cmd.to_bytes();
96        assert_eq!(bytes.len(), 4);
97        assert_eq!(PrivateCommand::parse(&bytes).unwrap(), cmd);
98    }
99
100    #[test]
101    fn rejects_short_identifier() {
102        assert!(matches!(
103            PrivateCommand::parse(&[0x00, 0x01]).unwrap_err(),
104            Error::BufferTooShort { .. }
105        ));
106    }
107}