rama_ws/protocol/frame/
coding.rs

1//! Various codes defined in RFC 6455.
2
3use std::{
4    convert::{From, Into},
5    fmt,
6};
7
8use crate::ProtocolError;
9/// WebSocket message opcode as in RFC 6455.
10#[derive(Debug, PartialEq, Eq, Clone, Copy)]
11pub enum OpCode {
12    /// Data (text or binary).
13    Data(OpCodeData),
14    /// Control message (close, ping, pong).
15    Control(OpCodeControl),
16}
17
18/// Data opcodes as in RFC 6455
19#[derive(Debug, PartialEq, Eq, Clone, Copy)]
20pub enum OpCodeData {
21    /// 0x0 denotes a continuation frame
22    Continue,
23    /// 0x1 denotes a text frame
24    Text,
25    /// 0x2 denotes a binary frame
26    Binary,
27    /// 0x3-7 are reserved for further non-control frames
28    Reserved(u8),
29}
30
31/// Control opcodes as in RFC 6455
32#[derive(Debug, PartialEq, Eq, Clone, Copy)]
33pub enum OpCodeControl {
34    /// 0x8 denotes a connection close
35    Close,
36    /// 0x9 denotes a ping
37    Ping,
38    /// 0xa denotes a pong
39    Pong,
40    /// 0xb-f are reserved for further control frames
41    Reserved(u8),
42}
43
44impl fmt::Display for OpCodeData {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        match *self {
47            Self::Continue => write!(f, "CONTINUE"),
48            Self::Text => write!(f, "TEXT"),
49            Self::Binary => write!(f, "BINARY"),
50            Self::Reserved(x) => write!(f, "RESERVED_DATA_{x}"),
51        }
52    }
53}
54
55impl fmt::Display for OpCodeControl {
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        match *self {
58            Self::Close => write!(f, "CLOSE"),
59            Self::Ping => write!(f, "PING"),
60            Self::Pong => write!(f, "PONG"),
61            Self::Reserved(x) => write!(f, "RESERVED_CONTROL_{x}"),
62        }
63    }
64}
65
66impl fmt::Display for OpCode {
67    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68        match *self {
69            Self::Data(d) => d.fmt(f),
70            Self::Control(c) => c.fmt(f),
71        }
72    }
73}
74
75impl From<OpCode> for u8 {
76    fn from(code: OpCode) -> Self {
77        use self::{
78            OpCodeControl::{Close, Ping, Pong},
79            OpCodeData::{Binary, Continue, Text},
80        };
81        match code {
82            OpCode::Data(Continue) => 0,
83            OpCode::Data(Text) => 1,
84            OpCode::Data(Binary) => 2,
85
86            OpCode::Control(Close) => 8,
87            OpCode::Control(Ping) => 9,
88            OpCode::Control(Pong) => 10,
89
90            OpCode::Data(self::OpCodeData::Reserved(i))
91            | OpCode::Control(self::OpCodeControl::Reserved(i)) => i,
92        }
93    }
94}
95
96impl TryFrom<u8> for OpCode {
97    type Error = ProtocolError;
98
99    fn try_from(byte: u8) -> Result<Self, Self::Error> {
100        use self::{
101            OpCodeControl::{Close, Ping, Pong},
102            OpCodeData::{Binary, Continue, Text},
103        };
104        Ok(match byte {
105            0 => Self::Data(Continue),
106            1 => Self::Data(Text),
107            2 => Self::Data(Binary),
108            i @ 3..=7 => Self::Data(self::OpCodeData::Reserved(i)),
109            8 => Self::Control(Close),
110            9 => Self::Control(Ping),
111            10 => Self::Control(Pong),
112            i @ 11..=15 => Self::Control(self::OpCodeControl::Reserved(i)),
113            _ => return Err(ProtocolError::InvalidOpcode(byte)),
114        })
115    }
116}
117
118/// Status code used to indicate why an endpoint is closing the WebSocket connection.
119#[derive(Debug, Eq, PartialEq, Clone, Copy)]
120#[allow(clippy::manual_non_exhaustive)]
121pub enum CloseCode {
122    /// Indicates a normal closure, meaning that the purpose for
123    /// which the connection was established has been fulfilled.
124    Normal,
125    /// Indicates that an endpoint is "going away", such as a server
126    /// going down or a browser having navigated away from a page.
127    Away,
128    /// Indicates that an endpoint is terminating the connection due
129    /// to a protocol error.
130    Protocol,
131    /// Indicates that an endpoint is terminating the connection
132    /// because it has received a type of data it cannot accept (e.g., an
133    /// endpoint that understands only text data MAY send this if it
134    /// receives a binary message).
135    Unsupported,
136    /// Indicates that no status code was included in a closing frame. This
137    /// close code makes it possible to use a single method, `on_close` to
138    /// handle even cases where no close code was provided.
139    Status,
140    /// Indicates an abnormal closure. If the abnormal closure was due to an
141    /// error, this close code will not be used. Instead, the `on_error` method
142    /// of the handler will be called with the error. However, if the connection
143    /// is simply dropped, without an error, this close code will be sent to the
144    /// handler.
145    Abnormal,
146    /// Indicates that an endpoint is terminating the connection
147    /// because it has received data within a message that was not
148    /// consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\]
149    /// data within a text message).
150    Invalid,
151    /// Indicates that an endpoint is terminating the connection
152    /// because it has received a message that violates its policy.  This
153    /// is a generic status code that can be returned when there is no
154    /// other more suitable status code (e.g., Unsupported or Size) or if there
155    /// is a need to hide specific details about the policy.
156    Policy,
157    /// Indicates that an endpoint is terminating the connection
158    /// because it has received a message that is too big for it to
159    /// process.
160    Size,
161    /// Indicates that an endpoint (client) is terminating the
162    /// connection because it has expected the server to negotiate one or
163    /// more extension, but the server didn't return them in the response
164    /// message of the WebSocket handshake.  The list of extensions that
165    /// are needed should be given as the reason for closing.
166    /// Note that this status code is not used by the server, because it
167    /// can fail the WebSocket handshake instead.
168    Extension,
169    /// Indicates that a server is terminating the connection because
170    /// it encountered an unexpected condition that prevented it from
171    /// fulfilling the request.
172    Error,
173    /// Indicates that the server is restarting. A client may choose to reconnect,
174    /// and if it does, it should use a randomized delay of 5-30 seconds between attempts.
175    Restart,
176    /// Indicates that the server is overloaded and the client should either connect
177    /// to a different IP (when multiple targets exist), or reconnect to the same IP
178    /// when a user has performed an action.
179    Again,
180    #[doc(hidden)]
181    Tls,
182    #[doc(hidden)]
183    Reserved(u16),
184    #[doc(hidden)]
185    Iana(u16),
186    #[doc(hidden)]
187    Library(u16),
188    #[doc(hidden)]
189    Bad(u16),
190}
191
192impl CloseCode {
193    /// Check if this CloseCode is allowed.
194    #[must_use]
195    pub fn is_allowed(self) -> bool {
196        !matches!(
197            self,
198            Self::Bad(_) | Self::Reserved(_) | Self::Status | Self::Abnormal | Self::Tls
199        )
200    }
201}
202
203impl fmt::Display for CloseCode {
204    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205        let code: u16 = self.into();
206        write!(f, "{code}")
207    }
208}
209
210impl From<CloseCode> for u16 {
211    fn from(code: CloseCode) -> Self {
212        match code {
213            CloseCode::Normal => 1000,
214            CloseCode::Away => 1001,
215            CloseCode::Protocol => 1002,
216            CloseCode::Unsupported => 1003,
217            CloseCode::Status => 1005,
218            CloseCode::Abnormal => 1006,
219            CloseCode::Invalid => 1007,
220            CloseCode::Policy => 1008,
221            CloseCode::Size => 1009,
222            CloseCode::Extension => 1010,
223            CloseCode::Error => 1011,
224            CloseCode::Restart => 1012,
225            CloseCode::Again => 1013,
226            CloseCode::Tls => 1015,
227            CloseCode::Reserved(code)
228            | CloseCode::Iana(code)
229            | CloseCode::Library(code)
230            | CloseCode::Bad(code) => code,
231        }
232    }
233}
234
235impl<'t> From<&'t CloseCode> for u16 {
236    fn from(code: &'t CloseCode) -> Self {
237        (*code).into()
238    }
239}
240
241impl From<u16> for CloseCode {
242    fn from(code: u16) -> Self {
243        match code {
244            1000 => Self::Normal,
245            1001 => Self::Away,
246            1002 => Self::Protocol,
247            1003 => Self::Unsupported,
248            1005 => Self::Status,
249            1006 => Self::Abnormal,
250            1007 => Self::Invalid,
251            1008 => Self::Policy,
252            1009 => Self::Size,
253            1010 => Self::Extension,
254            1011 => Self::Error,
255            1012 => Self::Restart,
256            1013 => Self::Again,
257            1015 => Self::Tls,
258            1016..=2999 => Self::Reserved(code),
259            3000..=3999 => Self::Iana(code),
260            4000..=4999 => Self::Library(code),
261            _ => Self::Bad(code),
262        }
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269
270    #[test]
271    fn opcode_from_u8() {
272        let byte = 2u8;
273        assert_eq!(
274            OpCode::try_from(byte).unwrap(),
275            OpCode::Data(OpCodeData::Binary)
276        );
277    }
278
279    #[test]
280    fn opcode_into_u8() {
281        let text = OpCode::Data(OpCodeData::Text);
282        let byte: u8 = text.into();
283        assert_eq!(byte, 1u8);
284    }
285
286    #[test]
287    fn closecode_from_u16() {
288        let byte = 1008u16;
289        assert_eq!(CloseCode::from(byte), CloseCode::Policy);
290    }
291
292    #[test]
293    fn closecode_into_u16() {
294        let text = CloseCode::Away;
295        let byte: u16 = text.into();
296        assert_eq!(byte, 1001u16);
297        assert_eq!(u16::from(text), 1001u16);
298    }
299}