Skip to main content

socketioxide_parser_common/
lib.rs

1#![warn(missing_docs)]
2//! The common parser sub-crate for the socketioxide crate.
3//!
4//! This is the default parser implementation.
5//!
6//! It is used to parse and serialize the common packet format of the socket.io protocol:
7//! ```text
8//! <packet type>[<# of binary attachments>-][<namespace>,][<acknowledgment id>][JSON-stringified payload without binary]
9//! + binary attachments extracted
10//! ```
11use std::{collections::VecDeque, sync::atomic::Ordering};
12
13use bytes::Bytes;
14
15use serde::{Deserialize, Serialize};
16use socketioxide_core::{
17    Str, Value,
18    packet::{Packet, PacketData},
19    parser::{Parse, ParseError, ParserError, ParserState},
20};
21
22mod de;
23mod ser;
24mod value;
25
26/// Parse and serialize from and into the socket.io common packet format.
27/// See details in the [socket.io protocol doc](https://socket.io/fr/docs/v4/socket-io-protocol/#packet-encoding).
28#[derive(Debug, Default, Clone, Copy)]
29pub struct CommonParser;
30
31impl Parse for CommonParser {
32    fn encode(self, packet: Packet) -> Value {
33        ser::serialize_packet(packet)
34    }
35
36    fn decode_str(self, state: &ParserState, value: Str) -> Result<Packet, ParseError> {
37        let (packet, incoming_binary_cnt) = de::deserialize_packet(value)?;
38        if packet.inner.is_binary() {
39            let incoming_binary_cnt = incoming_binary_cnt.ok_or(ParseError::InvalidAttachments)?;
40            if !is_bin_packet_complete(&packet.inner, incoming_binary_cnt) {
41                *state.partial_bin_packet.lock().unwrap() = Some(packet);
42                state
43                    .incoming_binary_cnt
44                    .store(incoming_binary_cnt, Ordering::Release);
45                Err(ParseError::NeedsMoreBinaryData)
46            } else {
47                Ok(packet)
48            }
49        } else {
50            Ok(packet)
51        }
52    }
53
54    fn decode_bin(self, state: &ParserState, data: Bytes) -> Result<Packet, ParseError> {
55        let packet = &mut *state.partial_bin_packet.lock().unwrap();
56        match packet {
57            Some(Packet {
58                inner:
59                    PacketData::BinaryEvent(Value::Str(_, binaries), _)
60                    | PacketData::BinaryAck(Value::Str(_, binaries), _),
61                ..
62            }) => {
63                let binaries = binaries.get_or_insert(VecDeque::new());
64                // We copy the data to avoid holding a ref to the engine.io
65                // websocket buffer too long.
66                binaries.push_back(Bytes::copy_from_slice(&data));
67                if state.incoming_binary_cnt.load(Ordering::Relaxed) > binaries.len() {
68                    Err(ParseError::NeedsMoreBinaryData)
69                } else {
70                    Ok(packet.take().unwrap())
71                }
72            }
73            _ => Err(ParseError::UnexpectedBinaryPacket),
74        }
75    }
76
77    #[inline]
78    fn encode_value<T: ?Sized + Serialize>(
79        self,
80        data: &T,
81        event: Option<&str>,
82    ) -> Result<Value, ParserError> {
83        value::to_value(data, event).map_err(ParserError::new)
84    }
85
86    #[inline]
87    fn decode_value<'de, T: Deserialize<'de>>(
88        self,
89        value: &'de mut Value,
90        with_event: bool,
91    ) -> Result<T, ParserError> {
92        value::from_value(value, with_event).map_err(ParserError::new)
93    }
94
95    fn decode_default<'de, T: Deserialize<'de>>(
96        self,
97        value: Option<&'de Value>,
98    ) -> Result<T, ParserError> {
99        if let Some(value) = value {
100            let data = value
101                .as_str()
102                .expect("CommonParser only supports string values");
103            serde_json::from_str(data).map_err(ParserError::new)
104        } else {
105            serde_json::from_str("{}").map_err(ParserError::new)
106        }
107    }
108
109    fn encode_default<T: ?Sized + Serialize>(self, data: &T) -> Result<Value, ParserError> {
110        let value = serde_json::to_string(data).map_err(ParserError::new)?;
111        Ok(Value::Str(Str::from(value), None))
112    }
113
114    #[inline]
115    fn read_event(self, value: &Value) -> Result<&str, ParserError> {
116        value::read_event(value).map_err(ParserError::new)
117    }
118}
119
120/// Check if the binary packet is complete, it means that all payloads have been received
121fn is_bin_packet_complete(packet: &PacketData, incoming_binary_cnt: usize) -> bool {
122    match &packet {
123        PacketData::BinaryEvent(Value::Str(_, binaries), _)
124        | PacketData::BinaryAck(Value::Str(_, binaries), _) => {
125            incoming_binary_cnt == binaries.as_ref().map(VecDeque::len).unwrap_or(0)
126        }
127        _ => true,
128    }
129}
130
131#[cfg(test)]
132mod test {
133    use std::collections::HashMap;
134
135    use crate::is_bin_packet_complete;
136
137    use super::*;
138    use serde_json::json;
139    use socketioxide_core::{Sid, packet::ConnectPacket};
140
141    fn to_event_value(data: &impl serde::Serialize, event: &str) -> Value {
142        CommonParser.encode_value(data, Some(event)).unwrap()
143    }
144
145    fn to_value(data: &impl serde::Serialize) -> Value {
146        CommonParser.encode_value(data, None).unwrap()
147    }
148    fn to_connect_value(data: &impl serde::Serialize) -> Value {
149        Value::Str(Str::from(serde_json::to_string(data).unwrap()), None)
150    }
151    fn encode(packet: Packet) -> String {
152        match CommonParser.encode(packet) {
153            Value::Str(d, _) => d.into(),
154            Value::Bytes(_) => panic!("testing only returns str"),
155        }
156    }
157    fn decode(value: String) -> Packet {
158        CommonParser
159            .decode_str(&Default::default(), value.into())
160            .unwrap()
161    }
162
163    #[test]
164    fn packet_decode_connect() {
165        let sid = Sid::new();
166        let payload = format!("0{}", json!({ "sid": sid }));
167        let packet = decode(payload);
168        let value = to_connect_value(&ConnectPacket { sid });
169        assert_eq!(Packet::connect("/", Some(value.clone())), packet);
170
171        let payload = format!("0/admin™,{}", json!({ "sid": sid }));
172        let packet = decode(payload);
173        assert_eq!(Packet::connect("/admin™", Some(value)), packet);
174    }
175
176    #[test]
177    fn packet_encode_connect() {
178        let sid = Sid::new();
179        let value = to_connect_value(&ConnectPacket { sid });
180        let payload = format!("0{}", json!({ "sid": sid }));
181        let packet = encode(Packet::connect("/", Some(value.clone())));
182        assert_eq!(packet, payload);
183
184        let payload = format!("0/admin™,{}", json!({ "sid": sid }));
185        let packet: String = encode(Packet::connect("/admin™", Some(value)));
186        assert_eq!(packet, payload);
187    }
188
189    // Disconnect
190
191    #[test]
192    fn packet_decode_disconnect() {
193        let payload = "1".to_string();
194        let packet = decode(payload);
195        assert_eq!(Packet::disconnect("/"), packet);
196
197        let payload = "1/admin™,".to_string();
198        let packet = decode(payload);
199        assert_eq!(Packet::disconnect("/admin™"), packet);
200    }
201
202    #[test]
203    fn packet_encode_disconnect() {
204        let payload = "1".to_string();
205        let packet = encode(Packet::disconnect("/"));
206        assert_eq!(packet, payload);
207
208        let payload = "1/admin™,".to_string();
209        let packet = encode(Packet::disconnect("/admin™"));
210        assert_eq!(packet, payload);
211    }
212
213    // Event(String, Value, Option<i64>),
214    #[test]
215    fn packet_decode_event() {
216        let payload = format!("2{}", json!(["event", { "data": "value" }]));
217        let packet = decode(payload);
218
219        assert_eq!(
220            Packet::event("/", to_event_value(&json!({"data": "value"}), "event")),
221            packet
222        );
223
224        // Check with ack ID
225        let payload = format!("21{}", json!(["event", { "data": "value" }]));
226        let packet = decode(payload);
227
228        let mut comparison_packet =
229            Packet::event("/", to_event_value(&json!({"data": "value"}), "event"));
230        comparison_packet.inner.set_ack_id(1);
231        assert_eq!(packet, comparison_packet);
232
233        // Check with NS
234        let payload = format!("2/admin™,{}", json!(["event", { "data": "value™" }]));
235        let packet = decode(payload);
236
237        assert_eq!(
238            Packet::event(
239                "/admin™",
240                to_event_value(&json!({"data": "value™"}), "event")
241            ),
242            packet
243        );
244
245        // Check with ack ID and NS
246        let payload = format!("2/admin™,1{}", json!(["event", { "data": "value™" }]));
247        let mut packet = decode(payload);
248        packet.inner.set_ack_id(1);
249
250        let mut comparison_packet = Packet::event(
251            "/admin™",
252            to_event_value(&json!({"data": "value™"}), "event"),
253        );
254        comparison_packet.inner.set_ack_id(1);
255
256        assert_eq!(packet, comparison_packet);
257    }
258
259    #[test]
260    fn packet_encode_event() {
261        let payload = format!("2{}", json!(["event", { "data": "value™" }]));
262        let packet = encode(Packet::event(
263            "/",
264            to_event_value(&json!({ "data": "value™" }), "event"),
265        ));
266
267        assert_eq!(packet, payload);
268
269        // Encode empty data
270        let payload = format!("2{}", json!(["event", []]));
271        let packet = encode(Packet::event("/", to_event_value(&json!([]), "event")));
272
273        assert_eq!(packet, payload);
274
275        // Encode with ack ID
276        let payload = format!("21{}", json!(["event", { "data": "value™" }]));
277        let mut packet = Packet::event("/", to_event_value(&json!({ "data": "value™" }), "event"));
278        packet.inner.set_ack_id(1);
279        let packet = encode(packet);
280
281        assert_eq!(packet, payload);
282
283        // Encode with NS
284        let payload = format!("2/admin™,{}", json!(["event", { "data": "value™" }]));
285        let packet = encode(Packet::event(
286            "/admin™",
287            to_event_value(&json!({"data": "value™"}), "event"),
288        ));
289
290        assert_eq!(packet, payload);
291
292        // Encode with NS and ack ID
293        let payload = format!("2/admin™,1{}", json!(["event", { "data": "value™" }]));
294        let mut packet = Packet::event(
295            "/admin™",
296            to_event_value(&json!({"data": "value™"}), "event"),
297        );
298        packet.inner.set_ack_id(1);
299        let packet = encode(packet);
300        assert_eq!(packet, payload);
301    }
302
303    // EventAck(Value, i64),
304    #[test]
305    fn packet_decode_event_ack() {
306        let payload = "354[\"data\"]".to_string();
307        let packet = decode(payload);
308
309        assert_eq!(Packet::ack("/", to_value(&json!("data")), 54), packet);
310
311        let payload = "3/admin™,54[\"data\"]".to_string();
312        let packet = decode(payload);
313
314        assert_eq!(Packet::ack("/admin™", to_value(&json!("data")), 54), packet);
315    }
316
317    #[test]
318    fn packet_encode_event_ack() {
319        let payload = "354[\"data\"]".to_string();
320        let packet = encode(Packet::ack("/", to_value(&json!("data")), 54));
321        assert_eq!(packet, payload);
322
323        let payload = "3/admin™,54[\"data\"]".to_string();
324        let packet = encode(Packet::ack("/admin™", to_value(&json!("data")), 54));
325        assert_eq!(packet, payload);
326    }
327
328    #[test]
329    fn packet_encode_connect_error() {
330        let payload = format!("4{}", json!({ "message": "Invalid namespace" }));
331        let packet = encode(Packet::connect_error("/", "Invalid namespace"));
332        assert_eq!(packet, payload);
333
334        let payload = format!("4/admin™,{}", json!({ "message": "Invalid namespace" }));
335        let packet = encode(Packet::connect_error("/admin™", "Invalid namespace"));
336        assert_eq!(packet, payload);
337    }
338
339    // BinaryEvent(String, BinaryPacket, Option<i64>),
340    #[test]
341    fn packet_encode_binary_event() {
342        let json = json!(["event", { "data": "value™" }, { "_placeholder": true, "num": 0}]);
343
344        let payload = format!("51-{json}");
345        let packet = encode(Packet::event(
346            "/",
347            to_event_value(
348                &(json!({ "data": "value™" }), Bytes::from_static(&[1])),
349                "event",
350            ),
351        ));
352
353        assert_eq!(packet, payload);
354
355        // Encode with ack ID
356        let payload = format!("51-254{json}");
357        let mut packet = Packet::event(
358            "/",
359            to_event_value(
360                &(json!({ "data": "value™" }), Bytes::from_static(&[1])),
361                "event",
362            ),
363        );
364        packet.inner.set_ack_id(254);
365        let packet = encode(packet);
366
367        assert_eq!(packet, payload);
368
369        // Encode with NS
370        let payload = format!("51-/admin™,{json}");
371        let packet = encode(Packet::event(
372            "/admin™",
373            to_event_value(
374                &(json!({ "data": "value™" }), Bytes::from_static(&[1])),
375                "event",
376            ),
377        ));
378
379        assert_eq!(packet, payload);
380
381        // Encode with NS and ack ID
382        let payload = format!("51-/admin™,254{json}");
383        let mut packet = Packet::event(
384            "/admin™",
385            to_event_value(
386                &(json!({ "data": "value™" }), Bytes::from_static(&[1])),
387                "event",
388            ),
389        );
390        packet.inner.set_ack_id(254);
391        let packet = encode(packet);
392        assert_eq!(packet, payload);
393    }
394
395    #[test]
396    fn packet_decode_binary_event() {
397        let json = json!(["event", { "data": "value™" },{ "_placeholder": true, "num": 0},{ "_placeholder": true, "num": 1}]);
398        let comparison_packet = |ack, ns: &'static str| {
399            let data = to_event_value(
400                &(
401                    json!({"data": "value™"}),
402                    Bytes::from_static(&[1]),
403                    Bytes::from_static(&[2]),
404                ),
405                "event",
406            );
407            Packet {
408                inner: PacketData::BinaryEvent(data, ack),
409                ns: ns.into(),
410            }
411        };
412        let state = ParserState::default();
413        let payload = format!("52-{json}");
414        assert!(matches!(
415            CommonParser.decode_str(&state, payload.into()),
416            Err(ParseError::NeedsMoreBinaryData)
417        ));
418        assert!(matches!(
419            CommonParser.decode_bin(&state, Bytes::from_static(&[1])),
420            Err(ParseError::NeedsMoreBinaryData)
421        ));
422        let packet = CommonParser
423            .decode_bin(&state, Bytes::from_static(&[2]))
424            .unwrap();
425
426        assert_eq!(packet, comparison_packet(None, "/"));
427
428        // Check with ack ID
429        let state = ParserState::default();
430        let payload = format!("52-254{json}");
431        assert!(matches!(
432            CommonParser.decode_str(&state, payload.into()),
433            Err(ParseError::NeedsMoreBinaryData)
434        ));
435        assert!(matches!(
436            CommonParser.decode_bin(&state, Bytes::from_static(&[1])),
437            Err(ParseError::NeedsMoreBinaryData)
438        ));
439        let packet = CommonParser
440            .decode_bin(&state, Bytes::from_static(&[2]))
441            .unwrap();
442
443        assert_eq!(packet, comparison_packet(Some(254), "/"));
444
445        // Check with NS
446        let state = ParserState::default();
447        let payload = format!("52-/admin™,{json}");
448        assert!(matches!(
449            CommonParser.decode_str(&state, payload.into()),
450            Err(ParseError::NeedsMoreBinaryData)
451        ));
452        assert!(matches!(
453            CommonParser.decode_bin(&state, Bytes::from_static(&[1])),
454            Err(ParseError::NeedsMoreBinaryData)
455        ));
456        let packet = CommonParser
457            .decode_bin(&state, Bytes::from_static(&[2]))
458            .unwrap();
459
460        assert_eq!(packet, comparison_packet(None, "/admin™"));
461
462        // Check with ack ID and NS
463        let state = ParserState::default();
464        let payload = format!("52-/admin™,254{json}");
465        assert!(matches!(
466            CommonParser.decode_str(&state, payload.into()),
467            Err(ParseError::NeedsMoreBinaryData)
468        ));
469        assert!(matches!(
470            CommonParser.decode_bin(&state, Bytes::from_static(&[1])),
471            Err(ParseError::NeedsMoreBinaryData)
472        ));
473        let packet = CommonParser
474            .decode_bin(&state, Bytes::from_static(&[2]))
475            .unwrap();
476
477        assert_eq!(packet, comparison_packet(Some(254), "/admin™"));
478    }
479
480    // BinaryAck(BinaryPacket, i64),
481    #[test]
482    fn packet_encode_binary_ack() {
483        let json = json!([{ "data": "value™" }, { "_placeholder": true, "num": 0}]);
484
485        let payload = format!("61-54{json}");
486        let packet = encode(Packet::ack(
487            "/",
488            to_value(&(json!({ "data": "value™" }), Bytes::from_static(&[1]))),
489            54,
490        ));
491
492        assert_eq!(packet, payload);
493
494        // Encode with NS
495        let payload = format!("61-/admin™,54{json}");
496        let packet = encode(Packet::ack(
497            "/admin™",
498            to_value(&(json!({ "data": "value™" }), Bytes::from_static(&[1]))),
499            54,
500        ));
501
502        assert_eq!(packet, payload);
503    }
504
505    #[test]
506    fn packet_decode_binary_ack() {
507        let json = json!([{ "data": "value™" }, { "_placeholder": true, "num": 0}]);
508        let comparison_packet = |ack, ns: &'static str| Packet {
509            inner: PacketData::BinaryAck(
510                to_value(&(json!({ "data": "value™" }), Bytes::from_static(&[1]))),
511                ack,
512            ),
513            ns: ns.into(),
514        };
515
516        let payload = format!("61-54{json}");
517        let state = ParserState::default();
518        assert!(matches!(
519            CommonParser.decode_str(&state, payload.into()),
520            Err(ParseError::NeedsMoreBinaryData)
521        ));
522        let packet = CommonParser
523            .decode_bin(&state, Bytes::from_static(&[1]))
524            .unwrap();
525
526        assert_eq!(packet, comparison_packet(54, "/"));
527
528        // Check with NS
529        let state = ParserState::default();
530        let payload = format!("61-/admin™,54{json}");
531        assert!(matches!(
532            CommonParser.decode_str(&state, payload.into()),
533            Err(ParseError::NeedsMoreBinaryData)
534        ));
535        let packet = CommonParser
536            .decode_bin(&state, Bytes::from_static(&[1]))
537            .unwrap();
538        assert_eq!(packet, comparison_packet(54, "/admin™"));
539    }
540
541    #[test]
542    fn packet_reject_invalid_binary_event() {
543        let payload = "5invalid".to_owned();
544        let err = CommonParser
545            .decode_str(&Default::default(), payload.into())
546            .unwrap_err();
547
548        assert!(matches!(err, ParseError::InvalidAttachments));
549    }
550
551    #[test]
552    fn decode_default_none() {
553        // Common parser should deserialize by default to an empty map to match the behavior of the
554        // socket.io client when deserializing incoming connect message without an auth payload.
555        let data = CommonParser.decode_default::<HashMap<String, ()>>(None);
556        assert!(matches!(data, Ok(d) if d.is_empty()));
557    }
558
559    #[test]
560    fn decode_default_some() {
561        // Common parser should deserialize by default to an empty map to match the behavior of the
562        // socket.io client when deserializing incoming connect message without an auth payload.
563        let data =
564            CommonParser.decode_default::<String>(Some(&Value::Str("\"test\"".into(), None)));
565        assert!(matches!(data, Ok(d) if d == "test"));
566    }
567
568    #[test]
569    fn encode_default() {
570        let data = CommonParser.encode_default(&20);
571        assert!(matches!(data, Ok(Value::Str(d, None)) if d == "20"));
572    }
573
574    #[test]
575    fn read_event() {
576        let data = Value::Str(r#"["event",{"data":1,"complex":[132,12]}]"#.into(), None);
577        let event = CommonParser.read_event(&data).unwrap();
578        assert_eq!(event, "event");
579    }
580
581    #[test]
582    fn unexpected_bin_packet() {
583        let err = CommonParser.decode_bin(&Default::default(), Bytes::new());
584        assert!(matches!(err, Err(ParseError::UnexpectedBinaryPacket)));
585    }
586    #[test]
587    fn check_is_bin_packet_complete() {
588        let data = PacketData::BinaryEvent(Value::Str("".into(), Some(vec![].into())), None);
589        assert!(!is_bin_packet_complete(&data, 2));
590        assert!(is_bin_packet_complete(&data, 0));
591        let data =
592            PacketData::BinaryAck(Value::Str("".into(), Some(vec![Bytes::new()].into())), 12);
593        assert!(is_bin_packet_complete(&data, 1));
594        assert!(!is_bin_packet_complete(&data, 2));
595
596        // any other packet
597        let data = PacketData::Connect(None);
598        assert!(is_bin_packet_complete(&data, 0));
599        assert!(is_bin_packet_complete(&data, 1));
600    }
601}