Skip to main content

arcly_stream/protocol/srt/
packet.rs

1//! SRT packet header parsing (draft-sharabayko-srt §3).
2//!
3//! Every SRT packet opens with a 16-byte header. The high bit of the first word
4//! selects the packet kind: `0` = data, `1` = control.
5
6/// SRT control packet types (the 15-bit type field of a control header).
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum ControlType {
10    /// `HANDSHAKE` (0x0000) — connection setup (induction/conclusion).
11    Handshake,
12    /// `KEEPALIVE` (0x0001).
13    Keepalive,
14    /// `ACK` (0x0002).
15    Ack,
16    /// `NAK` / loss report (0x0003).
17    Nak,
18    /// `SHUTDOWN` (0x0005) — peer is closing.
19    Shutdown,
20    /// `ACKACK` (0x0006).
21    AckAck,
22    /// Any other / vendor control type, carrying its raw type value.
23    Other(u16),
24}
25
26impl ControlType {
27    fn from_u16(v: u16) -> ControlType {
28        match v {
29            0x0000 => ControlType::Handshake,
30            0x0001 => ControlType::Keepalive,
31            0x0002 => ControlType::Ack,
32            0x0003 => ControlType::Nak,
33            0x0005 => ControlType::Shutdown,
34            0x0006 => ControlType::AckAck,
35            other => ControlType::Other(other),
36        }
37    }
38}
39
40/// A parsed SRT packet — its header fields and, for data packets, the payload
41/// offset (always 16, the fixed header length).
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum SrtPacket {
44    /// A control packet.
45    Control {
46        /// The control packet type.
47        control_type: ControlType,
48        /// Destination SRT socket id (last header word).
49        dest_socket_id: u32,
50    },
51    /// A data packet carrying media (here: MPEG-TS) bytes.
52    Data {
53        /// 31-bit packet sequence number.
54        sequence: u32,
55        /// Destination SRT socket id.
56        dest_socket_id: u32,
57        /// Encryption key flag (`KK`): 0 = unencrypted, 1 = even key, 2 = odd.
58        key_flag: u8,
59        /// Whether the retransmit (`R`) bit is set (a NAK-driven replay).
60        retransmit: bool,
61        /// Offset at which the payload begins (16).
62        payload_offset: usize,
63    },
64}
65
66impl SrtPacket {
67    /// Parse the 16-byte SRT header from `buf`. Returns `None` if `buf` is
68    /// shorter than a header.
69    pub fn parse(buf: &[u8]) -> Option<SrtPacket> {
70        use crate::protocol::byteops::ByteReader;
71        let mut r = ByteReader::new(buf);
72        let word0 = r.u32_be()?;
73        let word1 = r.u32_be()?;
74        r.skip(4)?; // timestamp field
75        let dest_socket_id = r.u32_be()?;
76
77        if word0 & 0x8000_0000 == 0 {
78            // Data packet: bit 0 is the control flag (0), bits 1..31 the seq num.
79            // word1: PP(2) | O(1) | KK(2) | R(1) | Message Number(26).
80            Some(SrtPacket::Data {
81                sequence: word0 & 0x7FFF_FFFF,
82                dest_socket_id,
83                key_flag: ((word1 >> 27) & 0b11) as u8,
84                retransmit: (word1 >> 26) & 1 != 0,
85                payload_offset: 16,
86            })
87        } else {
88            // Control packet: bits 1..15 = type, bits 16..31 = subtype.
89            let control_type = ((word0 >> 16) & 0x7FFF) as u16;
90            Some(SrtPacket::Control {
91                control_type: ControlType::from_u16(control_type),
92                dest_socket_id,
93            })
94        }
95    }
96}
97
98/// Build an SRT **data packet** carrying `payload` (used by the egress caller).
99///
100/// Header words: `[0]` control-flag 0 + 31-bit sequence; `[1]` packet-position
101/// `11` (solo) + order flag + `KK` key flag + `R` retransmit flag + 26-bit
102/// message number; `[2]` timestamp (µs); `[3]` destination socket id. `payload`
103/// follows at offset 16.
104///
105/// `key_flag` is the `KK` encryption selector (0 = clear, 1 = even key) and
106/// `retransmit` sets the `R` bit for a NAK-driven replay.
107pub fn build_data_packet(
108    sequence: u32,
109    message_number: u32,
110    timestamp: u32,
111    dest_socket_id: u32,
112    key_flag: u8,
113    retransmit: bool,
114    payload: &[u8],
115) -> Vec<u8> {
116    let mut out = Vec::with_capacity(16 + payload.len());
117    out.extend_from_slice(&(sequence & 0x7FFF_FFFF).to_be_bytes());
118    // PP=11 (whole message in one packet) | O=1 (in order) | KK | R | msg number.
119    let word1 = (0b11u32 << 30)
120        | (1 << 29)
121        | (((key_flag & 0b11) as u32) << 27)
122        | ((retransmit as u32) << 26)
123        | (message_number & 0x03FF_FFFF);
124    out.extend_from_slice(&word1.to_be_bytes());
125    out.extend_from_slice(&timestamp.to_be_bytes());
126    out.extend_from_slice(&dest_socket_id.to_be_bytes());
127    out.extend_from_slice(payload);
128    out
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    fn header(word0: u32, dest: u32) -> Vec<u8> {
136        let mut b = word0.to_be_bytes().to_vec();
137        b.extend_from_slice(&[0; 8]); // ts + msg fields
138        b.extend_from_slice(&dest.to_be_bytes());
139        b
140    }
141
142    #[test]
143    fn parses_data_packet() {
144        let pkt = SrtPacket::parse(&header(0x0000_002A, 99)).unwrap();
145        assert_eq!(
146            pkt,
147            SrtPacket::Data {
148                sequence: 42,
149                dest_socket_id: 99,
150                key_flag: 0,
151                retransmit: false,
152                payload_offset: 16,
153            }
154        );
155    }
156
157    #[test]
158    fn built_data_packet_round_trips() {
159        let payload = [0x47u8, 0x40, 0x00, 0x10, 0xAA, 0xBB]; // a tiny TS-ish chunk
160        let pkt = build_data_packet(1234, 7, 90_000, 0xDEAD_BEEF, 1, true, &payload);
161        match SrtPacket::parse(&pkt).unwrap() {
162            SrtPacket::Data {
163                sequence,
164                dest_socket_id,
165                key_flag,
166                retransmit,
167                payload_offset,
168            } => {
169                assert_eq!(sequence, 1234);
170                assert_eq!(dest_socket_id, 0xDEAD_BEEF);
171                assert_eq!(key_flag, 1, "even-key flag round-trips");
172                assert!(retransmit, "retransmit bit round-trips");
173                assert_eq!(&pkt[payload_offset..], &payload);
174            }
175            other => panic!("expected data packet, got {other:?}"),
176        }
177    }
178
179    #[test]
180    fn parses_control_handshake() {
181        // Control flag set, type 0x0000 (handshake).
182        let pkt = SrtPacket::parse(&header(0x8000_0000, 7)).unwrap();
183        assert_eq!(
184            pkt,
185            SrtPacket::Control {
186                control_type: ControlType::Handshake,
187                dest_socket_id: 7,
188            }
189        );
190    }
191
192    #[test]
193    fn parses_control_nak() {
194        let pkt = SrtPacket::parse(&header(0x8003_0000, 1)).unwrap();
195        match pkt {
196            SrtPacket::Control { control_type, .. } => assert_eq!(control_type, ControlType::Nak),
197            _ => panic!("expected control"),
198        }
199    }
200
201    #[test]
202    fn rejects_short_buffer() {
203        assert!(SrtPacket::parse(&[0u8; 8]).is_none());
204    }
205}