1use std::io::{Read, Write};
12use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender};
13use std::time::Duration;
14
15use super::{TestRequest, TestResponse};
16use crate::application::Application;
17use crate::websocket::{close_payload, encode_frame, read_raw_frame, take_upgrade, Frame, Message, WebSocket};
18
19pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
21
22const KEY: &str = "dGhlIHNhbXBsZSBub25jZQ==";
24
25pub struct TestWebSocketRequest<'a, A: Application> {
28 request: TestRequest<'a, A>,
29 timeout: Duration,
30}
31
32impl<'a, A: Application> TestWebSocketRequest<'a, A> {
33 pub(super) fn new(request: TestRequest<'a, A>) -> Self {
34 TestWebSocketRequest { request, timeout: DEFAULT_TIMEOUT }
35 }
36
37 pub fn header(mut self, name: &str, value: &str) -> Self {
40 self.request = self.request.header(name, value);
41 self
42 }
43
44 pub fn timeout(mut self, timeout: Duration) -> Self {
47 self.timeout = timeout;
48 self
49 }
50
51 pub fn connect(self) -> Result<TestWebSocket, TestResponse> {
56 let mut request = self.request;
57 for (name, value) in [
58 ("Host", "localhost"),
59 ("Upgrade", "websocket"),
60 ("Connection", "Upgrade"),
61 ("Sec-WebSocket-Key", KEY),
62 ("Sec-WebSocket-Version", "13"),
63 ] {
64 if !request.has_header(name) {
65 request = request.header(name, value);
66 }
67 }
68 let mut response = request.dispatch();
69 let upgrade = take_upgrade(&mut response);
70 let accepted = response
71 .headers
72 .iter()
73 .find(|h| h.name.eq_ignore_ascii_case("Sec-WebSocket-Accept"))
74 .is_some_and(|h| *h.value == *WebSocket::accept_key(KEY));
75 let handshake = TestResponse::from_response(response);
76 let upgrade = match upgrade {
77 Some(upgrade) if handshake.status() == 101 && accepted => upgrade,
78 _ => return Err(handshake),
79 };
80
81 let (to_server, server_in) = mpsc::channel();
82 let (server_out, from_server) = mpsc::channel();
83 let (done_tx, done) = mpsc::channel::<()>();
84 #[cfg(feature = "http2")]
85 let runtime = tokio::runtime::Handle::try_current().ok();
86 let spawned = std::thread::Builder::new().name("rws-test-websocket".to_string()).spawn(move || {
87 #[cfg(feature = "http2")]
90 let _entered = runtime.as_ref().map(|r| r.enter());
91 let reader = PipeReader::new(server_in, None);
92 let writer = PipeWriter(server_out);
93 upgrade.run(Box::new(reader), Box::new(writer));
94 let _ = done_tx.send(());
95 });
96 if spawned.is_err() {
97 return Err(handshake);
98 }
99 Ok(TestWebSocket {
100 handshake,
101 to_server: Some(PipeWriter(to_server)),
102 from_server: PipeReader::new(from_server, Some(self.timeout)),
103 done,
104 timeout: self.timeout,
105 close: None,
106 mask: 0x9E37_79B9,
107 })
108 }
109}
110
111pub struct TestWebSocket {
116 handshake: TestResponse,
117 to_server: Option<PipeWriter>,
118 from_server: PipeReader,
119 done: Receiver<()>,
120 timeout: Duration,
121 close: Option<(Option<u16>, String)>,
122 mask: u32,
123}
124
125impl TestWebSocket {
126 pub fn handshake(&self) -> &TestResponse {
128 &self.handshake
129 }
130
131 #[track_caller]
133 pub fn send_text(&mut self, text: &str) {
134 self.send(Frame::Text(text.to_string())).expect("send a WebSocket text message");
135 }
136
137 #[track_caller]
139 pub fn send_binary(&mut self, data: &[u8]) {
140 self.send(Frame::Binary(data.to_vec())).expect("send a WebSocket binary message");
141 }
142
143 pub fn ping(&mut self, payload: &[u8]) -> Result<(), String> {
146 self.send(Frame::Ping(payload.to_vec()))
147 }
148
149 pub fn send(&mut self, frame: Frame) -> Result<(), String> {
151 let masked = mask_frame(encode_frame(frame), self.next_mask());
152 self.send_raw(&masked)
153 }
154
155 pub fn send_raw(&mut self, bytes: &[u8]) -> Result<(), String> {
158 match self.to_server.as_mut() {
159 Some(writer) => writer.write_all(bytes).map_err(|e| format!("write error: {e}")),
160 None => Err("the test side already closed the connection".to_string()),
161 }
162 }
163
164 fn next_mask(&mut self) -> [u8; 4] {
165 self.mask = self.mask.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
167 self.mask.to_be_bytes()
168 }
169
170 pub fn recv_frame(&mut self) -> Result<Frame, String> {
173 self.recv_frame_fin().map(|(_, frame)| frame)
174 }
175
176 fn recv_frame_fin(&mut self) -> Result<(bool, Frame), String> {
178 let raw = read_raw_frame(&mut self.from_server, WebSocket::MAX_FRAME_PAYLOAD)?;
179 let fin = raw.fin;
180 let frame = match raw.opcode {
181 0x0 => Frame::Continuation { fin: raw.fin, data: raw.payload },
182 0x1 => Frame::Text(String::from_utf8(raw.payload).map_err(|_| "a text frame isn't UTF-8".to_string())?),
183 0x2 => Frame::Binary(raw.payload),
184 0x8 => {
185 let (code, reason) = close_payload(&raw.payload)?;
186 self.close = Some((code, reason.clone()));
187 Frame::Close(code, reason)
188 }
189 0x9 => {
190 let _ = self.send(Frame::Pong(raw.payload.clone()));
191 Frame::Ping(raw.payload)
192 }
193 0xA => Frame::Pong(raw.payload),
194 other => return Err(format!("unknown opcode 0x{other:X} from the handler")),
195 };
196 Ok((fin, frame))
197 }
198
199 pub fn recv(&mut self) -> Result<Option<Message>, String> {
204 if self.close.is_some() {
205 return Ok(None);
206 }
207 let mut message: Option<(bool, Vec<u8>)> = None;
208 loop {
209 let (fin, frame) = self.recv_frame_fin()?;
210 match frame {
211 Frame::Text(text) => message = Some((true, text.into_bytes())),
212 Frame::Binary(data) => message = Some((false, data)),
213 Frame::Continuation { data, .. } => match message.as_mut() {
214 Some((_, buf)) => buf.extend_from_slice(&data),
215 None => return Err("a continuation frame with no message".to_string()),
216 },
217 Frame::Close(code, _) => {
218 let _ = self.send(Frame::Close(code.or(Some(1000)), String::new()));
220 return Ok(None);
221 }
222 Frame::Ping(_) | Frame::Pong(_) => continue,
223 }
224 if !fin {
226 continue;
227 }
228 return match message.take() {
229 Some((true, bytes)) => String::from_utf8(bytes).map(|t| Some(Message::Text(t))).map_err(|_| "a text message isn't UTF-8".to_string()),
230 Some((false, bytes)) => Ok(Some(Message::Binary(bytes))),
231 None => continue,
232 };
233 }
234 }
235
236 #[track_caller]
239 pub fn recv_text(&mut self) -> String {
240 match self.recv() {
241 Ok(Some(Message::Text(text))) => text,
242 other => panic!("expected a WebSocket text message, got {other:?}"),
243 }
244 }
245
246 #[track_caller]
248 pub fn recv_binary(&mut self) -> Vec<u8> {
249 match self.recv() {
250 Ok(Some(Message::Binary(data))) => data,
251 other => panic!("expected a WebSocket binary message, got {other:?}"),
252 }
253 }
254
255 pub fn close_code(&self) -> Option<Option<u16>> {
258 self.close.as_ref().map(|(code, _)| *code)
259 }
260
261 pub fn close(mut self, code: u16, reason: &str) -> Option<u16> {
267 if self.close.is_none() {
268 let _ = self.send(Frame::Close(Some(code), reason.to_string()));
269 while self.close.is_none() {
270 if self.recv_frame().is_err() {
271 break;
272 }
273 }
274 }
275 self.to_server = None;
276 let _ = self.done.recv_timeout(self.timeout);
277 self.close.and_then(|(code, _)| code)
278 }
279
280 pub fn handler_finished(&self, timeout: Duration) -> bool {
282 match self.done.recv_timeout(timeout) {
283 Ok(()) | Err(RecvTimeoutError::Disconnected) => true,
284 Err(RecvTimeoutError::Timeout) => false,
285 }
286 }
287}
288
289fn mask_frame(mut frame: Vec<u8>, key: [u8; 4]) -> Vec<u8> {
291 let offset = match frame[1] & 0x7F {
292 126 => 4,
293 127 => 10,
294 _ => 2,
295 };
296 frame[1] |= 0x80;
297 for (i, byte) in frame[offset..].iter_mut().enumerate() {
298 *byte ^= key[i % 4];
299 }
300 frame.splice(offset..offset, key);
301 frame
302}
303
304struct PipeWriter(Sender<Vec<u8>>);
306
307impl Write for PipeWriter {
308 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
309 self.0
310 .send(buf.to_vec())
311 .map(|_| buf.len())
312 .map_err(|_| std::io::Error::new(std::io::ErrorKind::BrokenPipe, "the other side of the test WebSocket is gone"))
313 }
314
315 fn flush(&mut self) -> std::io::Result<()> {
316 Ok(())
317 }
318}
319
320struct PipeReader {
323 rx: Receiver<Vec<u8>>,
324 buf: Vec<u8>,
325 pos: usize,
326 timeout: Option<Duration>,
327}
328
329impl PipeReader {
330 fn new(rx: Receiver<Vec<u8>>, timeout: Option<Duration>) -> Self {
331 PipeReader { rx, buf: Vec::new(), pos: 0, timeout }
332 }
333}
334
335impl Read for PipeReader {
336 fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
337 while self.pos >= self.buf.len() {
338 let next = match self.timeout {
339 Some(timeout) => self.rx.recv_timeout(timeout).map_err(|e| match e {
340 RecvTimeoutError::Timeout => Some(std::io::Error::new(
341 std::io::ErrorKind::TimedOut,
342 format!("no WebSocket frame from the handler within {timeout:?}"),
343 )),
344 RecvTimeoutError::Disconnected => None,
345 }),
346 None => self.rx.recv().map_err(|_| None),
347 };
348 match next {
349 Ok(chunk) => {
350 self.buf = chunk;
351 self.pos = 0;
352 }
353 Err(Some(e)) => return Err(e),
354 Err(None) => return Ok(0),
355 }
356 }
357 let n = out.len().min(self.buf.len() - self.pos);
358 out[..n].copy_from_slice(&self.buf[self.pos..self.pos + n]);
359 self.pos += n;
360 Ok(n)
361 }
362}