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}