1use std::time::Duration;
2
3use tokio::io::AsyncReadExt;
4
5use crate::detect::{DetectResult, ProtocolDetector};
6use crate::replay::ReplayStream;
7use crate::{BoxStream, ProtocolId};
8
9const DEFAULT_MAX_SNIFF: usize = 8 * 1024;
10
11#[derive(Debug, thiserror::Error)]
13pub enum DispatchError {
14 #[error("handshake timeout")]
15 Timeout,
16 #[error("sniff buffer full ({0} bytes) with no protocol match")]
17 BufferOverflow(usize),
18 #[error("no protocol matched the connection")]
19 NoMatch,
20 #[error("IO error: {0}")]
21 Io(#[from] std::io::Error),
22}
23
24pub struct ProtocolDispatcher {
27 detectors: Vec<Box<dyn ProtocolDetector>>,
28 max_sniff: usize,
29 handshake_timeout: Duration,
30}
31
32impl ProtocolDispatcher {
33 pub fn new(
40 detectors: Vec<Box<dyn ProtocolDetector>>,
41 max_sniff: usize,
42 handshake_timeout: Duration,
43 ) -> Self {
44 Self {
45 detectors,
46 max_sniff,
47 handshake_timeout,
48 }
49 }
50
51 pub fn with_defaults(
53 detectors: Vec<Box<dyn ProtocolDetector>>,
54 handshake_timeout: Duration,
55 ) -> Self {
56 Self::new(detectors, DEFAULT_MAX_SNIFF, handshake_timeout)
57 }
58
59 pub fn protocol_ids(&self) -> Vec<ProtocolId> {
61 self.detectors.iter().map(|d| d.id()).collect()
62 }
63
64 pub async fn dispatch(
74 &self,
75 stream: BoxStream,
76 ) -> Result<(ProtocolId, ReplayStream), DispatchError> {
77 let mut replay = ReplayStream::with_max_buffer(stream, self.max_sniff);
78
79 let mut read_buf = [0u8; 4096];
80 let mut total_read: usize = 0;
81
82 let result = tokio::time::timeout(self.handshake_timeout, async {
83 loop {
84 if total_read >= self.max_sniff {
86 return Err(DispatchError::BufferOverflow(self.max_sniff));
87 }
88
89 let to_read = (self.max_sniff - total_read).min(read_buf.len());
91 let n = replay
92 .read(&mut read_buf[..to_read])
93 .await
94 .map_err(DispatchError::Io)?;
95
96 if n == 0 {
97 break;
99 }
100
101 total_read += n;
102 let prefix = &replay.buffer()[..total_read];
103
104 let mut need_more_min = None;
106 for detector in &self.detectors {
107 match detector.detect(prefix) {
108 DetectResult::Match { confidence: _ } => {
109 replay.finish_sniff();
110 return Ok((detector.id(), replay));
111 }
112 DetectResult::NeedMore { minimum } => {
113 if need_more_min.is_none_or(|m| minimum < m) {
114 need_more_min = Some(minimum);
115 }
116 }
117 DetectResult::NoMatch => {}
118 }
119 }
120
121 if need_more_min.is_some() {
124 if total_read < self.max_sniff {
125 continue;
126 }
127 return Err(DispatchError::BufferOverflow(self.max_sniff));
129 }
130
131 break;
133 }
134
135 Err(DispatchError::NoMatch)
137 });
138
139 match result.await {
140 Ok(result) => result,
141 Err(_elapsed) => Err(DispatchError::Timeout),
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::detect::PrefixDetector;
150 use tokio::io::{AsyncReadExt, AsyncWriteExt};
151
152 fn make_dispatcher(handshake_timeout: Duration) -> ProtocolDispatcher {
153 let detectors: Vec<Box<dyn ProtocolDetector>> = vec![
154 Box::new(PrefixDetector::new(ProtocolId::Http, b"GET ".to_vec())),
155 Box::new(PrefixDetector::new(ProtocolId::Socks5, b"\x05".to_vec())),
156 Box::new(PrefixDetector::new(ProtocolId::Http, b"CUSTOM-".to_vec())),
157 ];
158 ProtocolDispatcher::with_defaults(detectors, handshake_timeout)
159 }
160
161 #[tokio::test]
162 async fn test_dispatch_http() {
163 let dispatcher = make_dispatcher(Duration::from_secs(5));
164 let (mut tx, rx) = tokio::io::duplex(1024);
165
166 tx.write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
167 .await
168 .unwrap();
169
170 let (proto, mut replay) = dispatcher.dispatch(Box::new(rx)).await.unwrap();
171 assert_eq!(proto, ProtocolId::Http);
172
173 assert_eq!(
175 replay.buffer(),
176 b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
177 );
178
179 tx.write_all(b"more data").await.unwrap();
181 tx.shutdown().await.unwrap();
182
183 let mut buf = [0u8; 1024];
184 let n = replay.read(&mut buf).await.unwrap();
185 assert_eq!(&buf[..n], b"more data");
186 }
187
188 #[tokio::test]
189 async fn test_dispatch_socks5() {
190 let dispatcher = make_dispatcher(Duration::from_secs(5));
191 let (mut tx, rx) = tokio::io::duplex(1024);
192
193 tx.write_all(b"\x05\x01\x00").await.unwrap();
194
195 let (proto, _) = dispatcher.dispatch(Box::new(rx)).await.unwrap();
196 assert_eq!(proto, ProtocolId::Socks5);
197 }
198
199 #[tokio::test]
200 async fn test_dispatch_custom_prefix_as_http() {
201 let dispatcher = make_dispatcher(Duration::from_secs(5));
202 let (mut tx, rx) = tokio::io::duplex(1024);
203
204 tx.write_all(b"CUSTOM-payload").await.unwrap();
205
206 let (proto, _) = dispatcher.dispatch(Box::new(rx)).await.unwrap();
207 assert_eq!(proto, ProtocolId::Http);
208 }
209
210 #[tokio::test]
211 async fn test_dispatch_no_match() {
212 let dispatcher = make_dispatcher(Duration::from_secs(5));
213 let (tx, rx) = tokio::io::duplex(1024);
214
215 let jh = tokio::spawn(async move {
216 let mut stream = tx;
217 stream.write_all(b"\xFF\xFE\xFD").await.unwrap();
218 stream.shutdown().await.unwrap();
219 });
220
221 let result = dispatcher.dispatch(Box::new(rx)).await;
222 assert!(result.is_err());
223 match result.unwrap_err() {
224 DispatchError::NoMatch => {}
225 e => panic!("expected NoMatch, got {:?}", e),
226 }
227 jh.await.unwrap();
228 }
229
230 #[tokio::test]
231 async fn test_dispatch_timeout() {
232 let dispatcher = make_dispatcher(Duration::from_millis(50));
233 let (_tx, rx) = tokio::io::duplex(1024);
234
235 let result = dispatcher.dispatch(Box::new(rx)).await;
237 assert!(matches!(result, Err(DispatchError::Timeout)));
238 }
239
240 #[tokio::test]
241 async fn test_dispatch_buffer_overflow() {
242 let detectors: Vec<Box<dyn ProtocolDetector>> = vec![Box::new(PrefixDetector::new(
243 ProtocolId::Http,
244 b"NEVER_MATCH_ANYTHING_HERE_FOREVER".to_vec(),
245 ))];
246 let dispatcher = ProtocolDispatcher::new(
247 detectors,
248 16, Duration::from_secs(5),
250 );
251
252 let (tx, rx) = tokio::io::duplex(1024);
253
254 let jh = tokio::spawn(async move {
255 let mut stream = tx;
256 stream.write_all(b"AAAA_BBBB_CCCC_DDDD_EEEE").await.unwrap();
257 stream.shutdown().await.unwrap();
258 });
259
260 let result = dispatcher.dispatch(Box::new(rx)).await;
261 assert!(matches!(result, Err(DispatchError::BufferOverflow(16))));
262 jh.await.unwrap();
263 }
264
265 #[tokio::test]
266 async fn test_dispatch_ordered_detection() {
267 let detectors: Vec<Box<dyn ProtocolDetector>> = vec![
269 Box::new(PrefixDetector::new(ProtocolId::Http, b"\x05".to_vec())),
270 Box::new(PrefixDetector::new(ProtocolId::Socks5, b"\x05".to_vec())),
271 ];
272 let dispatcher = ProtocolDispatcher::with_defaults(detectors, Duration::from_secs(5));
273
274 let (mut tx, rx) = tokio::io::duplex(1024);
275 tx.write_all(b"\x05").await.unwrap();
276
277 let (proto, _) = dispatcher.dispatch(Box::new(rx)).await.unwrap();
278 assert_eq!(proto, ProtocolId::Http);
280 }
281
282 #[tokio::test]
283 async fn test_dispatch_fragmented_detection() {
284 let dispatcher = make_dispatcher(Duration::from_secs(5));
285 let (tx, rx) = tokio::io::duplex(1024);
286
287 let jh = tokio::spawn(async move {
288 let mut stream = tx;
289 stream.write_all(b"GE").await.unwrap();
290 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
291 stream.write_all(b"T /").await.unwrap();
292 stream.shutdown().await.unwrap();
293 });
294
295 let (proto, _) = dispatcher.dispatch(Box::new(rx)).await.unwrap();
296 assert_eq!(proto, ProtocolId::Http);
297
298 jh.await.unwrap();
299 }
300
301 #[tokio::test]
302 async fn test_dispatch_unknown_input_closes() {
303 let dispatcher = make_dispatcher(Duration::from_secs(5));
304 let (tx, rx) = tokio::io::duplex(1024);
305
306 drop(tx);
308
309 let result = dispatcher.dispatch(Box::new(rx)).await;
310 assert!(matches!(result, Err(DispatchError::NoMatch)));
312 }
313
314 #[tokio::test]
315 async fn test_dispatch_stream_closed_mid_detection() {
316 let dispatcher = make_dispatcher(Duration::from_secs(5));
317 let (mut tx, rx) = tokio::io::duplex(1024);
318
319 tx.write_all(b"GE").await.unwrap();
321 drop(tx);
322
323 let result = dispatcher.dispatch(Box::new(rx)).await;
324 assert!(matches!(result, Err(DispatchError::NoMatch)));
325 }
326
327 #[tokio::test]
328 async fn test_dispatch_protocol_ids() {
329 let dispatcher = make_dispatcher(Duration::from_secs(5));
330 let ids = dispatcher.protocol_ids();
331 assert_eq!(
332 ids,
333 vec![ProtocolId::Http, ProtocolId::Socks5, ProtocolId::Http]
334 );
335 }
336}