Skip to main content

rtsp_runtime/
interleaved.rs

1//! Interleaved (`$`-framed) binary data — RFC 2326 §10.12.
2//!
3//! When RTSP is carried over TCP, media (RTP) and control (RTCP) packets may be
4//! interleaved with RTSP messages on the same connection, each wrapped in a
5//! 4-byte framing prefix, per [`docs/interleaved-framing.md`](../docs/interleaved-framing.md):
6//!
7//! ```text
8//! | '$' (0x24) | channel id | length (u16 big-endian) | payload (length bytes) |
9//! ```
10//!
11//! [`InterleavedFrame`] models one such block. [`parse_frames`] is the streaming
12//! demultiplexer: given a byte buffer that may contain several complete frames
13//! followed by a partial tail, it returns the complete frames plus the number of
14//! unconsumed bytes, so the caller can retain the partial tail for the next read.
15
16use crate::error::{Error, Result};
17
18/// The `$` magic byte that prefixes an interleaved data block (RFC 2326 §10.12).
19pub const MAGIC: u8 = 0x24;
20
21/// Length of the interleaved framing prefix: `$` + channel + u16 length.
22pub const HEADER_LEN: usize = 4;
23
24/// One interleaved binary data block (RFC 2326 §10.12): a channel id and a
25/// payload (exactly one upper-layer PDU, e.g. one RTP packet).
26#[derive(Debug, Clone, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct InterleavedFrame {
29    /// Channel identifier, as negotiated by `interleaved=` in the Transport
30    /// header. By convention even = RTP, odd = RTCP.
31    pub channel: u8,
32    /// The framed payload bytes.
33    pub payload: Vec<u8>,
34}
35
36impl InterleavedFrame {
37    /// Creates a frame for `channel` carrying `payload`.
38    pub fn new(channel: u8, payload: impl Into<Vec<u8>>) -> Self {
39        InterleavedFrame {
40            channel,
41            payload: payload.into(),
42        }
43    }
44
45    /// The serialized length of this frame (`4 + payload.len()`).
46    pub fn serialized_len(&self) -> usize {
47        HEADER_LEN + self.payload.len()
48    }
49
50    /// Serializes the frame to a fresh `Vec<u8>` (`$`, channel, u16-be length,
51    /// payload).
52    ///
53    /// Returns an error if the payload exceeds the 16-bit length field.
54    pub fn to_bytes(&self) -> Result<Vec<u8>> {
55        let mut out = Vec::with_capacity(self.serialized_len());
56        self.serialize_into(&mut out)?;
57        Ok(out)
58    }
59
60    /// Appends the serialized frame to `out`.
61    ///
62    /// Returns an error if the payload exceeds the 16-bit length field.
63    pub fn serialize_into(&self, out: &mut Vec<u8>) -> Result<()> {
64        let len: u16 = u16::try_from(self.payload.len()).map_err(|_| {
65            Error::InterleavedFrame(format!(
66                "payload of {} bytes exceeds 16-bit length field",
67                self.payload.len()
68            ))
69        })?;
70        out.push(MAGIC);
71        out.push(self.channel);
72        out.extend_from_slice(&len.to_be_bytes());
73        out.extend_from_slice(&self.payload);
74        Ok(())
75    }
76
77    /// Parses a single frame from the front of `buf`.
78    ///
79    /// On success returns the frame and the number of bytes consumed
80    /// (`4 + payload len`). Returns `Ok(None)` if `buf` does not yet contain a
81    /// complete frame (need more bytes). Returns an error if the leading byte is
82    /// not the `$` magic.
83    pub fn parse(buf: &[u8]) -> Result<Option<(InterleavedFrame, usize)>> {
84        if buf.len() < HEADER_LEN {
85            return Ok(None);
86        }
87        if buf[0] != MAGIC {
88            return Err(Error::InterleavedFrame(format!(
89                "expected '$' (0x{MAGIC:02X}), found 0x{:02X}",
90                buf[0]
91            )));
92        }
93        let channel = buf[1];
94        let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
95        let total = HEADER_LEN + len;
96        if buf.len() < total {
97            return Ok(None);
98        }
99        let frame = InterleavedFrame {
100            channel,
101            payload: buf[HEADER_LEN..total].to_vec(),
102        };
103        Ok(Some((frame, total)))
104    }
105}
106
107/// Streaming demultiplexer for a run of interleaved frames (RFC 2326 §10.12).
108///
109/// Parses as many complete frames as `buf` contains, starting at offset 0, and
110/// returns them together with the count of trailing bytes that form an
111/// incomplete frame (the "remainder"). The caller keeps `buf[buf.len() -
112/// remainder ..]` and prepends it to the next chunk.
113///
114/// This function assumes the buffer starts on a frame boundary (a `$`). It is
115/// used by the engine only after it has classified the leading byte as `$`;
116/// mixed RTSP-message / `$`-frame streams are dispatched at a higher level.
117pub fn parse_frames(buf: &[u8]) -> Result<(Vec<InterleavedFrame>, usize)> {
118    let mut frames = Vec::new();
119    let mut offset = 0usize;
120    while offset < buf.len() {
121        match InterleavedFrame::parse(&buf[offset..])? {
122            Some((frame, consumed)) => {
123                frames.push(frame);
124                offset += consumed;
125            }
126            None => break, // partial frame at the tail
127        }
128    }
129    Ok((frames, buf.len() - offset))
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn single_frame_round_trip() {
138        let payload: Vec<u8> = (0u8..17).collect();
139        let frame = InterleavedFrame::new(0, payload.clone());
140        let bytes = frame.to_bytes().unwrap();
141        assert_eq!(bytes[0], MAGIC);
142        assert_eq!(bytes[1], 0);
143        assert_eq!(&bytes[2..4], &(payload.len() as u16).to_be_bytes());
144        let (parsed, consumed) = InterleavedFrame::parse(&bytes).unwrap().unwrap();
145        assert_eq!(parsed, frame);
146        assert_eq!(consumed, bytes.len());
147    }
148
149    #[test]
150    fn frame_channel_byte_reflects_channel() {
151        // Mutating the channel changes the serialized channel byte (offset 1).
152        let f = InterleavedFrame::new(1u8, vec![9u8, 8, 7]);
153        let bytes = f.to_bytes().unwrap();
154        assert_eq!(bytes[0], MAGIC);
155        assert_eq!(bytes[1], 1, "channel byte must reflect channel 1");
156        assert_eq!(u16::from_be_bytes([bytes[2], bytes[3]]), 3);
157        let (parsed, consumed) = InterleavedFrame::parse(&bytes).unwrap().unwrap();
158        assert_eq!(consumed, bytes.len());
159        assert_eq!(parsed.channel, 1);
160        assert_eq!(parsed.payload, vec![9, 8, 7]);
161    }
162
163    #[test]
164    fn two_frames_plus_partial_returns_two_and_remainder() {
165        let f0 = InterleavedFrame::new(0, vec![0xAA; 10]);
166        let f1 = InterleavedFrame::new(1, vec![0xBB; 4]);
167        let mut buf = Vec::new();
168        buf.extend_from_slice(&f0.to_bytes().unwrap());
169        buf.extend_from_slice(&f1.to_bytes().unwrap());
170        // A partial third frame: a full header claiming 8 bytes but only 3 present.
171        let partial = [MAGIC, 0x00, 0x00, 0x08, 1, 2, 3];
172        buf.extend_from_slice(&partial);
173
174        let (frames, remainder) = parse_frames(&buf).unwrap();
175        assert_eq!(frames.len(), 2);
176        assert_eq!(frames[0], f0);
177        assert_eq!(frames[1], f1);
178        assert_eq!(remainder, partial.len());
179        assert_eq!(&buf[buf.len() - remainder..], &partial);
180    }
181
182    #[test]
183    fn header_only_partial_is_remainder() {
184        let buf = [MAGIC, 0x00];
185        let (frames, remainder) = parse_frames(&buf).unwrap();
186        assert!(frames.is_empty());
187        assert_eq!(remainder, 2);
188    }
189
190    #[test]
191    fn wrong_magic_bites() {
192        let buf = [b'R', 0, 0, 1, 9];
193        assert!(InterleavedFrame::parse(&buf).is_err());
194    }
195}