rust_web_server/websocket/
mod.rs1#[cfg(test)]
42mod tests;
43
44use std::io::{Read, Write};
45
46use crate::header::Header;
47use crate::http::VERSION;
48use crate::request::Request;
49use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
50
51#[derive(Debug, PartialEq, Eq)]
53pub enum Frame {
54 Text(String),
56 Binary(Vec<u8>),
58 Ping(Vec<u8>),
60 Pong(Vec<u8>),
62 Close(Option<u16>, String),
64 Continuation { fin: bool, data: Vec<u8> },
66}
67
68pub struct WebSocket;
70
71impl WebSocket {
72 const MAGIC: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
73
74 pub const MAX_FRAME_PAYLOAD: u64 = 16 * 1024 * 1024;
78
79 pub fn is_upgrade_request(request: &Request) -> bool {
84 let upgrade = request.get_header("Upgrade".to_string());
85 let connection = request.get_header("Connection".to_string());
86 let key = request.get_header("Sec-WebSocket-Key".to_string());
87
88 upgrade.map(|h| h.value.to_lowercase() == "websocket").unwrap_or(false)
89 && connection.map(|h| h.value.to_lowercase().contains("upgrade")).unwrap_or(false)
90 && key.is_some()
91 }
92
93 pub fn handshake_response(request: &Request) -> Result<Response, String> {
100 let key_header = request.get_header("Sec-WebSocket-Key".to_string())
101 .ok_or_else(|| "missing Sec-WebSocket-Key header".to_string())?;
102 let accept = Self::accept_key(&key_header.value);
103
104 let mut response = Response {
105 http_version: VERSION.http_1_1.to_string(),
106 status_code: *STATUS_CODE_REASON_PHRASE.n101_switching_protocols.status_code,
107 reason_phrase: STATUS_CODE_REASON_PHRASE.n101_switching_protocols.reason_phrase.to_string(),
108 headers: vec![
109 Header { name: "Upgrade".into(), value: "websocket".into() },
110 Header { name: "Connection".into(), value: "Upgrade".into() },
111 Header { name: "Sec-WebSocket-Accept".into(), value: accept.into() },
112 ],
113 content_range_list: vec![],
114 stream_file: None,
115 stream_pipe: None,
116 trailers: None,
117 };
118
119 if let Some(proto) = request.get_header("Sec-WebSocket-Protocol".to_string()) {
120 response.headers.push(Header {
121 name: "Sec-WebSocket-Protocol".into(),
122 value: proto.value.split(',').next().unwrap_or("").trim().to_string().into(),
123 });
124 }
125
126 Ok(response)
127 }
128
129 pub fn accept_key(client_key: &str) -> String {
132 let mut data = client_key.as_bytes().to_vec();
133 data.extend_from_slice(Self::MAGIC.as_bytes());
134 let hash = sha1(&data);
135 base64_encode(&hash)
136 }
137
138 pub fn read_frame(stream: &mut impl Read) -> Result<Frame, String> {
149 Self::read_frame_limited(stream, Self::MAX_FRAME_PAYLOAD)
150 }
151
152 pub fn read_frame_limited(stream: &mut impl Read, max_payload: u64) -> Result<Frame, String> {
156 let mut header = [0u8; 2];
157 read_exact(stream, &mut header)?;
158
159 let fin = (header[0] & 0x80) != 0;
160 let opcode = header[0] & 0x0F;
161 let masked = (header[1] & 0x80) != 0;
162 let payload_len_byte = (header[1] & 0x7F) as usize;
163
164 let payload_len: u64 = match payload_len_byte {
165 126 => {
166 let mut ext = [0u8; 2];
167 read_exact(stream, &mut ext)?;
168 u16::from_be_bytes(ext) as u64
169 }
170 127 => {
171 let mut ext = [0u8; 8];
172 read_exact(stream, &mut ext)?;
173 u64::from_be_bytes(ext)
174 }
175 n => n as u64,
176 };
177 if payload_len > max_payload {
178 return Err(format!("frame payload of {payload_len} bytes exceeds the {max_payload}-byte limit"));
179 }
180
181 let mask_key = if masked {
182 let mut mk = [0u8; 4];
183 read_exact(stream, &mut mk)?;
184 Some(mk)
185 } else {
186 None
187 };
188
189 let mut payload = Vec::new();
190 if payload_len > 0 {
191 stream
192 .take(payload_len)
193 .read_to_end(&mut payload)
194 .map_err(|e| format!("read error: {}", e))?;
195 if payload.len() as u64 != payload_len {
196 return Err("read error: stream ended inside a frame".to_string());
197 }
198 }
199
200 if let Some(key) = mask_key {
201 for (i, byte) in payload.iter_mut().enumerate() {
202 *byte ^= key[i % 4];
203 }
204 }
205
206 let frame = match opcode {
207 0x0 => Frame::Continuation { fin, data: payload },
208 0x1 => {
209 let text = String::from_utf8(payload)
210 .map_err(|_| "text frame contains invalid UTF-8".to_string())?;
211 Frame::Text(text)
212 }
213 0x2 => Frame::Binary(payload),
214 0x8 => {
215 let code = if payload.len() >= 2 {
216 Some(u16::from_be_bytes([payload[0], payload[1]]))
217 } else {
218 None
219 };
220 let reason = if payload.len() > 2 {
221 String::from_utf8_lossy(&payload[2..]).into_owned()
222 } else {
223 String::new()
224 };
225 Frame::Close(code, reason)
226 }
227 0x9 => Frame::Ping(payload),
228 0xA => Frame::Pong(payload),
229 n => return Err(format!("unknown opcode: 0x{:X}", n)),
230 };
231
232 Ok(frame)
233 }
234
235 pub fn write_frame(stream: &mut impl Write, frame: Frame) -> Result<(), String> {
237 let (opcode, payload, fin) = match frame {
238 Frame::Text(s) => (0x1u8, s.into_bytes(), true),
239 Frame::Binary(b) => (0x2, b, true),
240 Frame::Ping(b) => (0x9, b, true),
241 Frame::Pong(b) => (0xA, b, true),
242 Frame::Close(code, reason) => {
243 let mut payload = Vec::new();
244 if let Some(c) = code {
245 payload.extend_from_slice(&c.to_be_bytes());
246 payload.extend_from_slice(reason.as_bytes());
247 }
248 (0x8, payload, true)
249 }
250 Frame::Continuation { fin, data } => (0x0, data, fin),
251 };
252
253 let fin_bit: u8 = if fin { 0x80 } else { 0x00 };
254 let byte0 = fin_bit | opcode;
255
256 let payload_len = payload.len();
257 let mut header = Vec::with_capacity(10);
258 header.push(byte0);
259 match payload_len {
260 0..=125 => header.push(payload_len as u8),
261 126..=65535 => {
262 header.push(126u8);
263 header.extend_from_slice(&(payload_len as u16).to_be_bytes());
264 }
265 _ => {
266 header.push(127u8);
267 header.extend_from_slice(&(payload_len as u64).to_be_bytes());
268 }
269 }
270
271 stream.write_all(&header).map_err(|e| format!("write error: {}", e))?;
272 if !payload.is_empty() {
273 stream.write_all(&payload).map_err(|e| format!("write error: {}", e))?;
274 }
275 stream.flush().map_err(|e| format!("flush error: {}", e))?;
276 Ok(())
277 }
278
279 pub fn send_text(stream: &mut impl Write, text: &str) -> Result<(), String> {
281 Self::write_frame(stream, Frame::Text(text.to_string()))
282 }
283
284 pub fn send_close(stream: &mut impl Write, code: u16, reason: &str) -> Result<(), String> {
286 Self::write_frame(stream, Frame::Close(Some(code), reason.to_string()))
287 }
288
289 pub fn send_pong(stream: &mut impl Write, payload: Vec<u8>) -> Result<(), String> {
291 Self::write_frame(stream, Frame::Pong(payload))
292 }
293}
294
295fn read_exact<R: Read>(r: &mut R, buf: &mut [u8]) -> Result<(), String> {
298 r.read_exact(buf).map_err(|e| format!("read error: {}", e))
299}
300
301pub(crate) fn sha1(data: &[u8]) -> [u8; 20] {
303 let mut h = [0x67452301u32, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
304
305 let msg_len = data.len();
306 let mut msg = data.to_vec();
307 msg.push(0x80);
308 while msg.len() % 64 != 56 {
309 msg.push(0x00);
310 }
311 msg.extend_from_slice(&((msg_len as u64) * 8).to_be_bytes());
312
313 for chunk in msg.chunks(64) {
314 let mut w = [0u32; 80];
315 for i in 0..16 {
316 w[i] = u32::from_be_bytes([chunk[i*4], chunk[i*4+1], chunk[i*4+2], chunk[i*4+3]]);
317 }
318 for i in 16..80 {
319 w[i] = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]).rotate_left(1);
320 }
321
322 let (mut a, mut b, mut c, mut d, mut e) = (h[0], h[1], h[2], h[3], h[4]);
323
324 for i in 0..80 {
325 let (f, k) = match i {
326 0..=19 => ((b & c) | (!b & d), 0x5A827999u32),
327 20..=39 => (b ^ c ^ d, 0x6ED9EBA1u32),
328 40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDCu32),
329 _ => (b ^ c ^ d, 0xCA62C1D6u32),
330 };
331 let temp = a.rotate_left(5)
332 .wrapping_add(f)
333 .wrapping_add(e)
334 .wrapping_add(k)
335 .wrapping_add(w[i]);
336 e = d; d = c; c = b.rotate_left(30); b = a; a = temp;
337 }
338
339 h[0] = h[0].wrapping_add(a);
340 h[1] = h[1].wrapping_add(b);
341 h[2] = h[2].wrapping_add(c);
342 h[3] = h[3].wrapping_add(d);
343 h[4] = h[4].wrapping_add(e);
344 }
345
346 let mut out = [0u8; 20];
347 for (i, v) in h.iter().enumerate() {
348 out[i*4..(i+1)*4].copy_from_slice(&v.to_be_bytes());
349 }
350 out
351}
352
353pub(crate) fn base64_encode(data: &[u8]) -> String {
355 const T: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
356 let mut out = Vec::with_capacity((data.len() + 2) / 3 * 4);
357 for chunk in data.chunks(3) {
358 let b0 = chunk[0] as u32;
359 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
360 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
361 let n = (b0 << 16) | (b1 << 8) | b2;
362 out.push(T[((n >> 18) & 0x3F) as usize]);
363 out.push(T[((n >> 12) & 0x3F) as usize]);
364 out.push(if chunk.len() > 1 { T[((n >> 6) & 0x3F) as usize] } else { b'=' });
365 out.push(if chunk.len() > 2 { T[(n & 0x3F) as usize] } else { b'=' });
366 }
367 String::from_utf8(out).unwrap()
368}