1use std::net::TcpStream;
10use std::thread;
11use std::time::{Duration, Instant};
12
13pub trait LocalService: Sync {
15 fn prefix(&self) -> &[u8];
18
19 fn serve(&self, client: &TcpStream);
22}
23
24const PEEK_PAUSE: Duration = Duration::from_millis(2);
26
27pub(crate) fn starts_with(client: &TcpStream, prefix: &[u8], deadline: Instant) -> bool {
30 let mut seen = vec![0u8; prefix.len()];
31 loop {
32 let n = match client.peek(&mut seen) {
33 Ok(0) | Err(_) => return false,
34 Ok(n) => n,
35 };
36 if seen[..n] != prefix[..n] {
37 return false;
38 }
39 if n == prefix.len() {
40 return true;
41 }
42 if Instant::now() >= deadline {
43 return false;
44 }
45 thread::sleep(PEEK_PAUSE);
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use std::io::{Read as _, Write as _};
53 use std::net::{Shutdown, TcpListener};
54
55 use super::*;
56
57 fn pair() -> (TcpStream, TcpStream) {
58 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
59 let client = TcpStream::connect(listener.local_addr().unwrap()).unwrap();
60 let (server, _) = listener.accept().unwrap();
61 (server, client)
62 }
63
64 fn soon() -> Instant {
65 Instant::now() + Duration::from_millis(500)
66 }
67
68 #[test]
69 fn a_prefix_is_matched_without_consuming_it() {
70 let (mut server, mut client) = pair();
71 client.write_all(b"MOAT-X/1\nrest").unwrap();
72 assert!(starts_with(&server, b"MOAT-X/1\n", soon()));
73 let mut all = Vec::new();
74 client.shutdown(Shutdown::Write).unwrap();
75 server.read_to_end(&mut all).unwrap();
76 assert_eq!(all, b"MOAT-X/1\nrest");
77 }
78
79 #[test]
80 fn a_prefix_sent_in_pieces_is_matched() {
81 let (server, mut client) = pair();
82 let writer = thread::spawn(move || {
83 client.write_all(b"MOAT").unwrap();
84 thread::sleep(Duration::from_millis(30));
85 client.write_all(b"-X/1\n").unwrap();
86 client
87 });
88 assert!(starts_with(&server, b"MOAT-X/1\n", soon()));
89 drop(writer.join());
90 }
91
92 #[test]
93 fn http_a_short_stream_and_silence_are_not_matched() {
94 let (server, mut client) = pair();
95 client.write_all(b"GET http://a/ HTTP/1.1\r\n").unwrap();
96 assert!(!starts_with(&server, b"MOAT-X/1\n", soon()));
97 let (server, mut client) = pair();
98 client.write_all(b"MOA").unwrap();
99 client.shutdown(Shutdown::Write).unwrap();
100 thread::sleep(Duration::from_millis(20));
101 assert!(!starts_with(&server, b"MOAT-X/1\n", soon()));
102 let (server, _client) = pair();
103 server
104 .set_read_timeout(Some(Duration::from_millis(50)))
105 .unwrap();
106 let deadline = Instant::now() + Duration::from_millis(100);
107 assert!(!starts_with(&server, b"MOAT-X/1\n", deadline));
108 }
109}