use crate::{DetectionStatus, ProtocolVersion};
#[inline(always)]
pub(crate) fn probe(data: &[u8]) -> (DetectionStatus, ProtocolVersion<'_>) {
if data.len() < 2 {
if data.is_empty() {
return (DetectionStatus::Incomplete, ProtocolVersion::Unknown);
}
return match data[0] {
b'+' | b'-' | b':' | b'$' | b'*' | b'_' | b',' | b'#' | b'!' | b'=' | b'(' | b'%' | b'~'
| b'>' => (DetectionStatus::Incomplete, ProtocolVersion::Unknown),
_ => (DetectionStatus::NoMatch, ProtocolVersion::Unknown),
};
}
let first = data[0];
let second = data[1];
let resp_ver = match first {
b'+' | b'-' => {
if !(32..=126).contains(&second) && second != b'\r' {
return (DetectionStatus::NoMatch, ProtocolVersion::Unknown);
}
2
}
b':' | b'$' | b'*' => {
if !second.is_ascii_digit() && second != b'-' {
return (DetectionStatus::NoMatch, ProtocolVersion::Unknown);
}
2
}
b'#' => {
if second != b't' && second != b'f' {
return (DetectionStatus::NoMatch, ProtocolVersion::Unknown);
}
3
}
b'_' => {
if second != b'\r' {
return (DetectionStatus::NoMatch, ProtocolVersion::Unknown);
}
3
}
b',' => {
if !second.is_ascii_digit() && !matches!(second, b'-' | b'+' | b'i' | b'n') {
return (DetectionStatus::NoMatch, ProtocolVersion::Unknown);
}
3
}
b'(' => {
if !second.is_ascii_digit() && second != b'-' {
return (DetectionStatus::NoMatch, ProtocolVersion::Unknown);
}
3
}
b'!' | b'=' | b'%' | b'~' | b'>' => {
if !second.is_ascii_digit() {
return (DetectionStatus::NoMatch, ProtocolVersion::Unknown);
}
3
}
_ => return (DetectionStatus::NoMatch, ProtocolVersion::Unknown),
};
(DetectionStatus::Match, ProtocolVersion::Redis(resp_ver))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_resp2_simple_string() {
assert_eq!(
probe(b"+OK\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(2))
);
}
#[test]
fn test_detect_resp2_error() {
assert_eq!(
probe(b"-ERR unknown\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(2))
);
}
#[test]
fn test_detect_resp2_integer() {
assert_eq!(
probe(b":1000\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(2))
);
}
#[test]
fn test_detect_resp2_bulk_string() {
assert_eq!(
probe(b"$6\r\nfoobar\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(2))
);
}
#[test]
fn test_detect_resp2_array() {
assert_eq!(
probe(b"*3\r\n$3\r\nSET\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(2))
);
}
#[test]
fn test_detect_resp2_negative() {
assert_eq!(
probe(b":-1\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(2))
);
assert_eq!(
probe(b"$-1\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(2))
);
}
#[test]
fn test_detect_resp3_boolean() {
assert_eq!(
probe(b"#t\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(3))
);
assert_eq!(
probe(b"#f\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(3))
);
}
#[test]
fn test_detect_resp3_null() {
assert_eq!(
probe(b"_\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(3))
);
}
#[test]
fn test_detect_resp3_double() {
assert_eq!(
probe(b",3.14\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(3))
);
assert_eq!(
probe(b",inf\r\n"),
(DetectionStatus::Match, ProtocolVersion::Redis(3))
);
}
#[test]
fn test_reject_invalid_second_byte() {
assert_eq!(probe(b"#comment\r\n").0, DetectionStatus::NoMatch);
assert_eq!(probe(b">some text\r\n").0, DetectionStatus::NoMatch);
assert_eq!(probe(b"*text\r\n").0, DetectionStatus::NoMatch);
assert_eq!(probe(b"_x\r\n").0, DetectionStatus::NoMatch);
}
#[test]
fn test_incomplete_single_byte() {
assert_eq!(probe(b"").0, DetectionStatus::Incomplete);
assert_eq!(probe(b"+").0, DetectionStatus::Incomplete);
assert_eq!(probe(b"*").0, DetectionStatus::Incomplete);
assert_eq!(probe(b"#").0, DetectionStatus::Incomplete);
}
#[test]
fn test_reject_non_resp_prefix() {
assert_eq!(probe(b"GET /index.html").0, DetectionStatus::NoMatch);
assert_eq!(probe(b"SSH-2.0-OpenSSH").0, DetectionStatus::NoMatch);
}
}