Skip to main content

ntex/ws/
proto.rs

1use std::fmt;
2
3use base64::{Engine, engine::general_purpose::STANDARD as base64};
4
5/// Operation codes as part of rfc6455.
6#[derive(Debug, Eq, PartialEq, Clone, Copy)]
7pub enum OpCode {
8    /// Indicates a continuation frame of a fragmented message.
9    Continue,
10    /// Indicates a text data frame.
11    Text,
12    /// Indicates a binary data frame.
13    Binary,
14    /// Indicates a close control frame.
15    Close,
16    /// Indicates a ping control frame.
17    Ping,
18    /// Indicates a pong control frame.
19    Pong,
20    /// Indicates an invalid opcode was received.
21    Bad,
22}
23
24impl fmt::Display for OpCode {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match *self {
27            OpCode::Continue => write!(f, "CONTINUE"),
28            OpCode::Text => write!(f, "TEXT"),
29            OpCode::Binary => write!(f, "BINARY"),
30            OpCode::Close => write!(f, "CLOSE"),
31            OpCode::Ping => write!(f, "PING"),
32            OpCode::Pong => write!(f, "PONG"),
33            OpCode::Bad => write!(f, "BAD"),
34        }
35    }
36}
37
38impl From<OpCode> for u8 {
39    fn from(code: OpCode) -> u8 {
40        match code {
41            OpCode::Continue => 0,
42            OpCode::Text => 1,
43            OpCode::Binary => 2,
44            OpCode::Close => 8,
45            OpCode::Ping => 9,
46            OpCode::Pong => 10,
47            OpCode::Bad => {
48                log::error!("Attempted to convert invalid opcode to u8. This is a bug.");
49                8 // if this somehow happens, a close frame will help us tear down quickly
50            }
51        }
52    }
53}
54
55impl From<u8> for OpCode {
56    fn from(byte: u8) -> OpCode {
57        match byte {
58            0 => OpCode::Continue,
59            1 => OpCode::Text,
60            2 => OpCode::Binary,
61            8 => OpCode::Close,
62            9 => OpCode::Ping,
63            10 => OpCode::Pong,
64            _ => OpCode::Bad,
65        }
66    }
67}
68
69/// Status code used to indicate why an endpoint is closing the `WebSocket`
70/// connection.
71#[derive(Debug, Eq, PartialEq, Clone, Copy)]
72pub enum CloseCode {
73    /// Indicates a normal closure, meaning that the purpose for
74    /// which the connection was established has been fulfilled.
75    Normal,
76    /// Indicates that an endpoint is "going away", such as a server
77    /// going down or a browser having navigated away from a page.
78    Away,
79    /// Indicates that an endpoint is terminating the connection due
80    /// to a protocol error.
81    Protocol,
82    /// Indicates that an endpoint is terminating the connection
83    /// because it has received a type of data it cannot accept (e.g., an
84    /// endpoint that understands only text data MAY send this if it
85    /// receives a binary message).
86    Unsupported,
87    /// Indicates an abnormal closure. If the abnormal closure was due to an
88    /// error, this close code will not be used. Instead, the `on_error` method
89    /// of the handler will be called with the error. However, if the connection
90    /// is simply dropped, without an error, this close code will be sent to the
91    /// handler.
92    Abnormal,
93    /// Indicates that an endpoint is terminating the connection
94    /// because it has received data within a message that was not
95    /// consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\]
96    /// data within a text message).
97    Invalid,
98    /// Indicates that an endpoint is terminating the connection
99    /// because it has received a message that violates its policy.  This
100    /// is a generic status code that can be returned when there is no
101    /// other more suitable status code (e.g., Unsupported or Size) or if there
102    /// is a need to hide specific details about the policy.
103    Policy,
104    /// Indicates that an endpoint is terminating the connection
105    /// because it has received a message that is too big for it to
106    /// process.
107    Size,
108    /// Indicates that an endpoint (client) is terminating the
109    /// connection because it has expected the server to negotiate one or
110    /// more extension, but the server didn't return them in the response
111    /// message of the WebSocket handshake.  The list of extensions that
112    /// are needed should be given as the reason for closing.
113    /// Note that this status code is not used by the server, because it
114    /// can fail the WebSocket handshake instead.
115    Extension,
116    /// Indicates that a server is terminating the connection because
117    /// it encountered an unexpected condition that prevented it from
118    /// fulfilling the request.
119    Error,
120    /// Indicates that the server is restarting. A client may choose to
121    /// reconnect, and if it does, it should use a randomized delay of 5-30
122    /// seconds between attempts.
123    Restart,
124    /// Indicates that the server is overloaded and the client should either
125    /// connect to a different IP (when multiple targets exist), or
126    /// reconnect to the same IP when a user has performed an action.
127    Again,
128    #[doc(hidden)]
129    Tls,
130    #[doc(hidden)]
131    Other(u16),
132}
133
134impl From<CloseCode> for u16 {
135    fn from(code: CloseCode) -> u16 {
136        match code {
137            CloseCode::Normal => 1000,
138            CloseCode::Away => 1001,
139            CloseCode::Protocol => 1002,
140            CloseCode::Unsupported => 1003,
141            CloseCode::Abnormal => 1006,
142            CloseCode::Invalid => 1007,
143            CloseCode::Policy => 1008,
144            CloseCode::Size => 1009,
145            CloseCode::Extension => 1010,
146            CloseCode::Error => 1011,
147            CloseCode::Restart => 1012,
148            CloseCode::Again => 1013,
149            CloseCode::Tls => 1015,
150            CloseCode::Other(code) => code,
151        }
152    }
153}
154
155impl From<u16> for CloseCode {
156    fn from(code: u16) -> CloseCode {
157        match code {
158            1000 => CloseCode::Normal,
159            1001 => CloseCode::Away,
160            1002 => CloseCode::Protocol,
161            1003 => CloseCode::Unsupported,
162            1006 => CloseCode::Abnormal,
163            1007 => CloseCode::Invalid,
164            1008 => CloseCode::Policy,
165            1009 => CloseCode::Size,
166            1010 => CloseCode::Extension,
167            1011 => CloseCode::Error,
168            1012 => CloseCode::Restart,
169            1013 => CloseCode::Again,
170            1015 => CloseCode::Tls,
171            _ => CloseCode::Other(code),
172        }
173    }
174}
175
176#[derive(Debug, Eq, PartialEq, Clone)]
177/// Reason for closing the connection
178pub struct CloseReason {
179    /// Exit code
180    pub code: CloseCode,
181    /// Optional description of the exit code
182    pub description: Option<String>,
183}
184
185impl From<CloseCode> for CloseReason {
186    fn from(code: CloseCode) -> Self {
187        CloseReason {
188            code,
189            description: None,
190        }
191    }
192}
193
194impl<T: Into<String>> From<(CloseCode, T)> for CloseReason {
195    fn from(info: (CloseCode, T)) -> Self {
196        CloseReason {
197            code: info.0,
198            description: Some(info.1.into()),
199        }
200    }
201}
202
203static WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
204
205// TODO: hash is always same size, we don't need String
206pub fn hash_key(key: &[u8]) -> String {
207    use sha1::Digest;
208    let mut hasher = sha1::Sha1::new();
209
210    hasher.update(key);
211    hasher.update(WS_GUID.as_bytes());
212
213    base64.encode(&hasher.finalize()[..])
214}
215
216#[cfg(test)]
217#[allow(unused_imports, unused_variables, dead_code)]
218mod test {
219    use super::*;
220
221    macro_rules! opcode_into {
222        ($from:expr => $opcode:pat) => {
223            match OpCode::from($from) {
224                e @ $opcode => (),
225                e => unreachable!("{:?}", e),
226            }
227        };
228    }
229
230    macro_rules! opcode_from {
231        ($from:expr => $opcode:pat) => {
232            let res: u8 = $from.into();
233            match res {
234                e @ $opcode => (),
235                e => unreachable!("{:?}", e),
236            }
237        };
238    }
239
240    #[test]
241    fn test_to_opcode() {
242        opcode_into!(0 => OpCode::Continue);
243        opcode_into!(1 => OpCode::Text);
244        opcode_into!(2 => OpCode::Binary);
245        opcode_into!(8 => OpCode::Close);
246        opcode_into!(9 => OpCode::Ping);
247        opcode_into!(10 => OpCode::Pong);
248        opcode_into!(99 => OpCode::Bad);
249    }
250
251    #[test]
252    fn test_from_opcode() {
253        opcode_from!(OpCode::Continue => 0);
254        opcode_from!(OpCode::Text => 1);
255        opcode_from!(OpCode::Binary => 2);
256        opcode_from!(OpCode::Close => 8);
257        opcode_from!(OpCode::Ping => 9);
258        opcode_from!(OpCode::Pong => 10);
259    }
260
261    #[test]
262    #[should_panic]
263    #[allow(clippy::should_panic_without_expect)]
264    fn test_from_opcode_debug() {
265        opcode_from!(OpCode::Bad => 99);
266    }
267
268    #[test]
269    fn test_from_opcode_display() {
270        assert_eq!(format!("{}", OpCode::Continue), "CONTINUE");
271        assert_eq!(format!("{}", OpCode::Text), "TEXT");
272        assert_eq!(format!("{}", OpCode::Binary), "BINARY");
273        assert_eq!(format!("{}", OpCode::Close), "CLOSE");
274        assert_eq!(format!("{}", OpCode::Ping), "PING");
275        assert_eq!(format!("{}", OpCode::Pong), "PONG");
276        assert_eq!(format!("{}", OpCode::Bad), "BAD");
277    }
278
279    #[test]
280    fn test_hash_key() {
281        let hash = hash_key(b"hello actix-web");
282        assert_eq!(&hash, "cR1dlyUUJKp0s/Bel25u5TgvC3E=");
283    }
284
285    #[test]
286    fn closecode_from_u16() {
287        assert_eq!(CloseCode::from(1000u16), CloseCode::Normal);
288        assert_eq!(CloseCode::from(1001u16), CloseCode::Away);
289        assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol);
290        assert_eq!(CloseCode::from(1003u16), CloseCode::Unsupported);
291        assert_eq!(CloseCode::from(1006u16), CloseCode::Abnormal);
292        assert_eq!(CloseCode::from(1007u16), CloseCode::Invalid);
293        assert_eq!(CloseCode::from(1008u16), CloseCode::Policy);
294        assert_eq!(CloseCode::from(1009u16), CloseCode::Size);
295        assert_eq!(CloseCode::from(1010u16), CloseCode::Extension);
296        assert_eq!(CloseCode::from(1011u16), CloseCode::Error);
297        assert_eq!(CloseCode::from(1012u16), CloseCode::Restart);
298        assert_eq!(CloseCode::from(1013u16), CloseCode::Again);
299        assert_eq!(CloseCode::from(1015u16), CloseCode::Tls);
300        assert_eq!(CloseCode::from(2000u16), CloseCode::Other(2000));
301    }
302
303    #[test]
304    fn closecode_into_u16() {
305        assert_eq!(1000u16, Into::<u16>::into(CloseCode::Normal));
306        assert_eq!(1001u16, Into::<u16>::into(CloseCode::Away));
307        assert_eq!(1002u16, Into::<u16>::into(CloseCode::Protocol));
308        assert_eq!(1003u16, Into::<u16>::into(CloseCode::Unsupported));
309        assert_eq!(1006u16, Into::<u16>::into(CloseCode::Abnormal));
310        assert_eq!(1007u16, Into::<u16>::into(CloseCode::Invalid));
311        assert_eq!(1008u16, Into::<u16>::into(CloseCode::Policy));
312        assert_eq!(1009u16, Into::<u16>::into(CloseCode::Size));
313        assert_eq!(1010u16, Into::<u16>::into(CloseCode::Extension));
314        assert_eq!(1011u16, Into::<u16>::into(CloseCode::Error));
315        assert_eq!(1012u16, Into::<u16>::into(CloseCode::Restart));
316        assert_eq!(1013u16, Into::<u16>::into(CloseCode::Again));
317        assert_eq!(1015u16, Into::<u16>::into(CloseCode::Tls));
318        assert_eq!(2000u16, Into::<u16>::into(CloseCode::Other(2000)));
319    }
320}