1use crate::error::WebsocketEventError;
2
3#[derive(Debug)]
4pub enum WebsocketEvent<'a> {
5 Open,
6 Text(&'a [u8]),
7 Binary(&'a [u8]),
8 Ping,
9 Close(&'a [u8]),
10 Disconnect,
11}
12
13impl<'a> WebsocketEvent<'a> {
14 pub fn parse_frame(resp_body: &'a [u8]) -> Result<WebsocketEvent<'a>, WebsocketEventError> {
15 let header_end = resp_body
16 .windows(2)
17 .position(|w| w == b"\r\n")
18 .ok_or(WebsocketEventError::ParseError)?;
19
20 let header = std::str::from_utf8(&resp_body[..header_end])
21 .map_err(|_| WebsocketEventError::InvalidUtf8)?;
22
23 let mut parts = header.split(' ');
24 let command = parts.next().ok_or(WebsocketEventError::ParseError)?;
25
26 match command {
27 "OPEN" => Ok(WebsocketEvent::Open),
28 "PING" => Ok(WebsocketEvent::Ping),
29 "DISCONNECT" => Ok(WebsocketEvent::Disconnect),
30 "TEXT" | "BINARY" => {
31 let len_hex = parts.next().ok_or(WebsocketEventError::MissingLength)?;
32 let len = usize::from_str_radix(len_hex, 16)
33 .map_err(|_| WebsocketEventError::InvalidLength)?;
34
35 let body_start = header_end + 2;
36 let body_end = body_start + len;
37
38 if resp_body.len() < body_end + 2 {
39 return Err(WebsocketEventError::TruncatedBody);
40 }
41
42 let body = &resp_body[body_start..body_end];
43
44 if &resp_body[body_end..body_end + 2] != b"\r\n" {
45 return Err(WebsocketEventError::MissingTrailingCrlf);
46 }
47
48 match command {
49 "TEXT" => Ok(WebsocketEvent::Text(body)),
50 "BINARY" => Ok(WebsocketEvent::Binary(body)),
51 _ => unreachable!(),
52 }
53 }
54
55 "CLOSE" => match parts.next() {
56 None => Ok(WebsocketEvent::Close(&[])),
57 Some(len_hex) => {
58 let len = usize::from_str_radix(len_hex, 16)
59 .map_err(|_| WebsocketEventError::InvalidLength)?;
60
61 let body_start = header_end + 2;
62 let body_end = body_start + len;
63
64 if resp_body.len() < body_end + 2 {
65 return Err(WebsocketEventError::TruncatedBody);
66 }
67
68 let body = &resp_body[body_start..body_end];
69
70 if &resp_body[body_end..body_end + 2] != b"\r\n" {
71 return Err(WebsocketEventError::MissingTrailingCrlf);
72 }
73
74 Ok(WebsocketEvent::Close(body))
75 }
76 },
77 _ => Err(WebsocketEventError::UnrecognizedCommand),
78 }
79 }
80}
81
82#[cfg(test)]
83mod test {
84 use super::*;
85
86 #[test]
87 fn parse_open() {
88 let frame: &[u8; 6] = b"OPEN\r\n";
89 let event = WebsocketEvent::parse_frame(frame).expect("Parsing OPEN frame failed.");
90 assert!(matches!(event, WebsocketEvent::Open));
91 }
92
93 #[test]
94 fn parse_ping() {
95 let frame = b"PING\r\n";
96 let event = WebsocketEvent::parse_frame(frame).expect("Parsing PING frame failed.");
97 assert!(matches!(event, WebsocketEvent::Ping));
98 }
99
100 #[test]
101 fn parse_disconnect() {
102 let frame = b"DISCONNECT\r\n";
103 let event = WebsocketEvent::parse_frame(frame).expect("Parsing DISCONNECT frame failed.");
104 assert!(matches!(event, WebsocketEvent::Disconnect));
105 }
106
107 #[test]
108 fn parse_close() {
109 let frame = b"CLOSE 2\r\n\x03\xe8\r\n";
110 let event = WebsocketEvent::parse_frame(frame).expect("Parsing CLOSE frame failed.");
111 assert!(matches!(event, WebsocketEvent::Close(_)));
112 }
113
114 #[test]
115 fn parse_close_no_body() {
116 let frame = b"CLOSE\r\n\r\n";
117 let event =
118 WebsocketEvent::parse_frame(frame).expect("Parsing CLOSE frame (no body) failed.");
119 assert!(matches!(event, WebsocketEvent::Close(&[])));
120 }
121
122 #[test]
123 fn parse_text() {
124 let frame = b"TEXT 5\r\nhello\r\n";
125 let event = WebsocketEvent::parse_frame(frame).expect("Parsing TEXT frame failed.");
126 assert!(matches!(event, WebsocketEvent::Text(b"hello")));
127 }
128
129 #[test]
130 fn parse_text_2() {
131 let frame = b"TEXT B\r\nhello world\r\n";
132 let event = WebsocketEvent::parse_frame(frame).expect("Parsing TEXT frame failed.");
133 assert!(matches!(event, WebsocketEvent::Text(b"hello world")));
134 }
135
136 #[test]
137 fn parse_text_3() {
138 let frame = b"TEXT 1C\r\nhere is another nice message\r\n";
139 let event = WebsocketEvent::parse_frame(frame).expect("Parsing TEXT frame failed.");
140 assert!(matches!(
141 event,
142 WebsocketEvent::Text(b"here is another nice message")
143 ));
144 }
145}