Skip to main content

http_ws/
proto.rs

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