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
//! Core protocol enumerations: OP codes and disconnect reasons.
/// The packet OP codes used in the SOE protocol. All packets are prefixed with a
/// big-endian `u16` OP code.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u16)]
pub enum OpCode {
/// Used to request the start of a session.
SessionRequest = 0x01,
/// Used to confirm the start of a session, and set connection details.
SessionResponse = 0x02,
/// Used to encapsulate two or more SOE protocol packets.
MultiPacket = 0x03,
/// Used to indicate that a party is closing the session.
Disconnect = 0x05,
/// Used to keep a session alive when no data has been received for some time.
Heartbeat = 0x06,
/// Network status request. Exact usage is not fully understood.
NetStatusRequest = 0x07,
/// Network status response. Exact usage is not fully understood.
NetStatusResponse = 0x08,
/// Used to transfer small buffers of application data.
ReliableData = 0x09,
/// Used to transfer large buffers of application data in multiple fragments.
ReliableDataFragment = 0x0D,
/// Used to acknowledge a single reliable data packet.
Acknowledge = 0x11,
/// Used to acknowledge all reliable data packets up to a particular sequence.
AcknowledgeAll = 0x15,
/// Indicates the receiver has no session associated with the sender's address.
UnknownSender = 0x1D,
/// Used to request that a session be remapped to another port.
RemapConnection = 0x1E,
}
impl OpCode {
/// Attempts to convert a raw `u16` into an [`OpCode`].
pub fn from_u16(value: u16) -> Option<Self> {
Some(match value {
0x01 => Self::SessionRequest,
0x02 => Self::SessionResponse,
0x03 => Self::MultiPacket,
0x05 => Self::Disconnect,
0x06 => Self::Heartbeat,
0x07 => Self::NetStatusRequest,
0x08 => Self::NetStatusResponse,
0x09 => Self::ReliableData,
0x0D => Self::ReliableDataFragment,
0x11 => Self::Acknowledge,
0x15 => Self::AcknowledgeAll,
0x1D => Self::UnknownSender,
0x1E => Self::RemapConnection,
_ => return None,
})
}
/// Returns the raw `u16` value of this OP code.
pub fn as_u16(self) -> u16 {
self as u16
}
/// Returns `true` if this packet type is used outside of an established
/// session context (and hence is not CRC-checked or compressed).
pub fn is_contextless(self) -> bool {
matches!(
self,
Self::SessionRequest
| Self::SessionResponse
| Self::NetStatusRequest
| Self::NetStatusResponse
| Self::UnknownSender
| Self::RemapConnection
)
}
}
/// The possible session termination reasons.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u16)]
pub enum DisconnectReason {
/// No reason can be given for the disconnect.
None = 0,
/// An ICMP error occurred, forcing the disconnect.
IcmpError = 1,
/// The other party has let the session become inactive.
Timeout = 2,
/// Internal: the other party has sent a disconnect.
OtherSideTerminated = 3,
/// The session manager has been disposed of (e.g. shutting down).
ManagerDeleted = 4,
/// Internal: a session request attempt has failed.
ConnectFail = 5,
/// The application is terminating the session.
Application = 6,
/// Internal: the other party is unreachable.
UnreachableConnection = 7,
/// A data sequence was not acknowledged quickly enough.
UnacknowledgedTimeout = 8,
/// A session request failed; a new attempt should be made after a short delay.
NewConnectionAttempt = 9,
/// The application did not accept a session request.
ConnectionRefused = 10,
/// The proper session negotiation flow has not been observed.
ConnectError = 11,
/// A session request was probably looped back to the sender.
ConnectingToSelf = 12,
/// Reliable data is being sent too fast to be processed.
ReliableOverflow = 13,
/// The session manager has been orphaned by the application.
ApplicationReleased = 14,
/// A corrupt packet was received.
CorruptPacket = 15,
/// The requested SOE protocol version or application protocol is invalid.
ProtocolMismatch = 16,
}
impl DisconnectReason {
/// Converts a raw `u16` into a [`DisconnectReason`], mapping unrecognized
/// values to [`DisconnectReason::None`].
pub fn from_u16(value: u16) -> Self {
match value {
1 => Self::IcmpError,
2 => Self::Timeout,
3 => Self::OtherSideTerminated,
4 => Self::ManagerDeleted,
5 => Self::ConnectFail,
6 => Self::Application,
7 => Self::UnreachableConnection,
8 => Self::UnacknowledgedTimeout,
9 => Self::NewConnectionAttempt,
10 => Self::ConnectionRefused,
11 => Self::ConnectError,
12 => Self::ConnectingToSelf,
13 => Self::ReliableOverflow,
14 => Self::ApplicationReleased,
15 => Self::CorruptPacket,
16 => Self::ProtocolMismatch,
_ => Self::None,
}
}
/// Returns the raw `u16` value of this reason.
pub fn as_u16(self) -> u16 {
self as u16
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn opcode_round_trip() {
for raw in [
0x01u16, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0D, 0x11, 0x15, 0x1D, 0x1E,
] {
let op = OpCode::from_u16(raw).unwrap();
assert_eq!(op.as_u16(), raw);
}
assert!(OpCode::from_u16(0x00).is_none());
assert!(OpCode::from_u16(0xFFFF).is_none());
}
#[test]
fn contextless_classification() {
assert!(OpCode::SessionRequest.is_contextless());
assert!(OpCode::RemapConnection.is_contextless());
assert!(!OpCode::ReliableData.is_contextless());
assert!(!OpCode::Disconnect.is_contextless());
}
#[test]
fn disconnect_reason_round_trip() {
for raw in 0u16..=16 {
assert_eq!(DisconnectReason::from_u16(raw).as_u16(), raw);
}
assert_eq!(DisconnectReason::from_u16(999), DisconnectReason::None);
}
}