Skip to main content

http_handle/
protocol_state.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (c) 2026 Sebastien Rousseau
3
4//! Protocol byte-classification helpers for fuzzing and conformance tests.
5
6/// Classification outcome for input protocol bytes.
7///
8/// # Examples
9///
10/// ```rust
11/// use http_handle::protocol_state::ProtocolClassification;
12/// assert_eq!(ProtocolClassification::Unknown as u8, ProtocolClassification::Unknown as u8);
13/// ```
14///
15/// # Panics
16///
17/// This type does not panic.
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum ProtocolClassification {
20    /// Input looks like HTTP/2 client preface.
21    Http2Preface,
22    /// Input looks like TLS record stream.
23    TlsLike,
24    /// Input is unclassified or incomplete.
25    Unknown,
26}
27
28const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
29
30/// Classifies protocol bytes without panicking.
31///
32/// # Examples
33///
34/// ```rust
35/// use http_handle::protocol_state::{classify_protocol_bytes, ProtocolClassification};
36/// let c = classify_protocol_bytes(b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
37/// assert_eq!(c, ProtocolClassification::Http2Preface);
38/// ```
39///
40/// # Panics
41///
42/// This function does not panic.
43pub fn classify_protocol_bytes(input: &[u8]) -> ProtocolClassification {
44    if input.is_empty() {
45        return ProtocolClassification::Unknown;
46    }
47    if H2_PREFACE.starts_with(input) || input.starts_with(H2_PREFACE) {
48        return ProtocolClassification::Http2Preface;
49    }
50    if is_tls_like(input) {
51        return ProtocolClassification::TlsLike;
52    }
53    ProtocolClassification::Unknown
54}
55
56fn is_tls_like(input: &[u8]) -> bool {
57    if input.len() < 5 {
58        return false;
59    }
60    let content_type = input[0];
61    let version_major = input[1];
62    matches!(content_type, 20..=23) && version_major == 3
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn classifies_http2_preface() {
71        assert_eq!(
72            classify_protocol_bytes(H2_PREFACE),
73            ProtocolClassification::Http2Preface
74        );
75    }
76
77    #[test]
78    fn classifies_tls_like() {
79        let tls = [22_u8, 3, 3, 0, 42, 1, 0, 0, 38];
80        assert_eq!(
81            classify_protocol_bytes(&tls),
82            ProtocolClassification::TlsLike
83        );
84    }
85
86    #[test]
87    fn unknown_for_random_bytes() {
88        let data = [1_u8, 2, 3, 4, 5, 6];
89        assert_eq!(
90            classify_protocol_bytes(&data),
91            ProtocolClassification::Unknown
92        );
93    }
94
95    #[test]
96    fn unknown_for_empty_or_short_frames() {
97        assert_eq!(
98            classify_protocol_bytes(&[]),
99            ProtocolClassification::Unknown
100        );
101        assert_eq!(
102            classify_protocol_bytes(&[22, 3, 1, 0]),
103            ProtocolClassification::Unknown
104        );
105    }
106
107    #[test]
108    fn classifies_http2_when_input_is_prefix() {
109        assert_eq!(
110            classify_protocol_bytes(&H2_PREFACE[..8]),
111            ProtocolClassification::Http2Preface
112        );
113    }
114}