1use crate::error::{WsError, WsResult};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Role {
35 Server,
36 Client,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum OpCode {
41 Continuation,
42 Text,
43 Binary,
44 Close,
45 Ping,
46 Pong,
47}
48
49impl OpCode {
50 pub fn bits(self) -> u8 {
51 match self {
52 OpCode::Continuation => 0x0,
53 OpCode::Text => 0x1,
54 OpCode::Binary => 0x2,
55 OpCode::Close => 0x8,
56 OpCode::Ping => 0x9,
57 OpCode::Pong => 0xa,
58 }
59 }
60
61 pub fn from_bits(bits: u8) -> WsResult<OpCode> {
62 match bits {
63 0x0 => Ok(OpCode::Continuation),
64 0x1 => Ok(OpCode::Text),
65 0x2 => Ok(OpCode::Binary),
66 0x8 => Ok(OpCode::Close),
67 0x9 => Ok(OpCode::Ping),
68 0xa => Ok(OpCode::Pong),
69 other => Err(WsError::protocol(format!("opcode {other:#x} is reserved"))),
70 }
71 }
72
73 pub fn is_control(self) -> bool {
76 matches!(self, OpCode::Close | OpCode::Ping | OpCode::Pong)
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub struct CloseCode(pub u16);
86
87impl CloseCode {
88 pub const NORMAL: CloseCode = CloseCode(1000);
89 pub const GOING_AWAY: CloseCode = CloseCode(1001);
90 pub const PROTOCOL_ERROR: CloseCode = CloseCode(1002);
91 pub const UNSUPPORTED: CloseCode = CloseCode(1003);
92 pub const NO_STATUS: CloseCode = CloseCode(1005);
94 pub const ABNORMAL: CloseCode = CloseCode(1006);
96 pub const INVALID_PAYLOAD: CloseCode = CloseCode(1007);
97 pub const POLICY: CloseCode = CloseCode(1008);
98 pub const TOO_LARGE: CloseCode = CloseCode(1009);
99 pub const INTERNAL_ERROR: CloseCode = CloseCode(1011);
100
101 pub fn code(self) -> u16 {
102 self.0
103 }
104
105 pub fn is_sendable(self) -> bool {
110 match self.0 {
111 1005 | 1006 | 1015 => false,
112 1000..=1014 => true,
113 3000..=4999 => true,
114 _ => false,
115 }
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct CloseFrame {
122 pub code: CloseCode,
123 pub reason: String,
124}
125
126impl CloseFrame {
127 pub fn new(code: CloseCode, reason: impl Into<String>) -> Self {
128 CloseFrame { code, reason: reason.into() }
129 }
130
131 pub fn parse(payload: &[u8]) -> WsResult<Option<CloseFrame>> {
133 match payload.len() {
134 0 => Ok(None),
135 1 => Err(WsError::protocol("close payload must be empty or at least two bytes")),
136 _ => {
137 let code = CloseCode(u16::from_be_bytes([payload[0], payload[1]]));
138 if !code.is_sendable() {
139 return Err(WsError::protocol(format!(
140 "close code {} must not be sent on the wire",
141 code.0
142 )));
143 }
144 let reason =
147 std::str::from_utf8(&payload[2..]).map_err(|_| WsError::InvalidUtf8)?;
148 Ok(Some(CloseFrame { code, reason: reason.to_string() }))
149 }
150 }
151 }
152
153 pub fn to_payload(&self) -> Vec<u8> {
160 let mut end = self.reason.len().min(123);
161 while end > 0 && !self.reason.is_char_boundary(end) {
162 end -= 1;
163 }
164
165 let mut out = Vec::with_capacity(2 + end);
166 out.extend_from_slice(&self.code.0.to_be_bytes());
167 out.extend_from_slice(&self.reason.as_bytes()[..end]);
168 out
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct Frame {
174 pub fin: bool,
175 pub opcode: OpCode,
176 pub mask: Option<[u8; 4]>,
179 pub payload: Vec<u8>,
180}
181
182impl Frame {
183 pub fn server(opcode: OpCode, payload: Vec<u8>) -> Frame {
185 Frame { fin: true, opcode, mask: None, payload }
186 }
187
188 pub fn client(opcode: OpCode, payload: Vec<u8>, mask: [u8; 4]) -> Frame {
191 Frame { fin: true, opcode, mask: Some(mask), payload }
192 }
193
194 pub fn encode(&self) -> Vec<u8> {
195 let mut out = Vec::with_capacity(self.payload.len() + 14);
196 out.push(if self.fin { 0x80 } else { 0 } | self.opcode.bits());
197
198 let masked = if self.mask.is_some() { 0x80 } else { 0 };
199 let length = self.payload.len();
200 if length < 126 {
203 out.push(masked | length as u8);
204 } else if length <= u16::MAX as usize {
205 out.push(masked | 126);
206 out.extend_from_slice(&(length as u16).to_be_bytes());
207 } else {
208 out.push(masked | 127);
209 out.extend_from_slice(&(length as u64).to_be_bytes());
210 }
211
212 match self.mask {
213 Some(mask) => {
214 out.extend_from_slice(&mask);
215 let start = out.len();
216 out.extend_from_slice(&self.payload);
217 apply_mask(&mut out[start..], mask);
218 }
219 None => out.extend_from_slice(&self.payload),
220 }
221 out
222 }
223
224 pub fn decode(
233 bytes: &[u8],
234 role: Role,
235 max_payload: usize,
236 ) -> WsResult<Option<(Frame, usize)>> {
237 if bytes.len() < 2 {
238 return Ok(None);
239 }
240
241 let (first, second) = (bytes[0], bytes[1]);
242 let fin = first & 0x80 != 0;
243 if first & 0x70 != 0 {
244 return Err(WsError::protocol("a reserved bit is set but no extension was negotiated"));
245 }
246 let opcode = OpCode::from_bits(first & 0x0f)?;
247 let masked = second & 0x80 != 0;
248
249 let mut cursor = 2;
250 let length = match second & 0x7f {
251 126 => {
252 if bytes.len() < cursor + 2 {
253 return Ok(None);
254 }
255 let value = u16::from_be_bytes([bytes[cursor], bytes[cursor + 1]]) as usize;
256 cursor += 2;
257 if value < 126 {
258 return Err(WsError::protocol(
259 "16-bit length used for a payload that fits in 7 bits",
260 ));
261 }
262 value
263 }
264 127 => {
265 if bytes.len() < cursor + 8 {
266 return Ok(None);
267 }
268 let mut wide = [0u8; 8];
269 wide.copy_from_slice(&bytes[cursor..cursor + 8]);
270 let value = u64::from_be_bytes(wide);
271 cursor += 8;
272 if value & (1 << 63) != 0 {
273 return Err(WsError::protocol("64-bit length has its high bit set"));
274 }
275 if value <= u16::MAX as u64 {
276 return Err(WsError::protocol(
277 "64-bit length used for a payload that fits in 16 bits",
278 ));
279 }
280 match usize::try_from(value) {
283 Ok(value) => value,
284 Err(_) => {
285 return Err(WsError::TooLarge { size: usize::MAX, limit: max_payload });
286 }
287 }
288 }
289 short => short as usize,
290 };
291
292 if length > max_payload {
293 return Err(WsError::TooLarge { size: length, limit: max_payload });
294 }
295
296 if opcode.is_control() {
297 if length > 125 {
298 return Err(WsError::protocol("a control frame payload must be 125 bytes or fewer"));
299 }
300 if !fin {
301 return Err(WsError::protocol("a control frame must not be fragmented"));
302 }
303 }
304
305 match (role, masked) {
306 (Role::Server, false) => {
307 return Err(WsError::protocol("a frame from a client must be masked"));
308 }
309 (Role::Client, true) => {
310 return Err(WsError::protocol("a frame from a server must not be masked"));
311 }
312 _ => {}
313 }
314
315 let mask = if masked {
316 if bytes.len() < cursor + 4 {
317 return Ok(None);
318 }
319 let mask = [bytes[cursor], bytes[cursor + 1], bytes[cursor + 2], bytes[cursor + 3]];
320 cursor += 4;
321 Some(mask)
322 } else {
323 None
324 };
325
326 if bytes.len() < cursor + length {
327 return Ok(None);
328 }
329
330 let mut payload = bytes[cursor..cursor + length].to_vec();
331 if let Some(mask) = mask {
332 apply_mask(&mut payload, mask);
333 }
334
335 Ok(Some((Frame { fin, opcode, mask, payload }, cursor + length)))
336 }
337}
338
339pub fn apply_mask(payload: &mut [u8], mask: [u8; 4]) {
341 for (index, byte) in payload.iter_mut().enumerate() {
342 *byte ^= mask[index % 4];
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn encodes_a_short_unmasked_text_frame() {
352 let frame = Frame::server(OpCode::Text, b"Hello".to_vec());
353
354 assert_eq!(frame.encode(), b"\x81\x05Hello");
356 }
357
358 #[test]
359 fn decodes_the_masked_frame_from_the_rfc() {
360 let wire = b"\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58";
362
363 let (frame, used) = Frame::decode(wire, Role::Server, 1024).unwrap().unwrap();
364
365 assert_eq!(used, wire.len());
366 assert!(frame.fin);
367 assert_eq!(frame.opcode, OpCode::Text);
368 assert_eq!(frame.mask, Some([0x37, 0xfa, 0x21, 0x3d]));
369 assert_eq!(frame.payload, b"Hello");
370 assert_eq!(frame.encode(), wire);
372 }
373
374 #[test]
375 fn each_payload_length_form_round_trips() {
376 for (length, header) in [
377 (5usize, vec![0x82, 5]),
379 (125, vec![0x82, 125]),
380 (126, vec![0x82, 126, 0x00, 0x7e]),
382 (65_535, vec![0x82, 126, 0xff, 0xff]),
383 (65_536, vec![0x82, 127, 0, 0, 0, 0, 0, 1, 0, 0]),
385 ] {
386 let frame = Frame::server(OpCode::Binary, vec![0xab; length]);
387 let encoded = frame.encode();
388
389 assert_eq!(&encoded[..header.len()], &header[..], "header for {length} bytes");
390 assert_eq!(encoded.len(), header.len() + length);
391
392 let (decoded, used) = Frame::decode(&encoded, Role::Client, 1 << 20).unwrap().unwrap();
393 assert_eq!(used, encoded.len());
394 assert_eq!(decoded, frame, "round trip of {length} bytes");
395 }
396 }
397
398 #[test]
399 fn masking_is_its_own_inverse() {
400 let mask = [0x11, 0x22, 0x33, 0x44];
401 let mut bytes = b"the quick brown fox".to_vec();
402 let original = bytes.clone();
403
404 apply_mask(&mut bytes, mask);
405 assert_ne!(bytes, original);
406 apply_mask(&mut bytes, mask);
407 assert_eq!(bytes, original);
408 }
409
410 #[test]
411 fn a_partial_frame_asks_for_more_bytes_instead_of_failing() {
412 let encoded = Frame::server(OpCode::Text, vec![b'x'; 300]).encode();
413
414 for cut in [0, 1, 2, 3, 4, encoded.len() - 1] {
415 assert!(
416 Frame::decode(&encoded[..cut], Role::Client, 1024).unwrap().is_none(),
417 "{cut} bytes should not decode yet"
418 );
419 }
420 assert!(Frame::decode(&encoded, Role::Client, 1024).unwrap().is_some());
421 }
422
423 #[test]
424 fn a_server_rejects_an_unmasked_client_frame() {
425 let unmasked = Frame::server(OpCode::Text, b"hi".to_vec()).encode();
426
427 let error = Frame::decode(&unmasked, Role::Server, 1024).unwrap_err();
428 assert!(error.to_string().contains("must be masked"), "{error}");
429 }
430
431 #[test]
432 fn a_client_rejects_a_masked_server_frame() {
433 let masked = Frame::client(OpCode::Text, b"hi".to_vec(), [1, 2, 3, 4]).encode();
434
435 let error = Frame::decode(&masked, Role::Client, 1024).unwrap_err();
436 assert!(error.to_string().contains("must not be masked"), "{error}");
437 }
438
439 #[test]
440 fn reserved_bits_and_reserved_opcodes_are_refused() {
441 let reserved_bit = Frame::decode(&[0xc1, 0x00], Role::Client, 1024).unwrap_err();
442 assert!(reserved_bit.to_string().contains("reserved bit"), "{reserved_bit}");
443
444 let reserved_opcode = Frame::decode(&[0x83, 0x00], Role::Client, 1024).unwrap_err();
445 assert!(reserved_opcode.to_string().contains("reserved"), "{reserved_opcode}");
446 }
447
448 #[test]
449 fn control_frames_must_be_short_and_unfragmented() {
450 let long_ping = [0x89, 126, 0x00, 0x7e];
453 let error = Frame::decode(&long_ping, Role::Client, 1 << 20).unwrap_err();
454 assert!(error.to_string().contains("125 bytes or fewer"), "{error}");
455
456 let fragmented_close = [0x08, 0x00];
458 let error = Frame::decode(&fragmented_close, Role::Client, 1024).unwrap_err();
459 assert!(error.to_string().contains("must not be fragmented"), "{error}");
460 }
461
462 #[test]
463 fn a_declared_length_over_the_limit_fails_before_anything_is_allocated() {
464 let header = [0x82, 127, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
466
467 let error = Frame::decode(&header, Role::Client, 1024).unwrap_err();
468 match error {
469 WsError::TooLarge { size, limit } => {
470 assert_eq!(size, 1 << 32);
471 assert_eq!(limit, 1024);
472 }
473 other => panic!("expected an oversize error, got {other}"),
474 }
475 }
476
477 #[test]
478 fn non_minimal_length_encodings_are_refused() {
479 let padded_16 = [0x82, 126, 0x00, 0x05];
481 assert!(Frame::decode(&padded_16, Role::Client, 1024).is_err());
482
483 let padded_64 = [0x82, 127, 0, 0, 0, 0, 0, 0, 0, 5];
485 assert!(Frame::decode(&padded_64, Role::Client, 1024).is_err());
486 }
487
488 #[test]
489 fn a_fragmented_message_decodes_as_three_frames() {
490 let mut wire = Vec::new();
492 wire.extend(Frame { fin: false, opcode: OpCode::Text, mask: None, payload: b"Hel".to_vec() }.encode());
493 wire.extend(
494 Frame { fin: false, opcode: OpCode::Continuation, mask: None, payload: b"lo ".to_vec() }
495 .encode(),
496 );
497 wire.extend(
498 Frame { fin: true, opcode: OpCode::Continuation, mask: None, payload: b"there".to_vec() }
499 .encode(),
500 );
501
502 let mut rest = &wire[..];
503 let mut assembled = Vec::new();
504 let mut opcodes = Vec::new();
505
506 while let Some((frame, used)) = Frame::decode(rest, Role::Client, 1024).unwrap() {
507 opcodes.push((frame.opcode, frame.fin));
508 assembled.extend_from_slice(&frame.payload);
509 rest = &rest[used..];
510 }
511
512 assert_eq!(assembled, b"Hello there");
513 assert_eq!(
514 opcodes,
515 [
516 (OpCode::Text, false),
517 (OpCode::Continuation, false),
518 (OpCode::Continuation, true),
519 ]
520 );
521 }
522
523 #[test]
524 fn a_close_payload_carries_a_code_and_a_reason() {
525 let close = CloseFrame::new(CloseCode::NORMAL, "bye");
526 let payload = close.to_payload();
527
528 assert_eq!(payload, b"\x03\xe8bye");
529 assert_eq!(CloseFrame::parse(&payload).unwrap(), Some(close));
530 assert_eq!(CloseFrame::parse(&[]).unwrap(), None);
532 }
533
534 #[test]
535 fn close_codes_that_only_describe_a_local_end_may_not_be_sent() {
536 for code in [1005u16, 1006, 1015] {
537 let payload = code.to_be_bytes();
538 assert!(CloseFrame::parse(&payload).is_err(), "{code} should be refused");
539 }
540 let custom = CloseFrame::new(CloseCode(4001), "seat taken");
542 assert_eq!(CloseFrame::parse(&custom.to_payload()).unwrap(), Some(custom));
543 }
544
545 #[test]
546 fn a_one_byte_close_payload_is_a_protocol_error() {
547 assert!(CloseFrame::parse(&[0x03]).is_err());
548 }
549
550 #[test]
551 fn a_long_close_reason_is_truncated_on_a_character_boundary() {
552 let close = CloseFrame::new(CloseCode::INTERNAL_ERROR, "é".repeat(100));
554 let payload = close.to_payload();
555
556 assert!(payload.len() <= 125, "a control frame may not exceed 125 bytes");
557 let parsed = CloseFrame::parse(&payload).unwrap().unwrap();
558 assert_eq!(parsed.code, CloseCode::INTERNAL_ERROR);
559 assert_eq!(parsed.reason, "é".repeat(61));
560 }
561}