Skip to main content

actix_http/ws/
proto.rs

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