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 {
121 match self.0 {
122 1004 | 1005 | 1006 | 1015 => false,
123 1000..=1014 => true,
124 3000..=4999 => true,
125 _ => false,
126 }
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct CloseFrame {
133 pub code: CloseCode,
134 pub reason: String,
135}
136
137impl CloseFrame {
138 pub fn new(code: CloseCode, reason: impl Into<String>) -> Self {
139 CloseFrame { code, reason: reason.into() }
140 }
141
142 pub fn parse(payload: &[u8]) -> WsResult<Option<CloseFrame>> {
144 match payload.len() {
145 0 => Ok(None),
146 1 => Err(WsError::protocol("close payload must be empty or at least two bytes")),
147 _ => {
148 let code = CloseCode(u16::from_be_bytes([payload[0], payload[1]]));
149 if !code.is_sendable() {
150 return Err(WsError::protocol(format!(
151 "close code {} must not be sent on the wire",
152 code.0
153 )));
154 }
155 let reason =
158 std::str::from_utf8(&payload[2..]).map_err(|_| WsError::InvalidUtf8)?;
159 Ok(Some(CloseFrame { code, reason: reason.to_string() }))
160 }
161 }
162 }
163
164 pub fn to_payload(&self) -> Vec<u8> {
171 let mut end = self.reason.len().min(123);
172 while end > 0 && !self.reason.is_char_boundary(end) {
173 end -= 1;
174 }
175
176 let mut out = Vec::with_capacity(2 + end);
177 out.extend_from_slice(&self.code.0.to_be_bytes());
178 out.extend_from_slice(&self.reason.as_bytes()[..end]);
179 out
180 }
181}
182
183#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct Frame {
185 pub fin: bool,
186 pub opcode: OpCode,
187 pub mask: Option<[u8; 4]>,
190 pub payload: Vec<u8>,
191}
192
193impl Frame {
194 pub fn server(opcode: OpCode, payload: Vec<u8>) -> Frame {
196 Frame { fin: true, opcode, mask: None, payload }
197 }
198
199 pub fn client(opcode: OpCode, payload: Vec<u8>, mask: [u8; 4]) -> Frame {
202 Frame { fin: true, opcode, mask: Some(mask), payload }
203 }
204
205 pub fn encode(&self) -> Vec<u8> {
206 let mut out = Vec::with_capacity(self.payload.len() + 14);
207 out.push(if self.fin { 0x80 } else { 0 } | self.opcode.bits());
208
209 let masked = if self.mask.is_some() { 0x80 } else { 0 };
210 let length = self.payload.len();
211 if length < 126 {
214 out.push(masked | length as u8);
215 } else if length <= u16::MAX as usize {
216 out.push(masked | 126);
217 out.extend_from_slice(&(length as u16).to_be_bytes());
218 } else {
219 out.push(masked | 127);
220 out.extend_from_slice(&(length as u64).to_be_bytes());
221 }
222
223 match self.mask {
224 Some(mask) => {
225 out.extend_from_slice(&mask);
226 let start = out.len();
227 out.extend_from_slice(&self.payload);
228 apply_mask(&mut out[start..], mask);
229 }
230 None => out.extend_from_slice(&self.payload),
231 }
232 out
233 }
234
235 pub fn decode(
244 bytes: &[u8],
245 role: Role,
246 max_payload: usize,
247 ) -> WsResult<Option<(Frame, usize)>> {
248 if bytes.len() < 2 {
249 return Ok(None);
250 }
251
252 let (first, second) = (bytes[0], bytes[1]);
253 let fin = first & 0x80 != 0;
254 if first & 0x70 != 0 {
255 return Err(WsError::protocol("a reserved bit is set but no extension was negotiated"));
256 }
257 let opcode = OpCode::from_bits(first & 0x0f)?;
258 let masked = second & 0x80 != 0;
259
260 let mut cursor = 2;
261 let length = match second & 0x7f {
262 126 => {
263 if bytes.len() < cursor + 2 {
264 return Ok(None);
265 }
266 let value = u16::from_be_bytes([bytes[cursor], bytes[cursor + 1]]) as usize;
267 cursor += 2;
268 if value < 126 {
269 return Err(WsError::protocol(
270 "16-bit length used for a payload that fits in 7 bits",
271 ));
272 }
273 value
274 }
275 127 => {
276 if bytes.len() < cursor + 8 {
277 return Ok(None);
278 }
279 let mut wide = [0u8; 8];
280 wide.copy_from_slice(&bytes[cursor..cursor + 8]);
281 let value = u64::from_be_bytes(wide);
282 cursor += 8;
283 if value & (1 << 63) != 0 {
284 return Err(WsError::protocol("64-bit length has its high bit set"));
285 }
286 if value <= u16::MAX as u64 {
287 return Err(WsError::protocol(
288 "64-bit length used for a payload that fits in 16 bits",
289 ));
290 }
291 match usize::try_from(value) {
294 Ok(value) => value,
295 Err(_) => {
296 return Err(WsError::TooLarge { size: usize::MAX, limit: max_payload });
297 }
298 }
299 }
300 short => short as usize,
301 };
302
303 if length > max_payload {
304 return Err(WsError::TooLarge { size: length, limit: max_payload });
305 }
306
307 if opcode.is_control() {
308 if length > 125 {
309 return Err(WsError::protocol("a control frame payload must be 125 bytes or fewer"));
310 }
311 if !fin {
312 return Err(WsError::protocol("a control frame must not be fragmented"));
313 }
314 }
315
316 match (role, masked) {
317 (Role::Server, false) => {
318 return Err(WsError::protocol("a frame from a client must be masked"));
319 }
320 (Role::Client, true) => {
321 return Err(WsError::protocol("a frame from a server must not be masked"));
322 }
323 _ => {}
324 }
325
326 let mask = if masked {
327 if bytes.len() < cursor + 4 {
328 return Ok(None);
329 }
330 let mask = [bytes[cursor], bytes[cursor + 1], bytes[cursor + 2], bytes[cursor + 3]];
331 cursor += 4;
332 Some(mask)
333 } else {
334 None
335 };
336
337 if bytes.len() < cursor + length {
338 return Ok(None);
339 }
340
341 let mut payload = bytes[cursor..cursor + length].to_vec();
342 if let Some(mask) = mask {
343 apply_mask(&mut payload, mask);
344 }
345
346 Ok(Some((Frame { fin, opcode, mask, payload }, cursor + length)))
347 }
348}
349
350pub fn apply_mask(payload: &mut [u8], mask: [u8; 4]) {
352 for (index, byte) in payload.iter_mut().enumerate() {
353 *byte ^= mask[index % 4];
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn encodes_a_short_unmasked_text_frame() {
363 let frame = Frame::server(OpCode::Text, b"Hello".to_vec());
364
365 assert_eq!(frame.encode(), b"\x81\x05Hello");
367 }
368
369 #[test]
370 fn decodes_the_masked_frame_from_the_rfc() {
371 let wire = b"\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58";
373
374 let (frame, used) = Frame::decode(wire, Role::Server, 1024).unwrap().unwrap();
375
376 assert_eq!(used, wire.len());
377 assert!(frame.fin);
378 assert_eq!(frame.opcode, OpCode::Text);
379 assert_eq!(frame.mask, Some([0x37, 0xfa, 0x21, 0x3d]));
380 assert_eq!(frame.payload, b"Hello");
381 assert_eq!(frame.encode(), wire);
383 }
384
385 #[test]
386 fn each_payload_length_form_round_trips() {
387 for (length, header) in [
388 (5usize, vec![0x82, 5]),
390 (125, vec![0x82, 125]),
391 (126, vec![0x82, 126, 0x00, 0x7e]),
393 (65_535, vec![0x82, 126, 0xff, 0xff]),
394 (65_536, vec![0x82, 127, 0, 0, 0, 0, 0, 1, 0, 0]),
396 ] {
397 let frame = Frame::server(OpCode::Binary, vec![0xab; length]);
398 let encoded = frame.encode();
399
400 assert_eq!(&encoded[..header.len()], &header[..], "header for {length} bytes");
401 assert_eq!(encoded.len(), header.len() + length);
402
403 let (decoded, used) = Frame::decode(&encoded, Role::Client, 1 << 20).unwrap().unwrap();
404 assert_eq!(used, encoded.len());
405 assert_eq!(decoded, frame, "round trip of {length} bytes");
406 }
407 }
408
409 #[test]
410 fn masking_is_its_own_inverse() {
411 let mask = [0x11, 0x22, 0x33, 0x44];
412 let mut bytes = b"the quick brown fox".to_vec();
413 let original = bytes.clone();
414
415 apply_mask(&mut bytes, mask);
416 assert_ne!(bytes, original);
417 apply_mask(&mut bytes, mask);
418 assert_eq!(bytes, original);
419 }
420
421 #[test]
422 fn a_partial_frame_asks_for_more_bytes_instead_of_failing() {
423 let encoded = Frame::server(OpCode::Text, vec![b'x'; 300]).encode();
424
425 for cut in [0, 1, 2, 3, 4, encoded.len() - 1] {
426 assert!(
427 Frame::decode(&encoded[..cut], Role::Client, 1024).unwrap().is_none(),
428 "{cut} bytes should not decode yet"
429 );
430 }
431 assert!(Frame::decode(&encoded, Role::Client, 1024).unwrap().is_some());
432 }
433
434 #[test]
435 fn a_server_rejects_an_unmasked_client_frame() {
436 let unmasked = Frame::server(OpCode::Text, b"hi".to_vec()).encode();
437
438 let error = Frame::decode(&unmasked, Role::Server, 1024).unwrap_err();
439 assert!(error.to_string().contains("must be masked"), "{error}");
440 }
441
442 #[test]
443 fn a_client_rejects_a_masked_server_frame() {
444 let masked = Frame::client(OpCode::Text, b"hi".to_vec(), [1, 2, 3, 4]).encode();
445
446 let error = Frame::decode(&masked, Role::Client, 1024).unwrap_err();
447 assert!(error.to_string().contains("must not be masked"), "{error}");
448 }
449
450 #[test]
451 fn reserved_bits_and_reserved_opcodes_are_refused() {
452 let reserved_bit = Frame::decode(&[0xc1, 0x00], Role::Client, 1024).unwrap_err();
453 assert!(reserved_bit.to_string().contains("reserved bit"), "{reserved_bit}");
454
455 let reserved_opcode = Frame::decode(&[0x83, 0x00], Role::Client, 1024).unwrap_err();
456 assert!(reserved_opcode.to_string().contains("reserved"), "{reserved_opcode}");
457 }
458
459 #[test]
460 fn control_frames_must_be_short_and_unfragmented() {
461 let long_ping = [0x89, 126, 0x00, 0x7e];
464 let error = Frame::decode(&long_ping, Role::Client, 1 << 20).unwrap_err();
465 assert!(error.to_string().contains("125 bytes or fewer"), "{error}");
466
467 let fragmented_close = [0x08, 0x00];
469 let error = Frame::decode(&fragmented_close, Role::Client, 1024).unwrap_err();
470 assert!(error.to_string().contains("must not be fragmented"), "{error}");
471 }
472
473 #[test]
474 fn a_declared_length_over_the_limit_fails_before_anything_is_allocated() {
475 let header = [0x82, 127, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
477
478 let error = Frame::decode(&header, Role::Client, 1024).unwrap_err();
479 match error {
480 WsError::TooLarge { size, limit } => {
481 assert_eq!(size, 1 << 32);
482 assert_eq!(limit, 1024);
483 }
484 other => panic!("expected an oversize error, got {other}"),
485 }
486 }
487
488 #[test]
489 fn non_minimal_length_encodings_are_refused() {
490 let padded_16 = [0x82, 126, 0x00, 0x05];
492 assert!(Frame::decode(&padded_16, Role::Client, 1024).is_err());
493
494 let padded_64 = [0x82, 127, 0, 0, 0, 0, 0, 0, 0, 5];
496 assert!(Frame::decode(&padded_64, Role::Client, 1024).is_err());
497 }
498
499 #[test]
500 fn a_fragmented_message_decodes_as_three_frames() {
501 let mut wire = Vec::new();
503 wire.extend(Frame { fin: false, opcode: OpCode::Text, mask: None, payload: b"Hel".to_vec() }.encode());
504 wire.extend(
505 Frame { fin: false, opcode: OpCode::Continuation, mask: None, payload: b"lo ".to_vec() }
506 .encode(),
507 );
508 wire.extend(
509 Frame { fin: true, opcode: OpCode::Continuation, mask: None, payload: b"there".to_vec() }
510 .encode(),
511 );
512
513 let mut rest = &wire[..];
514 let mut assembled = Vec::new();
515 let mut opcodes = Vec::new();
516
517 while let Some((frame, used)) = Frame::decode(rest, Role::Client, 1024).unwrap() {
518 opcodes.push((frame.opcode, frame.fin));
519 assembled.extend_from_slice(&frame.payload);
520 rest = &rest[used..];
521 }
522
523 assert_eq!(assembled, b"Hello there");
524 assert_eq!(
525 opcodes,
526 [
527 (OpCode::Text, false),
528 (OpCode::Continuation, false),
529 (OpCode::Continuation, true),
530 ]
531 );
532 }
533
534 #[test]
535 fn a_close_payload_carries_a_code_and_a_reason() {
536 let close = CloseFrame::new(CloseCode::NORMAL, "bye");
537 let payload = close.to_payload();
538
539 assert_eq!(payload, b"\x03\xe8bye");
540 assert_eq!(CloseFrame::parse(&payload).unwrap(), Some(close));
541 assert_eq!(CloseFrame::parse(&[]).unwrap(), None);
543 }
544
545 #[test]
550 fn every_close_code_autobahn_calls_invalid_is_refused() {
551 for code in [0u16, 999, 1004, 1005, 1006, 1016, 1100, 2000, 2999] {
552 let payload = code.to_be_bytes();
553 assert!(CloseFrame::parse(&payload).is_err(), "{code} should be refused");
554 }
555 }
556
557 #[test]
560 fn every_registered_close_code_is_accepted() {
561 for code in [1000u16, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 3000, 3999, 4000, 4999] {
562 let payload = code.to_be_bytes();
563 assert!(
564 CloseFrame::parse(&payload).is_ok(),
565 "{code} is a registered close code and was refused"
566 );
567 }
568 }
569
570 #[test]
571 fn close_codes_that_only_describe_a_local_end_may_not_be_sent() {
572 for code in [1005u16, 1006, 1015] {
573 let payload = code.to_be_bytes();
574 assert!(CloseFrame::parse(&payload).is_err(), "{code} should be refused");
575 }
576 let custom = CloseFrame::new(CloseCode(4001), "seat taken");
578 assert_eq!(CloseFrame::parse(&custom.to_payload()).unwrap(), Some(custom));
579 }
580
581 #[test]
582 fn a_one_byte_close_payload_is_a_protocol_error() {
583 assert!(CloseFrame::parse(&[0x03]).is_err());
584 }
585
586 #[test]
587 fn a_long_close_reason_is_truncated_on_a_character_boundary() {
588 let close = CloseFrame::new(CloseCode::INTERNAL_ERROR, "é".repeat(100));
590 let payload = close.to_payload();
591
592 assert!(payload.len() <= 125, "a control frame may not exceed 125 bytes");
593 let parsed = CloseFrame::parse(&payload).unwrap().unwrap();
594 assert_eq!(parsed.code, CloseCode::INTERNAL_ERROR);
595 assert_eq!(parsed.reason, "é".repeat(61));
596 }
597}