actix_http/ws/
proto.rs

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