websocket_rs/
codec.rs

1//! FOR FUTURE ME (me talking to me in the future)
2//! =============
3//! Well oussama if you come next year and you did take a look at this code,
4//! and start blaming yourself on why you wasted time doing this, like you
5//! always do, then read the following:
6//!
7//! MOTIVATION
8//! ==========
9//! I tried to integrate a couple of websockets crates but boy it was just
10//! plain painfull, way too complicated and time consuming when all i need is
11//! get the data that the client sent and move on with my life.
12//!
13//! So i find it to be easier and straight forward (thanks to tokio) to simply
14//! implement a decoder and encoder for websocket frames.
15//!
16//! This is in no way a full implementation of RFC 6455, but rather an
17//! implementation in a controlled environment that only adds features required
18//! by the application, and since the client will always be a legit browser
19//! then ill safely make some assumptions and skip some checks (like validating
20//! the frame structure).
21//!
22//! My minimal websocket implementation will only be used by xtermjs frontend
23//! using AttachAddon.
24//!
25//! FEATURES
26//! ========
27//! ```ignore
28//! [+] Receive opcode TEXT
29//! [+] Pong when Ping is received
30//! [+] Handle all payload lengths (**le 125**, **=126**, **=127**)
31//! [+] Mask key
32//! [+] Close websocket connection
33//! [+] Read fragmented payload
34//! [+] Extract websocket to its own crate
35//! [ ] Add binary support
36//! [ ] Send fragmented frame when the size reaches a treshold
37//! [ ] Schedule ping/pong
38//! [ ] Pong with application data included in ping
39//! [ ] Keep track of connected clients
40//! ```
41//!
42//! REFERENCE
43//! =========
44//! [RFC 6455 5.2](https://tools.ietf.org/html/rfc6455#section-5.2)
45//!
46//!  ```ignore
47//!  0                   1                   2                   3
48//!  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
49//! +-+-+-+-+-------+-+-------------+-------------------------------+
50//! |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
51//! |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
52//! |N|V|V|V|       |S|             |   (if payload len==126/127)   |
53//! | |1|2|3|       |K|             |                               |
54//! +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
55//! |     Extended payload length continued, if payload len == 127  |
56//! + - - - - - - - - - - - - - - - +-------------------------------+
57//! |                               |Masking-key, if MASK set to 1  |
58//! +-------------------------------+-------------------------------+
59//! | Masking-key (continued)       |          Payload Data         |
60//! +-------------------------------- - - - - - - - - - - - - - - - +
61//! :                     Payload Data continued ...                :
62//! + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
63//! |                     Payload Data continued ...                |
64//! +---------------------------------------------------------------+
65//! ```
66//!
67//! ```ignore
68//! FIN (1 bit): if 1 -> final fragment
69//! RSV1 RSV2 RSV3 (3 bit): Must be 0 unless an extension defines them (SKIP)
70//! Opcode (4 bit): Always assume its good (data sent from browser)
71//!   - 0   -> continuation frame
72//!   - 1   -> text frame
73//!   - 2   -> binary frame
74//!   - 3-7 -> reserved for further non-control frames
75//!   - 8   -> connection closed
76//!   - 9   -> ping
77//!   - A   -> pong
78//!   - B-F -> reserved for further control frames
79//! Mask (1 bit): if 1 -> masking key is present. #section 5.3
80//! Payload length (7 bit, 7+16 bit, 7+64 bit): length of the payload data
81//!   - 0-125 -> thats the payload length
82//!   - 126 -> next 2 bytes (UNSIGNED) are the payload length
83//!   - 127 -> next 8 bytes (UNSIGNED) are the payload length
84//! Masking key (0 or 4 bytes): present if mask bit is set to 1. #section 5.3
85//! Payload data (x+y bytes): extension data + application data
86//! Extension data (x bytes): is 0 unless an extension is negotiated (not in our case)
87//! Application data (y bytes): is payload length - length of extension data
88//! ```
89
90use std::convert::TryInto;
91
92use bytes::{ BytesMut, Buf, BufMut };
93use tokio_util::codec::{ Decoder, Encoder };
94
95use crate::{
96    Message,
97    Opcode,
98    WebsocketError,
99    WebsocketResult,
100};
101
102#[derive(Debug, Default)]
103pub struct WebsocketCodec {
104    fin: bool,
105    opcode: Opcode,
106    payload: BytesMut,
107}
108
109impl WebsocketCodec {
110    #[inline(always)]
111    fn next_frame(&mut self, buf: &mut BytesMut) -> Option<()> {
112        if buf.len() < 6 {
113            return None
114        }
115
116        self.fin = (0x80 & buf[0]) >> 7 != 0;
117
118        self._set_opcode(0x0F & buf[0]);
119
120        if self.opcode == Opcode::Text {
121            let (payload_len, payload_idx, mask_key) = self._payload_meta(buf);
122
123            if buf[payload_idx..].len() < payload_len {
124                return None
125            }
126
127            self._unmask(buf, payload_len, payload_idx, mask_key);
128
129            self.payload.extend_from_slice(&buf[payload_idx..payload_idx + payload_len]);
130            buf.advance(payload_idx + payload_len);
131
132            return Some(())
133        }
134
135        if
136            self.opcode == Opcode::Ping
137            || self.opcode == Opcode::Pong
138            || self.opcode == Opcode::Close
139        {
140            buf.advance(buf.len());
141            return Some(())
142        }
143
144        unimplemented!()
145    }
146
147    #[inline(always)]
148    fn _unmask(&mut self, buf: &mut BytesMut, len: usize, idx: usize, key: [u8; 4]) {
149        if (0x80 & buf[1]) >> 7 != 0 {
150            for i in idx..(idx + len) {
151                buf[i] ^= key[(i - idx) % 4];
152            }
153        }
154    }
155
156    #[inline(always)]
157    fn _payload_meta(&self, buf: &BytesMut) -> (usize, usize, [u8; 4]) {
158        let len = (0x7F & buf[1]) as usize;
159
160        if len == 127 {
161            return (
162                usize::from_be_bytes(buf[2..10].try_into().unwrap()),
163                14,
164                buf[10..14].try_into().unwrap()
165            )
166        }
167
168        if len == 126 {
169            return (
170                u16::from_be_bytes(buf[2..4].try_into().unwrap()) as usize,
171                8,
172                buf[4..8].try_into().unwrap()
173            )
174        }
175
176        (len, 6, buf[2..6].try_into().unwrap())
177    }
178
179    #[inline(always)]
180    fn _set_opcode(&mut self, opcode: u8) {
181        if opcode == 1 { self.opcode = Opcode::Text; }
182        else if opcode == 0x9 { self.opcode = Opcode::Ping; }
183        else if opcode == 0xA { self.opcode = Opcode::Pong; }
184        else if opcode == 0x8 { self.opcode = Opcode::Close; }
185        else { self.opcode = Opcode::Unknown; }
186    }
187}
188
189impl Decoder for WebsocketCodec {
190    type Item = Message;
191    type Error = WebsocketError;
192
193    fn decode(&mut self, buf: &mut BytesMut) -> WebsocketResult<Option<Self::Item>> {
194        while self.next_frame(buf).is_some() {
195            if self.fin && (
196                self.opcode == Opcode::Text
197                || self.opcode == Opcode::Ping
198                || self.opcode == Opcode::Pong
199                || self.opcode == Opcode::Close
200            ) {
201                return Ok(Some(Message {
202                    opcode: self.opcode,
203                    payload: self.payload.to_bytes(),
204                }))
205            }
206        }
207
208        Ok(None)
209    }
210}
211
212impl Encoder<Message> for WebsocketCodec {
213    type Error = WebsocketError;
214
215    fn encode(&mut self, msg: Message, dst: &mut BytesMut) -> WebsocketResult<()> {
216        if msg.opcode == Opcode::Pong {
217            // TODO RFC 5.5.3 must have identical application data sent by the
218            // client.
219            dst.extend_from_slice(&[138, 0]);
220        }
221
222        if msg.opcode == Opcode::Text {
223            dst.put_u8(0x81);
224
225            if msg.payload.len() <= 125 {
226                dst.put_u8(msg.payload.len() as u8);
227            } else if msg.payload.len() == 126 {
228                dst.put_u8(126);
229                dst.put_u16(msg.payload.len() as u16);
230            } else {
231                dst.put_u8(127);
232                dst.put_u64(msg.payload.len() as u64);
233            }
234
235            dst.put_slice(&msg.payload[..]);
236        }
237
238        Ok(())
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    extern crate test;
245
246    use super::*;
247    use bytes::BytesMut;
248    use std::str;
249    use test::Bencher;
250
251    macro_rules! frame {
252        ($buf:expr, $payload:expr, $opcode:expr) => {
253            let mut buf: BytesMut = $buf.as_bytes()
254                .chunks(2)
255                .map(|s| u8::from_str_radix(unsafe { str::from_utf8_unchecked(s) }, 16).unwrap())
256                .collect();
257
258            let mut wsc = WebsocketCodec::default();
259
260            if let Some(_) = wsc.next_frame(&mut buf) {
261                let payload = unsafe { str::from_utf8_unchecked(&wsc.payload) };
262                assert!(payload == $payload);
263                assert!(wsc.opcode == $opcode);
264            } else {
265                assert!(false, "failed to extract frame from '{:x}'")
266            }
267        }
268    }
269
270    #[test]
271    fn text_frame_with_no_payload() {
272        frame!("8180e0350bbc", "", Opcode::Text);
273    }
274
275    #[test]
276    fn text_frame_with_payload_len_eq_1() {
277        frame!("8181f3dba99092", "a", Opcode::Text);
278    }
279
280    #[test]
281    fn text_frame_with_hello_world_payload() {
282        frame!("818bb013fc40d876902cdf338b2fc27f98", "hello world", Opcode::Text);
283    }
284
285    #[test]
286    fn text_frame_payload_125() {
287        frame!(
288            "81fd16f9d90b5795b52b6291b87f369eb562628dbc7965d9b0783697b67f369eb66772d99f6a7f8bf96265d9bf646395f52b7797bd2b7096ac673690aa2b7098b0792cd99164609cab2b6291ab64639eb12b6291bc2b7096be2b7797bd2b7090b57f7e80f96a7f8bf72b36adb16e659cf97d7f96b56e788df96f7395b06c7e8daa2b7e",
289            "All that glitters is not gold Fair is foul, and foul is fair: Hover through the fog and filthy air.  These violent delights h",
290            Opcode::Text
291        );
292    }
293
294    #[bench]
295    fn bench_text_frame_payload_125(b: &mut Bencher) {
296        // 1,570 ns/iter (+/- 50)
297        b.iter(|| text_frame_payload_125())
298    }
299
300    #[test]
301    fn text_frame_long_payload() {
302        frame!(
303            "81fe020d6742ba33262ed613132adb474725d65a1336df411462d340472cd5474725d55f0348fc520e309a5a1462dc5c122e9613062cde13012dcf5f472bc9130123d3415d62f25c1127c813132ac85c1225d213132adf13012ddd13062cde13012bd6470f3b9a520e309439332adf400262cc5a082edf5d1362de560b2bdd5b13319a5b0634df13112bd55f022cce13022cde40496c94392f27d65f472bc913022fca471e62db5d0362db5f0b62ce5b0262de56112bd6404723c856472adf41026cb0711e62ce5b0262ca410e21d15a09259a5c0162d74a4736d2460a20c91f4711d55e0236d25a09259a440e21d1560362ce5b0e319a44063b9a50082fdf404962f543022c96130b2dd958146e9a640f2ddf4502309a58092dd9581463b0670f279a5f0626c313032dce5b4732c85c1327c9474736d55c472fcf500f6e9a5e0236d25a0929c91d6d00c856112bce4a472bc913132adf13142dcf5f472ddc13102bce1d6d0bdc130a37c95a0462d8564736d2564724d55c0362d555472ed545026e9a430b23c313082c9439292dcd130e319a470f279a440e2cce561562d555472dcf414726d340042dd447022cce1d6d05d55c0362d45a002ace1f4725d55c0362d45a002ace124732db41132bd454472bc9131437d95b4731cd5602369a400830c85c106e9a670f23ce132e62c95b062ed6131423c313002dd557472cd3540f369a470e2ed6130e369a510262d75c1530d54449",
304            "All that glitters is not gold
305Fair is foul, and foul is fair: Hover through the fog and filthy air.
306These violent delights have violent ends...
307Hell is empty and all the devils are here.
308By the pricking of my thumbs, Something wicked this way comes. Open, locks, Whoever knocks!
309The lady doth protest too much, methinks.
310Brevity is the soul of wit.
311If music be the food of love, play on.
312Now is the winter of our discontent.
313Good night, good night! parting is such sweet sorrow, That I shall say good night till it be morrow.",
314            Opcode::Text
315        );
316    }
317
318    #[bench]
319    fn bench_text_frame_long_payload(b: &mut Bencher) {
320        // 6,795 ns/iter (+/- 172)
321        b.iter(|| text_frame_long_payload())
322    }
323}