Skip to main content

soe_protocol/
packets.rs

1//! Wire representations of the SOE protocol packets, with (de)serialization.
2//!
3//! Two categories of packet exist:
4//!
5//! * **Session-control packets** (e.g. [`SessionRequest`], [`SessionResponse`],
6//!   [`RemapConnection`], [`UnknownSender`]) embed their own OP code. Their
7//!   `serialize` writes the OP code, and `deserialize` takes a `has_op_code` flag.
8//! * **Session-context packets** (e.g. [`Disconnect`], [`Acknowledge`],
9//!   [`AcknowledgeAll`]) are written without an OP code or CRC; those are added by
10//!   the contextual packet wrapper.
11
12use crate::constants::SOE_PROTOCOL_VERSION;
13use crate::error::Result;
14use crate::io::{BinaryReader, BinaryWriter};
15use crate::protocol::{DisconnectReason, OpCode};
16
17const OP_CODE_SIZE: usize = 2;
18
19/// A packet used to request the start of a session.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct SessionRequest {
22    /// The version of the SOE protocol in use.
23    pub soe_protocol_version: u32,
24    /// A randomly generated session identifier.
25    pub session_id: u32,
26    /// The maximum length of a UDP packet that the sender can receive.
27    pub udp_length: u32,
28    /// The application protocol that the sender wishes to transport.
29    pub application_protocol: String,
30}
31
32impl SessionRequest {
33    /// The minimum serialized size (with an empty application protocol).
34    pub const MIN_SIZE: usize = OP_CODE_SIZE + 4 + 4 + 4 + 1;
35
36    /// Returns the serialized size of this packet.
37    pub fn size(&self) -> usize {
38        OP_CODE_SIZE + 4 + 4 + 4 + self.application_protocol.len() + 1
39    }
40
41    /// Deserializes a packet from `buffer`.
42    pub fn deserialize(buffer: &[u8], has_op_code: bool) -> Result<Self> {
43        let mut r = BinaryReader::new(buffer);
44        if has_op_code {
45            r.read_u16()?;
46        }
47        Ok(Self {
48            soe_protocol_version: r.read_u32()?,
49            session_id: r.read_u32()?,
50            udp_length: r.read_u32()?,
51            application_protocol: r.read_null_terminated_string()?,
52        })
53    }
54
55    /// Serializes this packet (including its OP code) into `buffer`, returning the
56    /// number of bytes written.
57    pub fn serialize(&self, buffer: &mut [u8]) -> Result<usize> {
58        let mut w = BinaryWriter::new(buffer);
59        w.write_u16(OpCode::SessionRequest.as_u16())?;
60        w.write_u32(self.soe_protocol_version)?;
61        w.write_u32(self.session_id)?;
62        w.write_u32(self.udp_length)?;
63        w.write_null_terminated_string(&self.application_protocol)?;
64        Ok(w.offset())
65    }
66}
67
68/// A packet used to confirm a session request.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct SessionResponse {
71    /// The ID of the session being confirmed.
72    pub session_id: u32,
73    /// A randomly generated seed used to calculate CRC-32 check values.
74    pub crc_seed: u32,
75    /// The number of bytes used to store CRC-32 check values.
76    pub crc_length: u8,
77    /// Whether relevant packets may be compressed.
78    pub is_compression_enabled: bool,
79    /// Unknown. Always observed to be `0`.
80    pub unknown_value_1: u8,
81    /// The maximum length of a UDP packet that the sender can receive.
82    pub udp_length: u32,
83    /// The version of the SOE protocol in use.
84    pub soe_protocol_version: u32,
85}
86
87impl SessionResponse {
88    /// The serialized size of this packet.
89    pub const SIZE: usize = OP_CODE_SIZE + 4 + 4 + 1 + 1 + 1 + 4 + 4;
90
91    /// Deserializes a packet from `buffer`.
92    pub fn deserialize(buffer: &[u8], has_op_code: bool) -> Result<Self> {
93        let mut r = BinaryReader::new(buffer);
94        if has_op_code {
95            r.read_u16()?;
96        }
97        Ok(Self {
98            session_id: r.read_u32()?,
99            crc_seed: r.read_u32()?,
100            crc_length: r.read_u8()?,
101            is_compression_enabled: r.read_bool()?,
102            unknown_value_1: r.read_u8()?,
103            udp_length: r.read_u32()?,
104            soe_protocol_version: r.read_u32()?,
105        })
106    }
107
108    /// Serializes this packet (including its OP code) into `buffer`, returning the
109    /// number of bytes written.
110    pub fn serialize(&self, buffer: &mut [u8]) -> Result<usize> {
111        let mut w = BinaryWriter::new(buffer);
112        w.write_u16(OpCode::SessionResponse.as_u16())?;
113        w.write_u32(self.session_id)?;
114        w.write_u32(self.crc_seed)?;
115        w.write_u8(self.crc_length)?;
116        w.write_bool(self.is_compression_enabled)?;
117        w.write_u8(self.unknown_value_1)?;
118        w.write_u32(self.udp_length)?;
119        w.write_u32(self.soe_protocol_version)?;
120        Ok(w.offset())
121    }
122}
123
124/// A packet used to terminate a session. Serialized without OP code or CRC.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub struct Disconnect {
127    /// The ID of the session being terminated.
128    pub session_id: u32,
129    /// The reason for the termination.
130    pub reason: DisconnectReason,
131}
132
133impl Disconnect {
134    /// The serialized size of this packet (excluding OP code and CRC).
135    pub const SIZE: usize = 4 + 2;
136
137    /// Creates a new disconnect packet.
138    pub fn new(session_id: u32, reason: DisconnectReason) -> Self {
139        Self { session_id, reason }
140    }
141
142    /// Deserializes a packet from `buffer`.
143    pub fn deserialize(buffer: &[u8]) -> Result<Self> {
144        let mut r = BinaryReader::new(buffer);
145        let session_id = r.read_u32()?;
146        let reason = DisconnectReason::from_u16(r.read_u16()?);
147        Ok(Self { session_id, reason })
148    }
149
150    /// Serializes this packet into `buffer`, returning the number of bytes written.
151    pub fn serialize(&self, buffer: &mut [u8]) -> Result<usize> {
152        let mut w = BinaryWriter::new(buffer);
153        w.write_u32(self.session_id)?;
154        w.write_u16(self.reason.as_u16())?;
155        Ok(w.offset())
156    }
157}
158
159/// A packet used to remap an existing session to a new port.
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub struct RemapConnection {
162    /// The ID of the session to remap.
163    pub session_id: u32,
164    /// The CRC seed being used in the session.
165    pub crc_seed: u32,
166}
167
168impl RemapConnection {
169    /// The serialized size of this packet (including OP code).
170    pub const SIZE: usize = OP_CODE_SIZE + 4 + 4;
171
172    /// Deserializes a packet from `buffer`.
173    pub fn deserialize(buffer: &[u8], has_op_code: bool) -> Result<Self> {
174        let mut r = BinaryReader::new(buffer);
175        if has_op_code {
176            r.read_u16()?;
177        }
178        Ok(Self {
179            session_id: r.read_u32()?,
180            crc_seed: r.read_u32()?,
181        })
182    }
183
184    /// Serializes this packet (including its OP code) into `buffer`, returning the
185    /// number of bytes written.
186    pub fn serialize(&self, buffer: &mut [u8]) -> Result<usize> {
187        let mut w = BinaryWriter::new(buffer);
188        w.write_u16(OpCode::RemapConnection.as_u16())?;
189        w.write_u32(self.session_id)?;
190        w.write_u32(self.crc_seed)?;
191        Ok(w.offset())
192    }
193}
194
195/// A packet used to acknowledge a single data sequence. Serialized without OP code.
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct Acknowledge {
198    /// The sequence number being acknowledged.
199    pub sequence: u16,
200}
201
202impl Acknowledge {
203    /// The serialized size of this packet (excluding OP code and CRC).
204    pub const SIZE: usize = 2;
205
206    /// Creates a new acknowledge packet.
207    pub fn new(sequence: u16) -> Self {
208        Self { sequence }
209    }
210
211    /// Deserializes a packet from `buffer`.
212    pub fn deserialize(buffer: &[u8]) -> Result<Self> {
213        let mut r = BinaryReader::new(buffer);
214        Ok(Self {
215            sequence: r.read_u16()?,
216        })
217    }
218
219    /// Serializes this packet into `buffer`, returning the number of bytes written.
220    pub fn serialize(&self, buffer: &mut [u8]) -> Result<usize> {
221        let mut w = BinaryWriter::new(buffer);
222        w.write_u16(self.sequence)?;
223        Ok(w.offset())
224    }
225}
226
227/// A packet used to acknowledge all sequences up to and including `sequence`.
228/// Serialized without OP code.
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub struct AcknowledgeAll {
231    /// The most recent sequence number received.
232    pub sequence: u16,
233}
234
235impl AcknowledgeAll {
236    /// The serialized size of this packet (excluding OP code and CRC).
237    pub const SIZE: usize = 2;
238
239    /// Creates a new acknowledge-all packet.
240    pub fn new(sequence: u16) -> Self {
241        Self { sequence }
242    }
243
244    /// Deserializes a packet from `buffer`.
245    pub fn deserialize(buffer: &[u8]) -> Result<Self> {
246        let mut r = BinaryReader::new(buffer);
247        Ok(Self {
248            sequence: r.read_u16()?,
249        })
250    }
251
252    /// Serializes this packet into `buffer`, returning the number of bytes written.
253    pub fn serialize(&self, buffer: &mut [u8]) -> Result<usize> {
254        let mut w = BinaryWriter::new(buffer);
255        w.write_u16(self.sequence)?;
256        Ok(w.offset())
257    }
258}
259
260/// A packet indicating the receiver has no session for the sender's address.
261/// Carries no fields beyond its OP code.
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub struct UnknownSender;
264
265impl UnknownSender {
266    /// The serialized size of this packet (the OP code only).
267    pub const SIZE: usize = OP_CODE_SIZE;
268
269    /// Serializes this packet (its OP code) into `buffer`, returning the number of
270    /// bytes written.
271    pub fn serialize(buffer: &mut [u8]) -> Result<usize> {
272        let mut w = BinaryWriter::new(buffer);
273        w.write_u16(OpCode::UnknownSender.as_u16())?;
274        Ok(w.offset())
275    }
276}
277
278/// Constructs a [`SessionRequest`] using this crate's protocol version.
279pub fn session_request(
280    session_id: u32,
281    udp_length: u32,
282    application_protocol: &str,
283) -> SessionRequest {
284    SessionRequest {
285        soe_protocol_version: SOE_PROTOCOL_VERSION,
286        session_id,
287        udp_length,
288        application_protocol: application_protocol.to_owned(),
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295
296    // Ported from SessionRequestTests.cs
297    #[test]
298    fn session_request_round_trip() {
299        let pkt = SessionRequest {
300            soe_protocol_version: 3,
301            session_id: 5_467_392,
302            udp_length: 512,
303            application_protocol: "TestProtocol".to_owned(),
304        };
305        let mut buf = vec![0u8; pkt.size()];
306        let n = pkt.serialize(&mut buf).unwrap();
307        assert_eq!(n, pkt.size());
308        assert_eq!(SessionRequest::deserialize(&buf, true).unwrap(), pkt);
309    }
310
311    // Ported from SessionResponseTests.cs
312    #[test]
313    fn session_response_round_trip() {
314        let pkt = SessionResponse {
315            session_id: 531_633,
316            crc_seed: 34_322,
317            crc_length: 2,
318            is_compression_enabled: true,
319            unknown_value_1: 0,
320            udp_length: 512,
321            soe_protocol_version: 3,
322        };
323        let mut buf = [0u8; SessionResponse::SIZE];
324        let n = pkt.serialize(&mut buf).unwrap();
325        assert_eq!(n, SessionResponse::SIZE);
326        assert_eq!(SessionResponse::deserialize(&buf, true).unwrap(), pkt);
327    }
328
329    // Ported from DisconnectTests.cs
330    #[test]
331    fn disconnect_round_trip() {
332        let pkt = Disconnect::new(5, DisconnectReason::Application);
333        let mut buf = [0u8; Disconnect::SIZE];
334        pkt.serialize(&mut buf).unwrap();
335        assert_eq!(Disconnect::deserialize(&buf).unwrap(), pkt);
336    }
337
338    // Ported from RemapConnectionTests.cs
339    #[test]
340    fn remap_connection_round_trip() {
341        let pkt = RemapConnection {
342            session_id: 16,
343            crc_seed: 32,
344        };
345        let mut buf = [0u8; RemapConnection::SIZE];
346        pkt.serialize(&mut buf).unwrap();
347        assert_eq!(RemapConnection::deserialize(&buf, true).unwrap(), pkt);
348    }
349
350    // Ported from AcknowledgeTests.cs / AcknowledgeAllTests.cs
351    #[test]
352    fn acknowledge_round_trip() {
353        let pkt = Acknowledge::new(2);
354        let mut buf = [0u8; Acknowledge::SIZE];
355        pkt.serialize(&mut buf).unwrap();
356        assert_eq!(Acknowledge::deserialize(&buf).unwrap(), pkt);
357
358        let all = AcknowledgeAll::new(2);
359        let mut buf = [0u8; AcknowledgeAll::SIZE];
360        all.serialize(&mut buf).unwrap();
361        assert_eq!(AcknowledgeAll::deserialize(&buf).unwrap(), all);
362    }
363
364    #[test]
365    fn unknown_sender_serializes_opcode() {
366        let mut buf = [0u8; UnknownSender::SIZE];
367        UnknownSender::serialize(&mut buf).unwrap();
368        assert_eq!(buf, [0x00, 0x1D]);
369    }
370}