socketioxide_core/
packet.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! Socket.io packet implementation.
//! The [`Packet`] is the base unit of data that is sent over the engine.io socket.

use serde::{Deserialize, Serialize};

pub use engineioxide::{sid::Sid, Str};

use crate::Value;

/// The socket.io packet type.
/// Each packet has a type and a namespace
#[derive(Debug, Clone, PartialEq)]
pub struct Packet {
    /// The packet data
    pub inner: PacketData,
    /// The namespace the packet belongs to
    pub ns: Str,
}

impl Packet {
    /// Send a connect packet with a default payload for v5 and no payload for v4
    pub fn connect(ns: impl Into<Str>, value: Option<Value>) -> Self {
        Self {
            inner: PacketData::Connect(value),
            ns: ns.into(),
        }
    }

    /// Create a disconnect packet for the given namespace
    pub fn disconnect(ns: impl Into<Str>) -> Self {
        Self {
            inner: PacketData::Disconnect,
            ns: ns.into(),
        }
    }
}

impl Packet {
    /// Create a connect error packet for the given namespace with a message
    pub fn connect_error(ns: impl Into<Str>, message: impl Into<String>) -> Self {
        Self {
            inner: PacketData::ConnectError(message.into()),
            ns: ns.into(),
        }
    }

    /// Create an event packet for the given namespace.
    /// If the there is adjacent binary data, it will be a binary packet.
    pub fn event(ns: impl Into<Str>, data: Value) -> Self {
        Self {
            inner: match data {
                Value::Str(_, Some(ref bins)) if !bins.is_empty() => {
                    PacketData::BinaryEvent(data, None)
                }
                _ => PacketData::Event(data, None),
            },
            ns: ns.into(),
        }
    }

    /// Create an ack packet for the given namespace.
    /// If the there is adjacent binary data, it will be a binary packet.
    pub fn ack(ns: impl Into<Str>, data: Value, ack: i64) -> Self {
        Self {
            inner: match data {
                Value::Str(_, Some(ref bins)) if !bins.is_empty() => {
                    PacketData::BinaryAck(data, ack)
                }
                _ => PacketData::EventAck(data, ack),
            },
            ns: ns.into(),
        }
    }
}

/// | Type          | ID  | Usage                                                                                 |
/// |---------------|-----|---------------------------------------------------------------------------------------|
/// | CONNECT       | 0   | Used during the [connection to a namespace](#connection-to-a-namespace).              |
/// | DISCONNECT    | 1   | Used when [disconnecting from a namespace](#disconnection-from-a-namespace).          |
/// | EVENT         | 2   | Used to [send data](#sending-and-receiving-data) to the other side.                   |
/// | ACK           | 3   | Used to [acknowledge](#acknowledgement) an event.                                     |
/// | CONNECT_ERROR | 4   | Used during the [connection to a namespace](#connection-to-a-namespace).              |
/// | BINARY_EVENT  | 5   | Used to [send binary data](#sending-and-receiving-data) to the other side.            |
/// | BINARY_ACK    | 6   | Used to [acknowledge](#acknowledgement) an event (the response includes binary data). |
#[derive(Debug, Clone, PartialEq)]
pub enum PacketData {
    /// Connect packet with optional payload (only used with v5 for response)
    Connect(Option<Value>),
    /// Disconnect packet, used to disconnect from a namespace
    Disconnect,
    /// Event packet with optional ack id, to request an ack from the other side
    Event(Value, Option<i64>),
    /// Event ack packet, to acknowledge an event
    EventAck(Value, i64),
    /// Connect error packet, sent when the namespace is invalid
    ConnectError(String),
    /// Binary event packet with optional ack id, to request an ack from the other side
    BinaryEvent(Value, Option<i64>),
    /// Binary ack packet, to acknowledge an event with binary data
    BinaryAck(Value, i64),
}

impl PacketData {
    /// Returns the index of the packet type
    pub fn index(&self) -> usize {
        match self {
            PacketData::Connect(_) => 0,
            PacketData::Disconnect => 1,
            PacketData::Event(_, _) => 2,
            PacketData::EventAck(_, _) => 3,
            PacketData::ConnectError(_) => 4,
            PacketData::BinaryEvent(_, _) => 5,
            PacketData::BinaryAck(_, _) => 6,
        }
    }

    /// Set the ack id for the packet
    /// It will only set the ack id for the packets that support it
    pub fn set_ack_id(&mut self, ack_id: i64) {
        match self {
            PacketData::Event(_, ack) | PacketData::BinaryEvent(_, ack) => *ack = Some(ack_id),
            _ => {}
        };
    }

    /// Check if the packet is a binary packet (either binary event or binary ack)
    pub fn is_binary(&self) -> bool {
        matches!(
            self,
            PacketData::BinaryEvent(_, _) | PacketData::BinaryAck(_, _)
        )
    }
}

/// Connect packet sent by the client
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectPacket {
    /// The socket ID
    pub sid: Sid,
}

#[cfg(test)]
mod tests {

    use super::{Packet, PacketData, Value};
    use bytes::Bytes;

    #[test]
    fn should_create_bin_packet_with_adjacent_binary() {
        let val = Value::Str(
            "test".into(),
            Some(vec![Bytes::from_static(&[1, 2, 3])].into()),
        );
        assert!(matches!(
            Packet::event("/", val.clone()).inner,
            PacketData::BinaryEvent(v, None) if v == val));

        assert!(matches!(
            Packet::ack("/", val.clone(), 120).inner,
            PacketData::BinaryAck(v, 120) if v == val));
    }

    #[test]
    fn should_create_default_packet_with_base_data() {
        let val = Value::Str("test".into(), None);
        let val1 = Value::Bytes(Bytes::from_static(b"test"));

        assert!(matches!(
            Packet::event("/", val.clone()).inner,
            PacketData::Event(v, None) if v == val));

        assert!(matches!(
            Packet::ack("/", val.clone(), 120).inner,
            PacketData::EventAck(v, 120) if v == val));

        assert!(matches!(
            Packet::event("/", val1.clone()).inner,
            PacketData::Event(v, None) if v == val1));

        assert!(matches!(
            Packet::ack("/", val1.clone(), 120).inner,
            PacketData::EventAck(v, 120) if v == val1));
    }
}