#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProtocolClassification {
Http2Preface,
TlsLike,
Unknown,
}
const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
pub fn classify_protocol_bytes(input: &[u8]) -> ProtocolClassification {
if input.is_empty() {
return ProtocolClassification::Unknown;
}
if H2_PREFACE.starts_with(input) || input.starts_with(H2_PREFACE) {
return ProtocolClassification::Http2Preface;
}
if is_tls_like(input) {
return ProtocolClassification::TlsLike;
}
ProtocolClassification::Unknown
}
fn is_tls_like(input: &[u8]) -> bool {
if input.len() < 5 {
return false;
}
let content_type = input[0];
let version_major = input[1];
matches!(content_type, 20..=23) && version_major == 3
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classifies_http2_preface() {
assert_eq!(
classify_protocol_bytes(H2_PREFACE),
ProtocolClassification::Http2Preface
);
}
#[test]
fn classifies_tls_like() {
let tls = [22_u8, 3, 3, 0, 42, 1, 0, 0, 38];
assert_eq!(
classify_protocol_bytes(&tls),
ProtocolClassification::TlsLike
);
}
#[test]
fn unknown_for_random_bytes() {
let data = [1_u8, 2, 3, 4, 5, 6];
assert_eq!(
classify_protocol_bytes(&data),
ProtocolClassification::Unknown
);
}
#[test]
fn unknown_for_empty_or_short_frames() {
assert_eq!(
classify_protocol_bytes(&[]),
ProtocolClassification::Unknown
);
assert_eq!(
classify_protocol_bytes(&[22, 3, 1, 0]),
ProtocolClassification::Unknown
);
}
#[test]
fn classifies_http2_when_input_is_prefix() {
assert_eq!(
classify_protocol_bytes(&H2_PREFACE[..8]),
ProtocolClassification::Http2Preface
);
}
}