soe_protocol/protocol.rs
1//! Core protocol enumerations: OP codes and disconnect reasons.
2
3/// The packet OP codes used in the SOE protocol. All packets are prefixed with a
4/// big-endian `u16` OP code.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6#[repr(u16)]
7pub enum OpCode {
8 /// Used to request the start of a session.
9 SessionRequest = 0x01,
10 /// Used to confirm the start of a session, and set connection details.
11 SessionResponse = 0x02,
12 /// Used to encapsulate two or more SOE protocol packets.
13 MultiPacket = 0x03,
14 /// Used to indicate that a party is closing the session.
15 Disconnect = 0x05,
16 /// Used to keep a session alive when no data has been received for some time.
17 Heartbeat = 0x06,
18 /// Network status request. Exact usage is not fully understood.
19 NetStatusRequest = 0x07,
20 /// Network status response. Exact usage is not fully understood.
21 NetStatusResponse = 0x08,
22 /// Used to transfer small buffers of application data.
23 ReliableData = 0x09,
24 /// Used to transfer large buffers of application data in multiple fragments.
25 ReliableDataFragment = 0x0D,
26 /// Used to acknowledge a single reliable data packet.
27 Acknowledge = 0x11,
28 /// Used to acknowledge all reliable data packets up to a particular sequence.
29 AcknowledgeAll = 0x15,
30 /// Indicates the receiver has no session associated with the sender's address.
31 UnknownSender = 0x1D,
32 /// Used to request that a session be remapped to another port.
33 RemapConnection = 0x1E,
34}
35
36impl OpCode {
37 /// Attempts to convert a raw `u16` into an [`OpCode`].
38 pub fn from_u16(value: u16) -> Option<Self> {
39 Some(match value {
40 0x01 => Self::SessionRequest,
41 0x02 => Self::SessionResponse,
42 0x03 => Self::MultiPacket,
43 0x05 => Self::Disconnect,
44 0x06 => Self::Heartbeat,
45 0x07 => Self::NetStatusRequest,
46 0x08 => Self::NetStatusResponse,
47 0x09 => Self::ReliableData,
48 0x0D => Self::ReliableDataFragment,
49 0x11 => Self::Acknowledge,
50 0x15 => Self::AcknowledgeAll,
51 0x1D => Self::UnknownSender,
52 0x1E => Self::RemapConnection,
53 _ => return None,
54 })
55 }
56
57 /// Returns the raw `u16` value of this OP code.
58 pub fn as_u16(self) -> u16 {
59 self as u16
60 }
61
62 /// Returns `true` if this packet type is used outside of an established
63 /// session context (and hence is not CRC-checked or compressed).
64 pub fn is_contextless(self) -> bool {
65 matches!(
66 self,
67 Self::SessionRequest
68 | Self::SessionResponse
69 | Self::NetStatusRequest
70 | Self::NetStatusResponse
71 | Self::UnknownSender
72 | Self::RemapConnection
73 )
74 }
75}
76
77/// The possible session termination reasons.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79#[repr(u16)]
80pub enum DisconnectReason {
81 /// No reason can be given for the disconnect.
82 None = 0,
83 /// An ICMP error occurred, forcing the disconnect.
84 IcmpError = 1,
85 /// The other party has let the session become inactive.
86 Timeout = 2,
87 /// Internal: the other party has sent a disconnect.
88 OtherSideTerminated = 3,
89 /// The session manager has been disposed of (e.g. shutting down).
90 ManagerDeleted = 4,
91 /// Internal: a session request attempt has failed.
92 ConnectFail = 5,
93 /// The application is terminating the session.
94 Application = 6,
95 /// Internal: the other party is unreachable.
96 UnreachableConnection = 7,
97 /// A data sequence was not acknowledged quickly enough.
98 UnacknowledgedTimeout = 8,
99 /// A session request failed; a new attempt should be made after a short delay.
100 NewConnectionAttempt = 9,
101 /// The application did not accept a session request.
102 ConnectionRefused = 10,
103 /// The proper session negotiation flow has not been observed.
104 ConnectError = 11,
105 /// A session request was probably looped back to the sender.
106 ConnectingToSelf = 12,
107 /// Reliable data is being sent too fast to be processed.
108 ReliableOverflow = 13,
109 /// The session manager has been orphaned by the application.
110 ApplicationReleased = 14,
111 /// A corrupt packet was received.
112 CorruptPacket = 15,
113 /// The requested SOE protocol version or application protocol is invalid.
114 ProtocolMismatch = 16,
115}
116
117impl DisconnectReason {
118 /// Converts a raw `u16` into a [`DisconnectReason`], mapping unrecognized
119 /// values to [`DisconnectReason::None`].
120 pub fn from_u16(value: u16) -> Self {
121 match value {
122 1 => Self::IcmpError,
123 2 => Self::Timeout,
124 3 => Self::OtherSideTerminated,
125 4 => Self::ManagerDeleted,
126 5 => Self::ConnectFail,
127 6 => Self::Application,
128 7 => Self::UnreachableConnection,
129 8 => Self::UnacknowledgedTimeout,
130 9 => Self::NewConnectionAttempt,
131 10 => Self::ConnectionRefused,
132 11 => Self::ConnectError,
133 12 => Self::ConnectingToSelf,
134 13 => Self::ReliableOverflow,
135 14 => Self::ApplicationReleased,
136 15 => Self::CorruptPacket,
137 16 => Self::ProtocolMismatch,
138 _ => Self::None,
139 }
140 }
141
142 /// Returns the raw `u16` value of this reason.
143 pub fn as_u16(self) -> u16 {
144 self as u16
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn opcode_round_trip() {
154 for raw in [
155 0x01u16, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0D, 0x11, 0x15, 0x1D, 0x1E,
156 ] {
157 let op = OpCode::from_u16(raw).unwrap();
158 assert_eq!(op.as_u16(), raw);
159 }
160 assert!(OpCode::from_u16(0x00).is_none());
161 assert!(OpCode::from_u16(0xFFFF).is_none());
162 }
163
164 #[test]
165 fn contextless_classification() {
166 assert!(OpCode::SessionRequest.is_contextless());
167 assert!(OpCode::RemapConnection.is_contextless());
168 assert!(!OpCode::ReliableData.is_contextless());
169 assert!(!OpCode::Disconnect.is_contextless());
170 }
171
172 #[test]
173 fn disconnect_reason_round_trip() {
174 for raw in 0u16..=16 {
175 assert_eq!(DisconnectReason::from_u16(raw).as_u16(), raw);
176 }
177 assert_eq!(DisconnectReason::from_u16(999), DisconnectReason::None);
178 }
179}