http_ws/
proto.rs

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