Skip to main content

grip_control/
parse.rs

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}