1use bytes::{Buf, BufMut};
2
3use crate::error::{DecodeError, EncodeError};
4use crate::protocol::codec::RaknetCodec;
5use crate::protocol::constants::{
6 DEFAULT_UNCONNECTED_MAGIC, ID_ALREADY_CONNECTED, ID_CONNECTION_BANNED,
7 ID_CONNECTION_REQUEST_FAILED, ID_INCOMPATIBLE_PROTOCOL_VERSION, ID_IP_RECENTLY_CONNECTED,
8 ID_NO_FREE_INCOMING_CONNECTIONS, ID_OPEN_CONNECTION_REPLY_1, ID_OPEN_CONNECTION_REPLY_2,
9 ID_OPEN_CONNECTION_REQUEST_1, ID_OPEN_CONNECTION_REQUEST_2, ID_UNCONNECTED_PING,
10 ID_UNCONNECTED_PING_OPEN_CONNECTIONS, ID_UNCONNECTED_PONG, MAXIMUM_MTU_SIZE, MINIMUM_MTU_SIZE,
11 Magic,
12};
13
14mod incompatible;
15mod offline;
16mod open_connection;
17mod reject;
18
19pub use incompatible::IncompatibleProtocolVersion;
20pub use offline::{UnconnectedPing, UnconnectedPong};
21pub use open_connection::{
22 OpenConnectionReply1, OpenConnectionReply2, OpenConnectionRequest1, OpenConnectionRequest2,
23 Request2ParsePath,
24};
25pub use reject::{
26 AlreadyConnected, ConnectionBanned, ConnectionRequestFailed, IpRecentlyConnected,
27 NoFreeIncomingConnections,
28};
29
30use incompatible::decode_incompatible;
31use offline::{decode_ping, decode_pong};
32use open_connection::{decode_reply_1, decode_reply_2, decode_request_1, decode_request_2};
33use reject::decode_reject_packet;
34
35pub const MAX_UNCONNECTED_PONG_MOTD_BYTES: usize = i16::MAX as usize;
36
37#[derive(Debug, Clone)]
38pub enum OfflinePacket {
39 UnconnectedPing(UnconnectedPing),
40 UnconnectedPingOpenConnections(UnconnectedPing),
41 UnconnectedPong(UnconnectedPong),
42 OpenConnectionRequest1(OpenConnectionRequest1),
43 OpenConnectionReply1(OpenConnectionReply1),
44 OpenConnectionRequest2(OpenConnectionRequest2),
45 OpenConnectionReply2(OpenConnectionReply2),
46 IncompatibleProtocolVersion(IncompatibleProtocolVersion),
47 ConnectionRequestFailed(ConnectionRequestFailed),
48 AlreadyConnected(AlreadyConnected),
49 NoFreeIncomingConnections(NoFreeIncomingConnections),
50 ConnectionBanned(ConnectionBanned),
51 IpRecentlyConnected(IpRecentlyConnected),
52}
53
54macro_rules! offline_packet_registry {
55 ($($id:ident => $variant:ident => $decoder:expr),+ $(,)?) => {
56 fn offline_packet_id(packet: &OfflinePacket) -> u8 {
57 match packet {
58 $(OfflinePacket::$variant(_) => $id,)+
59 }
60 }
61
62 fn decode_offline_packet_with_registry(
63 id: u8,
64 src: &mut impl Buf,
65 expected_magic: Magic,
66 ) -> Result<OfflinePacket, DecodeError> {
67 match id {
68 $($id => ($decoder)(src, expected_magic),)+
69 _ => Err(DecodeError::InvalidOfflinePacketId(id)),
70 }
71 }
72 };
73}
74
75offline_packet_registry! {
76 ID_UNCONNECTED_PING => UnconnectedPing =>
77 |src, expected_magic| decode_ping(src, expected_magic).map(OfflinePacket::UnconnectedPing),
78 ID_UNCONNECTED_PING_OPEN_CONNECTIONS => UnconnectedPingOpenConnections =>
79 |src, expected_magic| decode_ping(src, expected_magic).map(OfflinePacket::UnconnectedPingOpenConnections),
80 ID_UNCONNECTED_PONG => UnconnectedPong =>
81 |src, expected_magic| decode_pong(src, expected_magic).map(OfflinePacket::UnconnectedPong),
82 ID_OPEN_CONNECTION_REQUEST_1 => OpenConnectionRequest1 =>
83 |src, expected_magic| decode_request_1(src, expected_magic).map(OfflinePacket::OpenConnectionRequest1),
84 ID_OPEN_CONNECTION_REPLY_1 => OpenConnectionReply1 =>
85 |src, expected_magic| decode_reply_1(src, expected_magic).map(OfflinePacket::OpenConnectionReply1),
86 ID_OPEN_CONNECTION_REQUEST_2 => OpenConnectionRequest2 =>
87 |src, expected_magic| decode_request_2(src, expected_magic).map(OfflinePacket::OpenConnectionRequest2),
88 ID_OPEN_CONNECTION_REPLY_2 => OpenConnectionReply2 =>
89 |src, expected_magic| decode_reply_2(src, expected_magic).map(OfflinePacket::OpenConnectionReply2),
90 ID_INCOMPATIBLE_PROTOCOL_VERSION => IncompatibleProtocolVersion =>
91 |src, expected_magic| decode_incompatible(src, expected_magic).map(OfflinePacket::IncompatibleProtocolVersion),
92 ID_CONNECTION_REQUEST_FAILED => ConnectionRequestFailed =>
93 |src, expected_magic| decode_reject_packet(src, expected_magic).map(|(magic, server_guid)| {
94 OfflinePacket::ConnectionRequestFailed(ConnectionRequestFailed {
95 server_guid,
96 magic,
97 })
98 }),
99 ID_ALREADY_CONNECTED => AlreadyConnected =>
100 |src, expected_magic| decode_reject_packet(src, expected_magic).map(|(magic, server_guid)| {
101 OfflinePacket::AlreadyConnected(AlreadyConnected { server_guid, magic })
102 }),
103 ID_NO_FREE_INCOMING_CONNECTIONS => NoFreeIncomingConnections =>
104 |src, expected_magic| decode_reject_packet(src, expected_magic).map(|(magic, server_guid)| {
105 OfflinePacket::NoFreeIncomingConnections(NoFreeIncomingConnections {
106 server_guid,
107 magic,
108 })
109 }),
110 ID_CONNECTION_BANNED => ConnectionBanned =>
111 |src, expected_magic| decode_reject_packet(src, expected_magic).map(|(magic, server_guid)| {
112 OfflinePacket::ConnectionBanned(ConnectionBanned { server_guid, magic })
113 }),
114 ID_IP_RECENTLY_CONNECTED => IpRecentlyConnected =>
115 |src, expected_magic| decode_reject_packet(src, expected_magic).map(|(magic, server_guid)| {
116 OfflinePacket::IpRecentlyConnected(IpRecentlyConnected { server_guid, magic })
117 }),
118}
119
120impl OfflinePacket {
121 pub fn id(&self) -> u8 {
122 offline_packet_id(self)
123 }
124
125 pub fn encode(&self, dst: &mut impl BufMut) -> Result<(), EncodeError> {
126 self.id().encode_raknet(dst)?;
127 match self {
128 OfflinePacket::UnconnectedPing(pkt)
129 | OfflinePacket::UnconnectedPingOpenConnections(pkt) => {
130 pkt.ping_time.encode_raknet(dst)?;
131 pkt.magic.encode_raknet(dst)?;
132 pkt.client_guid.encode_raknet(dst)?;
133 }
134 OfflinePacket::UnconnectedPong(pkt) => {
135 pkt.ping_time.encode_raknet(dst)?;
136 pkt.server_guid.encode_raknet(dst)?;
137 pkt.magic.encode_raknet(dst)?;
138 validate_unconnected_pong_motd_len(pkt.motd.len())?;
139 let motd_len = u16::try_from(pkt.motd.len())
140 .map_err(|_| EncodeError::OfflinePongMotdTooLong(pkt.motd.len()))?;
141 motd_len.encode_raknet(dst)?;
142 dst.put_slice(&pkt.motd);
143 }
144 OfflinePacket::OpenConnectionRequest1(pkt) => {
145 validate_mtu(pkt.mtu)?;
146 pkt.magic.encode_raknet(dst)?;
147 pkt.protocol_version.encode_raknet(dst)?;
148
149 let padding_len = usize::from(pkt.mtu).saturating_sub(18);
151 for _ in 0..padding_len {
152 dst.put_u8(0);
153 }
154 }
155 OfflinePacket::OpenConnectionReply1(pkt) => {
156 validate_mtu(pkt.mtu)?;
157 pkt.magic.encode_raknet(dst)?;
158 pkt.server_guid.encode_raknet(dst)?;
159 pkt.cookie.is_some().encode_raknet(dst)?;
160 if let Some(cookie) = pkt.cookie {
161 cookie.encode_raknet(dst)?;
162 }
163 pkt.mtu.encode_raknet(dst)?;
164 }
165 OfflinePacket::OpenConnectionRequest2(pkt) => {
166 validate_mtu(pkt.mtu)?;
167 pkt.magic.encode_raknet(dst)?;
168 if let Some(cookie) = pkt.cookie {
169 cookie.encode_raknet(dst)?;
170 pkt.client_proof.encode_raknet(dst)?;
171 }
172 pkt.server_addr.encode_raknet(dst)?;
173 pkt.mtu.encode_raknet(dst)?;
174 pkt.client_guid.encode_raknet(dst)?;
175 }
176 OfflinePacket::OpenConnectionReply2(pkt) => {
177 validate_mtu(pkt.mtu)?;
178 pkt.magic.encode_raknet(dst)?;
179 pkt.server_guid.encode_raknet(dst)?;
180 pkt.server_addr.encode_raknet(dst)?;
181 pkt.mtu.encode_raknet(dst)?;
182 pkt.use_encryption.encode_raknet(dst)?;
183 }
184 OfflinePacket::IncompatibleProtocolVersion(pkt) => {
185 pkt.protocol_version.encode_raknet(dst)?;
186 pkt.magic.encode_raknet(dst)?;
187 pkt.server_guid.encode_raknet(dst)?;
188 }
189 OfflinePacket::ConnectionRequestFailed(pkt) => {
190 pkt.magic.encode_raknet(dst)?;
191 pkt.server_guid.encode_raknet(dst)?;
192 }
193 OfflinePacket::AlreadyConnected(pkt) => {
194 pkt.magic.encode_raknet(dst)?;
195 pkt.server_guid.encode_raknet(dst)?;
196 }
197 OfflinePacket::NoFreeIncomingConnections(pkt) => {
198 pkt.magic.encode_raknet(dst)?;
199 pkt.server_guid.encode_raknet(dst)?;
200 }
201 OfflinePacket::ConnectionBanned(pkt) => {
202 pkt.magic.encode_raknet(dst)?;
203 pkt.server_guid.encode_raknet(dst)?;
204 }
205 OfflinePacket::IpRecentlyConnected(pkt) => {
206 pkt.magic.encode_raknet(dst)?;
207 pkt.server_guid.encode_raknet(dst)?;
208 }
209 }
210
211 Ok(())
212 }
213
214 pub fn decode(src: &mut impl Buf) -> Result<Self, DecodeError> {
215 Self::decode_with_magic(src, DEFAULT_UNCONNECTED_MAGIC)
216 }
217
218 pub fn decode_with_magic(
219 src: &mut impl Buf,
220 expected_magic: Magic,
221 ) -> Result<Self, DecodeError> {
222 let id = u8::decode_raknet(src)?;
223 decode_offline_packet_with_registry(id, src, expected_magic)
224 }
225}
226
227fn validate_magic(magic: Magic, expected_magic: Magic) -> Result<Magic, DecodeError> {
228 if magic != expected_magic {
229 return Err(DecodeError::InvalidMagic);
230 }
231 Ok(magic)
232}
233
234fn validate_mtu(mtu: u16) -> Result<(), EncodeError> {
235 if !(MINIMUM_MTU_SIZE..=MAXIMUM_MTU_SIZE).contains(&mtu) {
236 return Err(EncodeError::InvalidMtu(mtu));
237 }
238 Ok(())
239}
240
241pub fn validate_unconnected_pong_motd_len(len: usize) -> Result<(), EncodeError> {
242 if len > MAX_UNCONNECTED_PONG_MOTD_BYTES {
243 return Err(EncodeError::OfflinePongMotdTooLong(len));
244 }
245 Ok(())
246}
247
248#[cfg(test)]
249mod tests {
250 use bytes::BytesMut;
251
252 use super::{
253 ConnectionBanned, DEFAULT_UNCONNECTED_MAGIC, MAX_UNCONNECTED_PONG_MOTD_BYTES,
254 NoFreeIncomingConnections, OfflinePacket, OpenConnectionRequest1, OpenConnectionRequest2,
255 Request2ParsePath, UnconnectedPong,
256 };
257 use crate::error::{DecodeError, EncodeError};
258
259 fn roundtrip(packet: OfflinePacket) -> OfflinePacket {
260 let mut buf = BytesMut::new();
261 packet.encode(&mut buf).expect("encode must succeed");
262 let mut src = &buf[..];
263 OfflinePacket::decode(&mut src).expect("decode must succeed")
264 }
265
266 #[test]
267 fn no_free_incoming_connections_roundtrip() {
268 let packet = OfflinePacket::NoFreeIncomingConnections(NoFreeIncomingConnections {
269 server_guid: 0xAA11_BB22_CC33_DD44,
270 magic: DEFAULT_UNCONNECTED_MAGIC,
271 });
272 let decoded = roundtrip(packet);
273 match decoded {
274 OfflinePacket::NoFreeIncomingConnections(p) => {
275 assert_eq!(p.server_guid, 0xAA11_BB22_CC33_DD44);
276 assert_eq!(p.magic, DEFAULT_UNCONNECTED_MAGIC);
277 }
278 _ => panic!("unexpected packet variant"),
279 }
280 }
281
282 #[test]
283 fn connection_banned_roundtrip() {
284 let packet = OfflinePacket::ConnectionBanned(ConnectionBanned {
285 server_guid: 0x1020_3040_5060_7080,
286 magic: DEFAULT_UNCONNECTED_MAGIC,
287 });
288 let decoded = roundtrip(packet);
289 match decoded {
290 OfflinePacket::ConnectionBanned(p) => {
291 assert_eq!(p.server_guid, 0x1020_3040_5060_7080);
292 assert_eq!(p.magic, DEFAULT_UNCONNECTED_MAGIC);
293 }
294 _ => panic!("unexpected packet variant"),
295 }
296 }
297
298 #[test]
299 fn open_connection_request2_without_cookie_prefers_strict_no_cookie_path() {
300 let packet = OfflinePacket::OpenConnectionRequest2(OpenConnectionRequest2 {
301 server_addr: "127.0.0.1:19132".parse().expect("valid socket addr"),
302 mtu: 1400,
303 client_guid: 0x11_22_33_44_55_66_77_88,
304 cookie: None,
305 client_proof: false,
306 parse_path: Request2ParsePath::StrictNoCookie,
307 magic: DEFAULT_UNCONNECTED_MAGIC,
308 });
309 let decoded = roundtrip(packet);
310 match decoded {
311 OfflinePacket::OpenConnectionRequest2(p) => {
312 assert_eq!(p.cookie, None);
313 assert_eq!(p.parse_path, Request2ParsePath::StrictNoCookie);
314 }
315 _ => panic!("unexpected packet variant"),
316 }
317 }
318
319 #[test]
320 fn open_connection_request2_with_cookie_prefers_strict_cookie_path() {
321 let packet = OfflinePacket::OpenConnectionRequest2(OpenConnectionRequest2 {
322 server_addr: "127.0.0.1:19132".parse().expect("valid socket addr"),
323 mtu: 1400,
324 client_guid: 0x88_77_66_55_44_33_22_11,
325 cookie: Some(0xAABB_CCDD),
326 client_proof: true,
327 parse_path: Request2ParsePath::StrictWithCookie,
328 magic: DEFAULT_UNCONNECTED_MAGIC,
329 });
330 let decoded = roundtrip(packet);
331 match decoded {
332 OfflinePacket::OpenConnectionRequest2(p) => {
333 assert_eq!(p.cookie, Some(0xAABB_CCDD));
334 assert!(p.client_proof);
335 assert_eq!(p.parse_path, Request2ParsePath::StrictWithCookie);
336 }
337 _ => panic!("unexpected packet variant"),
338 }
339 }
340
341 #[test]
342 fn open_connection_request2_legacy_fallback_accepts_non_boolean_proof_byte() {
343 let packet = OfflinePacket::OpenConnectionRequest2(OpenConnectionRequest2 {
344 server_addr: "127.0.0.1:19132".parse().expect("valid socket addr"),
345 mtu: 1400,
346 client_guid: 0xAB_CD_EF_01_23_45_67_89,
347 cookie: Some(0xAABB_CCDD),
348 client_proof: true,
349 parse_path: Request2ParsePath::StrictWithCookie,
350 magic: DEFAULT_UNCONNECTED_MAGIC,
351 });
352 let mut buf = BytesMut::new();
353 packet.encode(&mut buf).expect("encode must succeed");
354
355 let proof_idx = 1 + 16 + 4;
356 buf[proof_idx] = 2;
357
358 let mut src = &buf[..];
359 let decoded = OfflinePacket::decode(&mut src).expect("decode must succeed");
360 match decoded {
361 OfflinePacket::OpenConnectionRequest2(p) => {
362 assert_eq!(p.parse_path, Request2ParsePath::LegacyHeuristic);
363 assert_eq!(p.cookie, Some(0xAABB_CCDD));
364 assert!(!p.client_proof);
365 }
366 _ => panic!("unexpected packet variant"),
367 }
368 }
369
370 #[test]
371 fn unconnected_pong_encode_rejects_oversized_motd() {
372 let oversized = vec![b'a'; MAX_UNCONNECTED_PONG_MOTD_BYTES + 1];
373 let packet = OfflinePacket::UnconnectedPong(UnconnectedPong {
374 ping_time: 1,
375 server_guid: 2,
376 magic: DEFAULT_UNCONNECTED_MAGIC,
377 motd: oversized.into(),
378 });
379 let mut buf = BytesMut::new();
380 let err = packet
381 .encode(&mut buf)
382 .expect_err("oversized motd must be rejected");
383 assert!(matches!(err, EncodeError::OfflinePongMotdTooLong(_)));
384 }
385
386 #[test]
387 fn decode_with_custom_magic_accepts_matching_packet() {
388 let custom_magic = [
389 0x13, 0x57, 0x9B, 0xDF, 0x24, 0x68, 0xAC, 0xF0, 0x10, 0x32, 0x54, 0x76, 0x98, 0xBA,
390 0xDC, 0xFE,
391 ];
392 let packet = OfflinePacket::OpenConnectionRequest1(OpenConnectionRequest1 {
393 protocol_version: 10,
394 mtu: 1400,
395 magic: custom_magic,
396 });
397 let mut buf = BytesMut::new();
398 packet.encode(&mut buf).expect("encode must succeed");
399
400 let mut src = &buf[..];
401 let decoded = OfflinePacket::decode_with_magic(&mut src, custom_magic)
402 .expect("decode must accept matching custom magic");
403 match decoded {
404 OfflinePacket::OpenConnectionRequest1(req1) => {
405 assert_eq!(req1.magic, custom_magic);
406 assert_eq!(req1.mtu, 1400);
407 }
408 _ => panic!("unexpected packet variant"),
409 }
410 }
411
412 #[test]
413 fn decode_with_custom_magic_rejects_mismatch() {
414 let packet_magic = [
415 0x13, 0x57, 0x9B, 0xDF, 0x24, 0x68, 0xAC, 0xF0, 0x10, 0x32, 0x54, 0x76, 0x98, 0xBA,
416 0xDC, 0xFE,
417 ];
418 let expected_magic = [
419 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
420 0xEE, 0xFF,
421 ];
422 let packet = OfflinePacket::OpenConnectionRequest1(OpenConnectionRequest1 {
423 protocol_version: 10,
424 mtu: 1400,
425 magic: packet_magic,
426 });
427 let mut buf = BytesMut::new();
428 packet.encode(&mut buf).expect("encode must succeed");
429
430 let mut src = &buf[..];
431 let err = OfflinePacket::decode_with_magic(&mut src, expected_magic)
432 .expect_err("decode must reject mismatched magic");
433 assert!(matches!(err, DecodeError::InvalidMagic));
434 }
435}