Skip to main content

raknet_rust/handshake/
mod.rs

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                // Req1 MTU is inferred from packet length; remaining bytes are zero padding.
150                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}