#[inline(always)]
pub(crate) fn detect(data: &[u8]) -> bool {
if data.len() < 14 {
return false;
}
if data.starts_with(b"RTSP/1.0 ") || data.starts_with(b"RTSP/2.0 ") {
return validate_rtsp_line(data, true);
}
if is_rtsp_request(data) {
return validate_rtsp_line(data, false);
}
false
}
#[inline(always)]
fn is_rtsp_request(data: &[u8]) -> bool {
match data[0] {
b'O' => data.starts_with(b"OPTIONS "),
b'D' => data.starts_with(b"DESCRIBE "),
b'S' => data.starts_with(b"SETUP ") || data.starts_with(b"SET_PARAMETER "),
b'P' => data.starts_with(b"PLAY ") || data.starts_with(b"PAUSE "),
b'T' => data.starts_with(b"TEARDOWN "),
b'G' => data.starts_with(b"GET_PARAMETER "),
b'R' => data.starts_with(b"REDIRECT ") || data.starts_with(b"RECORD "),
b'A' => data.starts_with(b"ANNOUNCE "),
_ => false,
}
}
#[inline(always)]
fn validate_rtsp_line(data: &[u8], is_response: bool) -> bool {
let limit = data.len().min(64);
let mut found_version = is_response;
let mut end_of_line = limit;
for (i, &b) in data.iter().enumerate().take(limit) {
if b == b'\n' || b == b'\r' {
end_of_line = i;
break;
}
if !(32..=126).contains(&b) {
return false;
}
}
if !found_version && end_of_line > 9 {
let line = &data[..end_of_line];
for i in 0..=(line.len() - 9) {
let sub = &line[i..i + 9];
if sub == b" RTSP/1.0" || sub == b" RTSP/2.0" {
found_version = true;
break;
}
}
}
found_version
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_rtsp_response() {
assert!(detect(b"RTSP/1.0 200 OK\r\n"));
assert!(detect(b"RTSP/2.0 200 OK\n"));
assert!(detect(b"RTSP/1.0 404 Not Found\r\n"));
}
#[test]
fn test_detect_rtsp_requests() {
assert!(detect(b"OPTIONS * RTSP/1.0\r\n"));
assert!(detect(b"DESCRIBE rtsp://example.com/stream RTSP/1.0\r\n"));
assert!(detect(b"SETUP rtsp://example.com/stream/track1 RTSP/1.0\n"));
assert!(detect(b"PLAY rtsp://example.com/stream RTSP/1.0\r\n"));
}
#[test]
fn test_detect_rtsp_partial() {
assert!(detect(b"DESCRIBE <rtsp://server.com/media> RTSP/1.0"));
}
#[test]
fn test_reject_http_collision() {
assert!(!detect(b"GET /index.html HTTP/1.1\r\n"));
assert!(!detect(b"HTTP/1.1 200 OK\r\n"));
}
#[test]
fn test_reject_sip_collision() {
assert!(!detect(b"INVITE sip:bob@example.com SIP/2.0\r\n"));
assert!(!detect(b"SIP/2.0 200 OK\r\n"));
}
#[test]
fn test_reject_non_ascii() {
let mut data = [0u8; 20];
data[..9].copy_from_slice(b"RTSP/1.0 ");
data[9..15].copy_from_slice(&[0xFF, 0x00, 0x12, 0x34, 0x56, 0x78]);
assert!(!detect(&data));
}
#[test]
fn test_short_data() {
assert!(!detect(b"RTSP/1.0"));
assert!(!detect(b"SETUP"));
}
#[test]
fn test_random_data() {
assert!(!detect(&[0x42; 64]));
}
}