1#[cfg(test)]
43mod tests;
44mod connection;
45
46pub use connection::{Message, WebSocketConnection, WebSocketSender};
47#[doc(hidden)]
48pub use connection::{take_upgrade, upgrade_head};
49#[cfg(feature = "http2")]
50#[doc(hidden)]
51pub use connection::hand_over_async;
52pub use connection::{MAX_MESSAGE, UPGRADE_TTL};
53
54use std::io::{Read, Write};
55
56use crate::header::Header;
57use crate::http::VERSION;
58use crate::request::Request;
59use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
60
61#[derive(Debug, PartialEq, Eq)]
63pub enum Frame {
64 Text(String),
66 Binary(Vec<u8>),
68 Ping(Vec<u8>),
70 Pong(Vec<u8>),
72 Close(Option<u16>, String),
74 Continuation { fin: bool, data: Vec<u8> },
76}
77
78pub struct WebSocket;
80
81impl WebSocket {
82 const MAGIC: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
83
84 pub const MAX_FRAME_PAYLOAD: u64 = 16 * 1024 * 1024;
88
89 pub fn is_upgrade_request(request: &Request) -> bool {
94 let upgrade = request.get_header("Upgrade".to_string());
95 let connection = request.get_header("Connection".to_string());
96 let key = request.get_header("Sec-WebSocket-Key".to_string());
97
98 upgrade.map(|h| h.value.to_lowercase() == "websocket").unwrap_or(false)
99 && connection.map(|h| h.value.to_lowercase().contains("upgrade")).unwrap_or(false)
100 && key.is_some()
101 }
102
103 pub fn handshake_response(request: &Request) -> Result<Response, String> {
110 let key_header = request.get_header("Sec-WebSocket-Key".to_string())
111 .ok_or_else(|| "missing Sec-WebSocket-Key header".to_string())?;
112 let accept = Self::accept_key(&key_header.value);
113
114 let mut response = Response {
115 http_version: VERSION.http_1_1.to_string(),
116 status_code: *STATUS_CODE_REASON_PHRASE.n101_switching_protocols.status_code,
117 reason_phrase: STATUS_CODE_REASON_PHRASE.n101_switching_protocols.reason_phrase.to_string(),
118 headers: vec![
119 Header { name: "Upgrade".into(), value: "websocket".into() },
120 Header { name: "Connection".into(), value: "Upgrade".into() },
121 Header { name: "Sec-WebSocket-Accept".into(), value: accept.into() },
122 ],
123 content_range_list: vec![],
124 stream_file: None,
125 stream_pipe: None,
126 trailers: None,
127 };
128
129 if let Some(proto) = request.get_header("Sec-WebSocket-Protocol".to_string()) {
130 response.headers.push(Header {
131 name: "Sec-WebSocket-Protocol".into(),
132 value: proto.value.split(',').next().unwrap_or("").trim().to_string().into(),
133 });
134 }
135
136 Ok(response)
137 }
138
139 pub fn accept_key(client_key: &str) -> String {
142 let mut data = client_key.as_bytes().to_vec();
143 data.extend_from_slice(Self::MAGIC.as_bytes());
144 let hash = sha1(&data);
145 base64_encode(&hash)
146 }
147
148 pub fn read_frame(stream: &mut impl Read) -> Result<Frame, String> {
159 Self::read_frame_limited(stream, Self::MAX_FRAME_PAYLOAD)
160 }
161
162 pub fn read_frame_limited(stream: &mut impl Read, max_payload: u64) -> Result<Frame, String> {
166 let RawFrame { fin, opcode, payload, .. } = read_raw_frame(stream, max_payload)?;
167 let frame = match opcode {
168 0x0 => Frame::Continuation { fin, data: payload },
169 0x1 => {
170 let text = String::from_utf8(payload)
171 .map_err(|_| "text frame contains invalid UTF-8".to_string())?;
172 Frame::Text(text)
173 }
174 0x2 => Frame::Binary(payload),
175 0x8 => {
176 let (code, reason) = close_payload(&payload)?;
177 Frame::Close(code, reason)
178 }
179 0x9 => Frame::Ping(payload),
180 0xA => Frame::Pong(payload),
181 n => return Err(format!("unknown opcode: 0x{:X}", n)),
182 };
183
184 Ok(frame)
185 }
186
187 pub fn write_frame(stream: &mut impl Write, frame: Frame) -> Result<(), String> {
189 stream.write_all(&encode_frame(frame)).map_err(|e| format!("write error: {}", e))?;
190 stream.flush().map_err(|e| format!("flush error: {}", e))?;
191 Ok(())
192 }
193
194 pub fn send_text(stream: &mut impl Write, text: &str) -> Result<(), String> {
196 Self::write_frame(stream, Frame::Text(text.to_string()))
197 }
198
199 pub fn send_close(stream: &mut impl Write, code: u16, reason: &str) -> Result<(), String> {
201 Self::write_frame(stream, Frame::Close(Some(code), reason.to_string()))
202 }
203
204 pub fn send_pong(stream: &mut impl Write, payload: Vec<u8>) -> Result<(), String> {
206 Self::write_frame(stream, Frame::Pong(payload))
207 }
208}
209
210#[doc(hidden)]
212pub struct RawFrame {
213 #[doc(hidden)]
214 pub fin: bool,
215 #[doc(hidden)]
217 pub rsv: u8,
218 #[doc(hidden)]
219 pub opcode: u8,
220 #[doc(hidden)]
221 pub masked: bool,
222 #[doc(hidden)]
223 pub payload: Vec<u8>,
224}
225
226#[doc(hidden)]
230pub fn close_payload(payload: &[u8]) -> Result<(Option<u16>, String), String> {
231 let code = if payload.len() >= 2 { Some(u16::from_be_bytes([payload[0], payload[1]])) } else { None };
232 let reason = if payload.len() > 2 {
233 String::from_utf8(payload[2..].to_vec()).map_err(|_| "close frame reason contains invalid UTF-8".to_string())?
234 } else {
235 String::new()
236 };
237 Ok((code, reason))
238}
239
240#[doc(hidden)]
243pub fn read_raw_frame(stream: &mut impl Read, max_payload: u64) -> Result<RawFrame, String> {
244 let mut header = [0u8; 2];
245 read_exact(stream, &mut header)?;
246
247 let fin = (header[0] & 0x80) != 0;
248 let rsv = (header[0] >> 4) & 0x07;
249 let opcode = header[0] & 0x0F;
250 let masked = (header[1] & 0x80) != 0;
251 let payload_len_byte = (header[1] & 0x7F) as usize;
252
253 let payload_len: u64 = match payload_len_byte {
254 126 => {
255 let mut ext = [0u8; 2];
256 read_exact(stream, &mut ext)?;
257 u16::from_be_bytes(ext) as u64
258 }
259 127 => {
260 let mut ext = [0u8; 8];
261 read_exact(stream, &mut ext)?;
262 u64::from_be_bytes(ext)
263 }
264 n => n as u64,
265 };
266 if payload_len > max_payload {
267 return Err(format!("frame payload of {payload_len} bytes exceeds the {max_payload}-byte limit"));
268 }
269
270 let mask_key = if masked {
271 let mut mk = [0u8; 4];
272 read_exact(stream, &mut mk)?;
273 Some(mk)
274 } else {
275 None
276 };
277
278 let mut payload = Vec::new();
279 if payload_len > 0 {
280 stream
281 .take(payload_len)
282 .read_to_end(&mut payload)
283 .map_err(|e| format!("read error: {}", e))?;
284 if payload.len() as u64 != payload_len {
285 return Err("read error: stream ended inside a frame".to_string());
286 }
287 }
288
289 if let Some(key) = mask_key {
290 for (i, byte) in payload.iter_mut().enumerate() {
291 *byte ^= key[i % 4];
292 }
293 }
294
295 Ok(RawFrame { fin, rsv, opcode, masked, payload })
296}
297
298#[doc(hidden)]
300pub fn encode_frame(frame: Frame) -> Vec<u8> {
301 let (opcode, payload, fin) = match frame {
302 Frame::Text(s) => (0x1u8, s.into_bytes(), true),
303 Frame::Binary(b) => (0x2, b, true),
304 Frame::Ping(b) => (0x9, b, true),
305 Frame::Pong(b) => (0xA, b, true),
306 Frame::Close(code, reason) => {
307 let mut payload = Vec::new();
308 if let Some(c) = code {
309 payload.extend_from_slice(&c.to_be_bytes());
310 payload.extend_from_slice(reason.as_bytes());
311 }
312 (0x8, payload, true)
313 }
314 Frame::Continuation { fin, data } => (0x0, data, fin),
315 };
316
317 let fin_bit: u8 = if fin { 0x80 } else { 0x00 };
318 let payload_len = payload.len();
319 let mut out = Vec::with_capacity(10 + payload_len);
320 out.push(fin_bit | opcode);
321 match payload_len {
322 0..=125 => out.push(payload_len as u8),
323 126..=65535 => {
324 out.push(126u8);
325 out.extend_from_slice(&(payload_len as u16).to_be_bytes());
326 }
327 _ => {
328 out.push(127u8);
329 out.extend_from_slice(&(payload_len as u64).to_be_bytes());
330 }
331 }
332 out.extend_from_slice(&payload);
333 out
334}
335
336fn read_exact<R: Read>(r: &mut R, buf: &mut [u8]) -> Result<(), String> {
339 r.read_exact(buf).map_err(|e| format!("read error: {}", e))
340}
341
342#[doc(hidden)]
344pub fn sha1(data: &[u8]) -> [u8; 20] {
345 let mut h = [0x67452301u32, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
346
347 let msg_len = data.len();
348 let mut msg = data.to_vec();
349 msg.push(0x80);
350 while msg.len() % 64 != 56 {
351 msg.push(0x00);
352 }
353 msg.extend_from_slice(&((msg_len as u64) * 8).to_be_bytes());
354
355 for chunk in msg.chunks(64) {
356 let mut w = [0u32; 80];
357 for i in 0..16 {
358 w[i] = u32::from_be_bytes([chunk[i*4], chunk[i*4+1], chunk[i*4+2], chunk[i*4+3]]);
359 }
360 for i in 16..80 {
361 w[i] = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]).rotate_left(1);
362 }
363
364 let (mut a, mut b, mut c, mut d, mut e) = (h[0], h[1], h[2], h[3], h[4]);
365
366 for i in 0..80 {
367 let (f, k) = match i {
368 0..=19 => ((b & c) | (!b & d), 0x5A827999u32),
369 20..=39 => (b ^ c ^ d, 0x6ED9EBA1u32),
370 40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDCu32),
371 _ => (b ^ c ^ d, 0xCA62C1D6u32),
372 };
373 let temp = a.rotate_left(5)
374 .wrapping_add(f)
375 .wrapping_add(e)
376 .wrapping_add(k)
377 .wrapping_add(w[i]);
378 e = d; d = c; c = b.rotate_left(30); b = a; a = temp;
379 }
380
381 h[0] = h[0].wrapping_add(a);
382 h[1] = h[1].wrapping_add(b);
383 h[2] = h[2].wrapping_add(c);
384 h[3] = h[3].wrapping_add(d);
385 h[4] = h[4].wrapping_add(e);
386 }
387
388 let mut out = [0u8; 20];
389 for (i, v) in h.iter().enumerate() {
390 out[i*4..(i+1)*4].copy_from_slice(&v.to_be_bytes());
391 }
392 out
393}
394
395#[doc(hidden)]
397pub fn base64_encode(data: &[u8]) -> String {
398 const T: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
399 let mut out = Vec::with_capacity((data.len() + 2) / 3 * 4);
400 for chunk in data.chunks(3) {
401 let b0 = chunk[0] as u32;
402 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
403 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
404 let n = (b0 << 16) | (b1 << 8) | b2;
405 out.push(T[((n >> 18) & 0x3F) as usize]);
406 out.push(T[((n >> 12) & 0x3F) as usize]);
407 out.push(if chunk.len() > 1 { T[((n >> 6) & 0x3F) as usize] } else { b'=' });
408 out.push(if chunk.len() > 2 { T[(n & 0x3F) as usize] } else { b'=' });
409 }
410 String::from_utf8(out).unwrap()
411}