#[derive(Debug, Clone)]
pub struct InterleavedPacket {
pub channel: u8,
pub data: Vec<u8>,
}
#[derive(Debug)]
pub enum FrameStatus {
NeedMore,
Interleaved {
consumed: usize,
packet: InterleavedPacket,
},
RtspMessage,
}
#[must_use]
pub fn next_frame(buf: &[u8]) -> FrameStatus {
if buf.is_empty() {
return FrameStatus::NeedMore;
}
if buf[0] != b'$' {
return FrameStatus::RtspMessage;
}
if buf.len() < 4 {
return FrameStatus::NeedMore;
}
let channel = buf[1];
let length = u16::from_be_bytes([buf[2], buf[3]]) as usize;
let total = 4 + length;
if buf.len() < total {
return FrameStatus::NeedMore;
}
FrameStatus::Interleaved {
consumed: total,
packet: InterleavedPacket {
channel,
data: buf[4..total].to_vec(),
},
}
}
#[must_use]
pub fn encode_interleaved(channel: u8, data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(4 + data.len());
out.push(b'$');
out.push(channel);
out.extend_from_slice(&(data.len() as u16).to_be_bytes());
out.extend_from_slice(data);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_buffer_needs_more() {
assert!(matches!(next_frame(&[]), FrameStatus::NeedMore));
}
#[test]
fn rtsp_byte_signals_message() {
assert!(matches!(next_frame(b"RTSP/1.0 "), FrameStatus::RtspMessage));
}
#[test]
fn partial_interleaved_header_needs_more() {
assert!(matches!(next_frame(b"$\x00\x10"), FrameStatus::NeedMore));
}
#[test]
fn partial_interleaved_payload_needs_more() {
let mut buf = vec![b'$', 0, 0, 8];
buf.extend_from_slice(&[1, 2, 3]); assert!(matches!(next_frame(&buf), FrameStatus::NeedMore));
}
#[test]
fn parses_complete_interleaved_packet() {
let payload = b"hello-rtp-payload";
let mut buf = vec![b'$', 7];
buf.extend_from_slice(&(payload.len() as u16).to_be_bytes());
buf.extend_from_slice(payload);
match next_frame(&buf) {
FrameStatus::Interleaved { consumed, packet } => {
assert_eq!(consumed, 4 + payload.len());
assert_eq!(packet.channel, 7);
assert_eq!(packet.data, payload);
}
other => panic!("expected interleaved, got {other:?}"),
}
}
#[test]
fn encode_round_trips() {
let enc = encode_interleaved(3, b"abc");
match next_frame(&enc) {
FrameStatus::Interleaved { consumed, packet } => {
assert_eq!(consumed, enc.len());
assert_eq!(packet.channel, 3);
assert_eq!(packet.data, b"abc");
}
_ => panic!("expected interleaved"),
}
}
}