Skip to main content

vane_core/
protocol_detect.rs

1//! Public types for protocol detection results stored on
2//! `ConnContext.user` by the listener-side peek prelude.
3//!
4//! See `spec/crates/engine.md` § _Protocol detection_. The
5//! detector functions themselves (which run rustls's `Acceptor` to
6//! parse a `ClientHello`) live in `vane-engine` since rustls is an
7//! engine-level dependency. Predicates and middleware in `vane-core`
8//! reach the buffer + parsed fields through the types defined here.
9
10use bytes::Bytes;
11
12#[derive(Clone, Debug)]
13pub struct PeekResult {
14	pub buffer: Bytes,
15	pub detected: Option<DetectedProtocol>,
16	pub tls: Option<TlsClientHello>,
17}
18
19#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
20pub enum DetectedProtocol {
21	TlsClientHello,
22	Http1,
23	Http2Preface,
24	QuicInitial,
25	Dns,
26	Unknown,
27}
28
29#[derive(Clone, Debug, Default)]
30pub struct TlsClientHello {
31	pub sni: Option<String>,
32	/// ALPN protocol IDs offered by the client in the `ClientHello`.
33	pub alpn: Vec<Vec<u8>>,
34}
35
36/// Maximum number of bytes the listener-side peek prelude accumulates
37/// before declaring the connection's prefix `Unknown`. Mirrors
38/// `spec/crates/engine.md` § _Protocol detection_'s 8 KiB cap.
39pub const MAX_PEEK_BYTES: usize = 8 * 1024;
40
41#[cfg(test)]
42mod tests {
43	use super::*;
44
45	#[test]
46	fn peek_result_is_clone_send_sync_static() {
47		fn assert_bounds<T: Clone + Send + Sync + 'static>() {}
48		assert_bounds::<PeekResult>();
49	}
50
51	#[test]
52	fn detected_protocol_variants_are_distinct() {
53		let all = [
54			DetectedProtocol::TlsClientHello,
55			DetectedProtocol::Http1,
56			DetectedProtocol::Http2Preface,
57			DetectedProtocol::QuicInitial,
58			DetectedProtocol::Dns,
59			DetectedProtocol::Unknown,
60		];
61		for (i, a) in all.iter().enumerate() {
62			for (j, b) in all.iter().enumerate() {
63				assert_eq!(a == b, i == j);
64			}
65		}
66	}
67
68	#[test]
69	fn tls_client_hello_default_is_empty() {
70		let h = TlsClientHello::default();
71		assert!(h.sni.is_none());
72		assert!(h.alpn.is_empty());
73	}
74
75	#[test]
76	fn max_peek_bytes_matches_spec() {
77		assert_eq!(MAX_PEEK_BYTES, 8 * 1024);
78	}
79}