Skip to main content

deepslate_protocol/packet/
login.rs

1//! Login state packets.
2
3use bytes::{Buf, BufMut, Bytes};
4use uuid::Uuid;
5
6use crate::types::{self, GameProfile, ProfileProperty, ProtocolError};
7use crate::varint;
8
9use super::Packet;
10
11/// Maximum length (in bytes) for an RSA-encrypted field.
12///
13/// Minecraft uses 1024-bit RSA, producing 128-byte ciphertext. We allow up to
14/// 256 bytes to accommodate potential future key-size changes while still
15/// rejecting obviously malformed packets early.
16const MAX_ENCRYPTED_FIELD: usize = 256;
17
18/// Serverbound login start (packet ID 0x00 in LOGIN state).
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct LoginStartPacket {
21    /// The player's username.
22    pub username: String,
23    /// The player's UUID (sent by client since 1.19.1).
24    pub uuid: Uuid,
25}
26
27impl Packet for LoginStartPacket {
28    const PACKET_ID: i32 = 0x00;
29
30    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
31        let username = types::read_string_max(buf, 16)?;
32        let uuid = types::read_uuid(buf)?;
33        Ok(Self { username, uuid })
34    }
35
36    fn encode(&self, buf: &mut impl BufMut) {
37        types::write_string(buf, &self.username);
38        types::write_uuid(buf, self.uuid);
39    }
40}
41
42/// Clientbound encryption request (packet ID 0x01 in LOGIN state).
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct EncryptionRequestPacket {
45    /// Server ID (empty string for modern Minecraft).
46    pub server_id: String,
47    /// DER-encoded RSA public key.
48    pub public_key: Vec<u8>,
49    /// Random verify token (4 bytes).
50    pub verify_token: Vec<u8>,
51    /// Whether the server should authenticate the player (1.20.5+).
52    pub should_authenticate: bool,
53}
54
55impl Packet for EncryptionRequestPacket {
56    const PACKET_ID: i32 = 0x01;
57
58    #[allow(clippy::cast_sign_loss)]
59    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
60        let server_id = types::read_string_max(buf, 20)?;
61        let pk_len = varint::read_var_int(buf)? as usize;
62        if pk_len > MAX_ENCRYPTED_FIELD {
63            return Err(ProtocolError::ByteArrayTooLong {
64                length: pk_len,
65                max: MAX_ENCRYPTED_FIELD,
66            });
67        }
68        if buf.remaining() < pk_len {
69            return Err(ProtocolError::UnexpectedEof);
70        }
71        let public_key = buf.copy_to_bytes(pk_len).to_vec();
72        let vt_len = varint::read_var_int(buf)? as usize;
73        if vt_len > MAX_ENCRYPTED_FIELD {
74            return Err(ProtocolError::ByteArrayTooLong {
75                length: vt_len,
76                max: MAX_ENCRYPTED_FIELD,
77            });
78        }
79        if buf.remaining() < vt_len {
80            return Err(ProtocolError::UnexpectedEof);
81        }
82        let verify_token = buf.copy_to_bytes(vt_len).to_vec();
83        let should_authenticate = if buf.has_remaining() {
84            buf.get_u8() != 0
85        } else {
86            true
87        };
88        Ok(Self {
89            server_id,
90            public_key,
91            verify_token,
92            should_authenticate,
93        })
94    }
95
96    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
97    fn encode(&self, buf: &mut impl BufMut) {
98        types::write_string(buf, &self.server_id);
99        varint::write_var_int(buf, self.public_key.len() as i32);
100        buf.put_slice(&self.public_key);
101        varint::write_var_int(buf, self.verify_token.len() as i32);
102        buf.put_slice(&self.verify_token);
103        buf.put_u8(u8::from(self.should_authenticate));
104    }
105}
106
107/// Serverbound encryption response (packet ID 0x01 in LOGIN state).
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct EncryptionResponsePacket {
110    /// RSA-encrypted shared secret.
111    pub shared_secret: Vec<u8>,
112    /// RSA-encrypted verify token.
113    pub verify_token: Vec<u8>,
114}
115
116impl Packet for EncryptionResponsePacket {
117    const PACKET_ID: i32 = 0x01;
118
119    #[allow(clippy::cast_sign_loss)]
120    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
121        let ss_len = varint::read_var_int(buf)? as usize;
122        if ss_len > MAX_ENCRYPTED_FIELD {
123            return Err(ProtocolError::ByteArrayTooLong {
124                length: ss_len,
125                max: MAX_ENCRYPTED_FIELD,
126            });
127        }
128        if buf.remaining() < ss_len {
129            return Err(ProtocolError::UnexpectedEof);
130        }
131        let shared_secret = buf.copy_to_bytes(ss_len).to_vec();
132        let vt_len = varint::read_var_int(buf)? as usize;
133        if vt_len > MAX_ENCRYPTED_FIELD {
134            return Err(ProtocolError::ByteArrayTooLong {
135                length: vt_len,
136                max: MAX_ENCRYPTED_FIELD,
137            });
138        }
139        if buf.remaining() < vt_len {
140            return Err(ProtocolError::UnexpectedEof);
141        }
142        let verify_token = buf.copy_to_bytes(vt_len).to_vec();
143        Ok(Self {
144            shared_secret,
145            verify_token,
146        })
147    }
148
149    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
150    fn encode(&self, buf: &mut impl BufMut) {
151        varint::write_var_int(buf, self.shared_secret.len() as i32);
152        buf.put_slice(&self.shared_secret);
153        varint::write_var_int(buf, self.verify_token.len() as i32);
154        buf.put_slice(&self.verify_token);
155    }
156}
157
158/// Clientbound login success (packet ID 0x02 in LOGIN state).
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct LoginSuccessPacket {
161    /// The player's game profile.
162    pub uuid: Uuid,
163    /// The player's username.
164    pub username: String,
165    /// Profile properties.
166    pub properties: Vec<ProfileProperty>,
167}
168
169impl LoginSuccessPacket {
170    /// Create from a game profile.
171    #[must_use]
172    pub fn from_profile(profile: &GameProfile) -> Self {
173        Self {
174            uuid: profile.id,
175            username: profile.name.clone(),
176            properties: profile.properties.clone(),
177        }
178    }
179}
180
181impl Packet for LoginSuccessPacket {
182    const PACKET_ID: i32 = 0x02;
183
184    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
185        let uuid = types::read_uuid(buf)?;
186        let username = types::read_string_max(buf, 16)?;
187        let properties = types::read_properties(buf)?;
188        Ok(Self {
189            uuid,
190            username,
191            properties,
192        })
193    }
194
195    fn encode(&self, buf: &mut impl BufMut) {
196        types::write_uuid(buf, self.uuid);
197        types::write_string(buf, &self.username);
198        types::write_properties(buf, &self.properties);
199    }
200}
201
202/// Clientbound set compression (packet ID 0x03 in LOGIN state).
203#[derive(Debug, Clone, PartialEq, Eq)]
204pub struct SetCompressionPacket {
205    /// Compression threshold in bytes. Packets larger than this will be compressed.
206    /// A value of -1 disables compression.
207    pub threshold: i32,
208}
209
210impl Packet for SetCompressionPacket {
211    const PACKET_ID: i32 = 0x03;
212
213    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
214        let threshold = varint::read_var_int(buf)?;
215        Ok(Self { threshold })
216    }
217
218    fn encode(&self, buf: &mut impl BufMut) {
219        varint::write_var_int(buf, self.threshold);
220    }
221}
222
223/// Clientbound login plugin message (packet ID 0x04 in LOGIN state).
224/// Used by Velocity's modern forwarding protocol.
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub struct LoginPluginRequestPacket {
227    /// Message ID for correlating request/response.
228    pub message_id: i32,
229    /// Channel identifier (e.g., "`velocity:player_info`").
230    pub channel: String,
231    /// Plugin-specific payload data.
232    pub data: Bytes,
233}
234
235impl Packet for LoginPluginRequestPacket {
236    const PACKET_ID: i32 = 0x04;
237
238    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
239        let message_id = varint::read_var_int(buf)?;
240        let channel = types::read_string(buf)?;
241        let data = buf.copy_to_bytes(buf.remaining());
242        Ok(Self {
243            message_id,
244            channel,
245            data,
246        })
247    }
248
249    fn encode(&self, buf: &mut impl BufMut) {
250        varint::write_var_int(buf, self.message_id);
251        types::write_string(buf, &self.channel);
252        buf.put_slice(&self.data);
253    }
254}
255
256/// Serverbound login plugin response (packet ID 0x02 in LOGIN state).
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct LoginPluginResponsePacket {
259    /// Message ID matching the request.
260    pub message_id: i32,
261    /// Whether the client understood the request.
262    pub successful: bool,
263    /// Response payload (only meaningful if successful).
264    pub data: Bytes,
265}
266
267impl Packet for LoginPluginResponsePacket {
268    const PACKET_ID: i32 = 0x02;
269
270    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
271        let message_id = varint::read_var_int(buf)?;
272        if !buf.has_remaining() {
273            return Err(ProtocolError::UnexpectedEof);
274        }
275        let successful = buf.get_u8() != 0;
276        let data = if successful {
277            buf.copy_to_bytes(buf.remaining())
278        } else {
279            Bytes::new()
280        };
281        Ok(Self {
282            message_id,
283            successful,
284            data,
285        })
286    }
287
288    fn encode(&self, buf: &mut impl BufMut) {
289        varint::write_var_int(buf, self.message_id);
290        buf.put_u8(u8::from(self.successful));
291        if self.successful {
292            buf.put_slice(&self.data);
293        }
294    }
295}
296
297/// Serverbound login acknowledged (packet ID 0x03 in LOGIN state).
298/// Sent by the client after receiving `LoginSuccess`, triggers transition to CONFIG state.
299#[derive(Debug, Clone, PartialEq, Eq)]
300pub struct LoginAcknowledgedPacket;
301
302impl Packet for LoginAcknowledgedPacket {
303    const PACKET_ID: i32 = 0x03;
304
305    fn decode(_buf: &mut impl Buf) -> Result<Self, ProtocolError> {
306        Ok(Self)
307    }
308
309    fn encode(&self, _buf: &mut impl BufMut) {}
310}
311
312/// Clientbound disconnect during login (packet ID 0x00 in LOGIN state).
313#[derive(Debug, Clone, PartialEq, Eq)]
314pub struct LoginDisconnectPacket {
315    /// JSON text component with the disconnect reason.
316    pub reason: String,
317}
318
319impl Packet for LoginDisconnectPacket {
320    const PACKET_ID: i32 = 0x00;
321
322    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
323        let reason = types::read_string(buf)?;
324        Ok(Self { reason })
325    }
326
327    fn encode(&self, buf: &mut impl BufMut) {
328        types::write_string(buf, &self.reason);
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335    use proptest::prelude::*;
336
337    fn profile_property_strategy() -> impl Strategy<Value = ProfileProperty> {
338        (
339            ".{0,32}",                                // name
340            ".{0,1024}",                              // value
341            prop::option::weighted(0.5, ".{0,1024}"), // signature
342        )
343            .prop_map(|(name, value, signature)| ProfileProperty {
344                name,
345                value,
346                signature,
347            })
348    }
349
350    proptest! {
351        #[test]
352        fn login_start_roundtrip(
353            username in ".{0,16}",
354            u in any::<u128>()
355        ) {
356            let packet = LoginStartPacket {
357                username,
358                uuid: Uuid::from_u128(u),
359            };
360            let mut buf = Vec::new();
361            packet.encode(&mut buf);
362            let decoded = LoginStartPacket::decode(&mut &buf[..]).unwrap();
363            prop_assert_eq!(decoded, packet);
364        }
365
366        #[test]
367        fn encryption_request_roundtrip(
368            server_id in ".{0,20}",
369            public_key in prop::collection::vec(any::<u8>(), 0..=MAX_ENCRYPTED_FIELD),
370            verify_token in prop::collection::vec(any::<u8>(), 0..=MAX_ENCRYPTED_FIELD),
371            should_authenticate in any::<bool>()
372        ) {
373            let packet = EncryptionRequestPacket {
374                server_id,
375                public_key,
376                verify_token,
377                should_authenticate,
378            };
379            let mut buf = Vec::new();
380            packet.encode(&mut buf);
381            let decoded = EncryptionRequestPacket::decode(&mut &buf[..]).unwrap();
382            prop_assert_eq!(decoded, packet);
383        }
384
385        #[test]
386        fn encryption_response_roundtrip(
387            shared_secret in prop::collection::vec(any::<u8>(), 0..=MAX_ENCRYPTED_FIELD),
388            verify_token in prop::collection::vec(any::<u8>(), 0..=MAX_ENCRYPTED_FIELD)
389        ) {
390            let packet = EncryptionResponsePacket {
391                shared_secret,
392                verify_token,
393            };
394            let mut buf = Vec::new();
395            packet.encode(&mut buf);
396            let decoded = EncryptionResponsePacket::decode(&mut &buf[..]).unwrap();
397            prop_assert_eq!(decoded, packet);
398        }
399
400        #[test]
401        fn login_success_roundtrip(
402            u in any::<u128>(),
403            username in ".{0,16}",
404            properties in prop::collection::vec(profile_property_strategy(), 0..4)
405        ) {
406            let packet = LoginSuccessPacket {
407                uuid: Uuid::from_u128(u),
408                username,
409                properties,
410            };
411            let mut buf = Vec::new();
412            packet.encode(&mut buf);
413            let decoded = LoginSuccessPacket::decode(&mut &buf[..]).unwrap();
414            prop_assert_eq!(decoded, packet);
415        }
416
417        #[test]
418        fn set_compression_roundtrip(threshold in any::<i32>()) {
419            let packet = SetCompressionPacket { threshold };
420            let mut buf = Vec::new();
421            packet.encode(&mut buf);
422            let decoded = SetCompressionPacket::decode(&mut &buf[..]).unwrap();
423            prop_assert_eq!(decoded, packet);
424        }
425
426        #[test]
427        fn login_plugin_request_roundtrip(
428            message_id in any::<i32>(),
429            channel in ".{0,128}",
430            data in prop::collection::vec(any::<u8>(), 0..1024)
431        ) {
432            let packet = LoginPluginRequestPacket {
433                message_id,
434                channel,
435                data: Bytes::from(data),
436            };
437            let mut buf = Vec::new();
438            packet.encode(&mut buf);
439            let decoded = LoginPluginRequestPacket::decode(&mut &buf[..]).unwrap();
440            prop_assert_eq!(decoded, packet);
441        }
442
443        #[test]
444        fn login_plugin_response_roundtrip(
445            message_id in any::<i32>(),
446            successful in any::<bool>(),
447            data in prop::collection::vec(any::<u8>(), 0..1024)
448        ) {
449            let packet = LoginPluginResponsePacket {
450                message_id,
451                successful,
452                data: if successful { Bytes::from(data) } else { Bytes::new() },
453            };
454            let mut buf = Vec::new();
455            packet.encode(&mut buf);
456            let decoded = LoginPluginResponsePacket::decode(&mut &buf[..]).unwrap();
457            prop_assert_eq!(decoded, packet);
458        }
459
460        #[test]
461        fn login_acknowledged_roundtrip(packet in Just(LoginAcknowledgedPacket)) {
462            let mut buf = Vec::new();
463            packet.encode(&mut buf);
464            let decoded = LoginAcknowledgedPacket::decode(&mut &buf[..]).unwrap();
465            prop_assert_eq!(decoded, packet);
466        }
467
468        #[test]
469        fn login_disconnect_roundtrip(reason in ".{0,1024}") {
470            let packet = LoginDisconnectPacket { reason };
471            let mut buf = Vec::new();
472            packet.encode(&mut buf);
473            let decoded = LoginDisconnectPacket::decode(&mut &buf[..]).unwrap();
474            prop_assert_eq!(decoded, packet);
475        }
476    }
477
478    #[test]
479    fn encryption_request_rejects_oversized_public_key() {
480        let packet = EncryptionRequestPacket {
481            server_id: String::new(),
482            public_key: vec![0xAA; MAX_ENCRYPTED_FIELD + 1],
483            verify_token: vec![0xBB; 4],
484            should_authenticate: true,
485        };
486        let mut buf = Vec::new();
487        packet.encode(&mut buf);
488        let result = EncryptionRequestPacket::decode(&mut &buf[..]);
489        assert!(matches!(
490            result,
491            Err(ProtocolError::ByteArrayTooLong {
492                length,
493                max: MAX_ENCRYPTED_FIELD,
494            }) if length == MAX_ENCRYPTED_FIELD + 1
495        ));
496    }
497
498    #[test]
499    fn encryption_request_rejects_oversized_verify_token() {
500        let packet = EncryptionRequestPacket {
501            server_id: String::new(),
502            public_key: vec![0xAA; 128],
503            verify_token: vec![0xBB; MAX_ENCRYPTED_FIELD + 1],
504            should_authenticate: true,
505        };
506        let mut buf = Vec::new();
507        packet.encode(&mut buf);
508        let result = EncryptionRequestPacket::decode(&mut &buf[..]);
509        assert!(matches!(
510            result,
511            Err(ProtocolError::ByteArrayTooLong {
512                length,
513                max: MAX_ENCRYPTED_FIELD,
514            }) if length == MAX_ENCRYPTED_FIELD + 1
515        ));
516    }
517
518    #[test]
519    fn encryption_response_rejects_oversized_shared_secret() {
520        let packet = EncryptionResponsePacket {
521            shared_secret: vec![0xAA; MAX_ENCRYPTED_FIELD + 1],
522            verify_token: vec![0xBB; 4],
523        };
524        let mut buf = Vec::new();
525        packet.encode(&mut buf);
526        let result = EncryptionResponsePacket::decode(&mut &buf[..]);
527        assert!(matches!(
528            result,
529            Err(ProtocolError::ByteArrayTooLong {
530                length,
531                max: MAX_ENCRYPTED_FIELD,
532            }) if length == MAX_ENCRYPTED_FIELD + 1
533        ));
534    }
535
536    #[test]
537    fn encryption_response_rejects_oversized_verify_token() {
538        let packet = EncryptionResponsePacket {
539            shared_secret: vec![0xAA; 128],
540            verify_token: vec![0xBB; MAX_ENCRYPTED_FIELD + 1],
541        };
542        let mut buf = Vec::new();
543        packet.encode(&mut buf);
544        let result = EncryptionResponsePacket::decode(&mut &buf[..]);
545        assert!(matches!(
546            result,
547            Err(ProtocolError::ByteArrayTooLong {
548                length,
549                max: MAX_ENCRYPTED_FIELD,
550            }) if length == MAX_ENCRYPTED_FIELD + 1
551        ));
552    }
553}