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        /// Offset at which the payload begins (16).
58        payload_offset: usize,
59    },
60}
61
62impl SrtPacket {
63    /// Parse the 16-byte SRT header from `buf`. Returns `None` if `buf` is
64    /// shorter than a header.
65    pub fn parse(buf: &[u8]) -> Option<SrtPacket> {
66        use crate::protocol::byteops::ByteReader;
67        let mut r = ByteReader::new(buf);
68        let word0 = r.u32_be()?;
69        r.skip(8)?; // timestamp + message-number fields
70        let dest_socket_id = r.u32_be()?;
71
72        if word0 & 0x8000_0000 == 0 {
73            // Data packet: bit 0 is the control flag (0), bits 1..31 the seq num.
74            Some(SrtPacket::Data {
75                sequence: word0 & 0x7FFF_FFFF,
76                dest_socket_id,
77                payload_offset: 16,
78            })
79        } else {
80            // Control packet: bits 1..15 = type, bits 16..31 = subtype.
81            let control_type = ((word0 >> 16) & 0x7FFF) as u16;
82            Some(SrtPacket::Control {
83                control_type: ControlType::from_u16(control_type),
84                dest_socket_id,
85            })
86        }
87    }
88}
89
90/// Build an SRT **data packet** carrying `payload` (used by the egress caller).
91///
92/// Header words: `[0]` control-flag 0 + 31-bit sequence; `[1]` packet-position
93/// `11` (solo) + order flag + 26-bit message number; `[2]` timestamp (µs);
94/// `[3]` destination socket id. `payload` follows at offset 16.
95pub fn build_data_packet(
96    sequence: u32,
97    message_number: u32,
98    timestamp: u32,
99    dest_socket_id: u32,
100    payload: &[u8],
101) -> Vec<u8> {
102    let mut out = Vec::with_capacity(16 + payload.len());
103    out.extend_from_slice(&(sequence & 0x7FFF_FFFF).to_be_bytes());
104    // PP=11 (whole message in one packet) | O=1 (in order) | msg number.
105    let word1 = (0b11u32 << 30) | (1 << 29) | (message_number & 0x03FF_FFFF);
106    out.extend_from_slice(&word1.to_be_bytes());
107    out.extend_from_slice(&timestamp.to_be_bytes());
108    out.extend_from_slice(&dest_socket_id.to_be_bytes());
109    out.extend_from_slice(payload);
110    out
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    fn header(word0: u32, dest: u32) -> Vec<u8> {
118        let mut b = word0.to_be_bytes().to_vec();
119        b.extend_from_slice(&[0; 8]); // ts + msg fields
120        b.extend_from_slice(&dest.to_be_bytes());
121        b
122    }
123
124    #[test]
125    fn parses_data_packet() {
126        let pkt = SrtPacket::parse(&header(0x0000_002A, 99)).unwrap();
127        assert_eq!(
128            pkt,
129            SrtPacket::Data {
130                sequence: 42,
131                dest_socket_id: 99,
132                payload_offset: 16,
133            }
134        );
135    }
136
137    #[test]
138    fn built_data_packet_round_trips() {
139        let payload = [0x47u8, 0x40, 0x00, 0x10, 0xAA, 0xBB]; // a tiny TS-ish chunk
140        let pkt = build_data_packet(1234, 7, 90_000, 0xDEAD_BEEF, &payload);
141        match SrtPacket::parse(&pkt).unwrap() {
142            SrtPacket::Data {
143                sequence,
144                dest_socket_id,
145                payload_offset,
146            } => {
147                assert_eq!(sequence, 1234);
148                assert_eq!(dest_socket_id, 0xDEAD_BEEF);
149                assert_eq!(&pkt[payload_offset..], &payload);
150            }
151            other => panic!("expected data packet, got {other:?}"),
152        }
153    }
154
155    #[test]
156    fn parses_control_handshake() {
157        // Control flag set, type 0x0000 (handshake).
158        let pkt = SrtPacket::parse(&header(0x8000_0000, 7)).unwrap();
159        assert_eq!(
160            pkt,
161            SrtPacket::Control {
162                control_type: ControlType::Handshake,
163                dest_socket_id: 7,
164            }
165        );
166    }
167
168    #[test]
169    fn parses_control_nak() {
170        let pkt = SrtPacket::parse(&header(0x8003_0000, 1)).unwrap();
171        match pkt {
172            SrtPacket::Control { control_type, .. } => assert_eq!(control_type, ControlType::Nak),
173            _ => panic!("expected control"),
174        }
175    }
176
177    #[test]
178    fn rejects_short_buffer() {
179        assert!(SrtPacket::parse(&[0u8; 8]).is_none());
180    }
181}