socketioxide_core/
packet.rs

1//! Socket.io packet implementation.
2//! The [`Packet`] is the base unit of data that is sent over the engine.io socket.
3
4use serde::{Deserialize, Serialize};
5
6pub use engineioxide::{sid::Sid, Str};
7
8use crate::Value;
9
10/// The socket.io packet type.
11/// Each packet has a type and a namespace
12#[derive(Debug, Clone, PartialEq)]
13pub struct Packet {
14    /// The packet data
15    pub inner: PacketData,
16    /// The namespace the packet belongs to
17    pub ns: Str,
18}
19
20impl Packet {
21    /// Send a connect packet with a default payload for v5 and no payload for v4
22    pub fn connect(ns: impl Into<Str>, value: Option<Value>) -> Self {
23        Self {
24            inner: PacketData::Connect(value),
25            ns: ns.into(),
26        }
27    }
28
29    /// Create a disconnect packet for the given namespace
30    pub fn disconnect(ns: impl Into<Str>) -> Self {
31        Self {
32            inner: PacketData::Disconnect,
33            ns: ns.into(),
34        }
35    }
36}
37
38impl Packet {
39    /// Create a connect error packet for the given namespace with a message
40    pub fn connect_error(ns: impl Into<Str>, message: impl Into<String>) -> Self {
41        Self {
42            inner: PacketData::ConnectError(message.into()),
43            ns: ns.into(),
44        }
45    }
46
47    /// Create an event packet for the given namespace.
48    /// If the there is adjacent binary data, it will be a binary packet.
49    pub fn event(ns: impl Into<Str>, data: Value) -> Self {
50        Self {
51            inner: match data {
52                Value::Str(_, Some(ref bins)) if !bins.is_empty() => {
53                    PacketData::BinaryEvent(data, None)
54                }
55                _ => PacketData::Event(data, None),
56            },
57            ns: ns.into(),
58        }
59    }
60
61    /// Create an ack packet for the given namespace.
62    /// If the there is adjacent binary data, it will be a binary packet.
63    pub fn ack(ns: impl Into<Str>, data: Value, ack: i64) -> Self {
64        Self {
65            inner: match data {
66                Value::Str(_, Some(ref bins)) if !bins.is_empty() => {
67                    PacketData::BinaryAck(data, ack)
68                }
69                _ => PacketData::EventAck(data, ack),
70            },
71            ns: ns.into(),
72        }
73    }
74}
75
76/// | Type          | ID  | Usage                                                                                 |
77/// |---------------|-----|---------------------------------------------------------------------------------------|
78/// | CONNECT       | 0   | Used during the [connection to a namespace](#connection-to-a-namespace).              |
79/// | DISCONNECT    | 1   | Used when [disconnecting from a namespace](#disconnection-from-a-namespace).          |
80/// | EVENT         | 2   | Used to [send data](#sending-and-receiving-data) to the other side.                   |
81/// | ACK           | 3   | Used to [acknowledge](#acknowledgement) an event.                                     |
82/// | CONNECT_ERROR | 4   | Used during the [connection to a namespace](#connection-to-a-namespace).              |
83/// | BINARY_EVENT  | 5   | Used to [send binary data](#sending-and-receiving-data) to the other side.            |
84/// | BINARY_ACK    | 6   | Used to [acknowledge](#acknowledgement) an event (the response includes binary data). |
85#[derive(Debug, Clone, PartialEq)]
86pub enum PacketData {
87    /// Connect packet with optional payload (only used with v5 for response)
88    Connect(Option<Value>),
89    /// Disconnect packet, used to disconnect from a namespace
90    Disconnect,
91    /// Event packet with optional ack id, to request an ack from the other side
92    Event(Value, Option<i64>),
93    /// Event ack packet, to acknowledge an event
94    EventAck(Value, i64),
95    /// Connect error packet, sent when the namespace is invalid
96    ConnectError(String),
97    /// Binary event packet with optional ack id, to request an ack from the other side
98    BinaryEvent(Value, Option<i64>),
99    /// Binary ack packet, to acknowledge an event with binary data
100    BinaryAck(Value, i64),
101}
102
103impl PacketData {
104    /// Returns the index of the packet type
105    pub fn index(&self) -> usize {
106        match self {
107            PacketData::Connect(_) => 0,
108            PacketData::Disconnect => 1,
109            PacketData::Event(_, _) => 2,
110            PacketData::EventAck(_, _) => 3,
111            PacketData::ConnectError(_) => 4,
112            PacketData::BinaryEvent(_, _) => 5,
113            PacketData::BinaryAck(_, _) => 6,
114        }
115    }
116
117    /// Set the ack id for the packet
118    /// It will only set the ack id for the packets that support it
119    pub fn set_ack_id(&mut self, ack_id: i64) {
120        match self {
121            PacketData::Event(_, ack) | PacketData::BinaryEvent(_, ack) => *ack = Some(ack_id),
122            _ => {}
123        };
124    }
125
126    /// Check if the packet is a binary packet (either binary event or binary ack)
127    pub fn is_binary(&self) -> bool {
128        matches!(
129            self,
130            PacketData::BinaryEvent(_, _) | PacketData::BinaryAck(_, _)
131        )
132    }
133}
134
135/// Connect packet sent by the client
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct ConnectPacket {
138    /// The socket ID
139    pub sid: Sid,
140}
141
142#[cfg(test)]
143mod tests {
144
145    use super::{Packet, PacketData, Value};
146    use bytes::Bytes;
147
148    #[test]
149    fn should_create_bin_packet_with_adjacent_binary() {
150        let val = Value::Str(
151            "test".into(),
152            Some(vec![Bytes::from_static(&[1, 2, 3])].into()),
153        );
154        assert!(matches!(
155            Packet::event("/", val.clone()).inner,
156            PacketData::BinaryEvent(v, None) if v == val));
157
158        assert!(matches!(
159            Packet::ack("/", val.clone(), 120).inner,
160            PacketData::BinaryAck(v, 120) if v == val));
161    }
162
163    #[test]
164    fn should_create_default_packet_with_base_data() {
165        let val = Value::Str("test".into(), None);
166        let val1 = Value::Bytes(Bytes::from_static(b"test"));
167
168        assert!(matches!(
169            Packet::event("/", val.clone()).inner,
170            PacketData::Event(v, None) if v == val));
171
172        assert!(matches!(
173            Packet::ack("/", val.clone(), 120).inner,
174            PacketData::EventAck(v, 120) if v == val));
175
176        assert!(matches!(
177            Packet::event("/", val1.clone()).inner,
178            PacketData::Event(v, None) if v == val1));
179
180        assert!(matches!(
181            Packet::ack("/", val1.clone(), 120).inner,
182            PacketData::EventAck(v, 120) if v == val1));
183    }
184}