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/// Serverbound login start (packet ID 0x00 in LOGIN state).
12#[derive(Debug, Clone)]
13pub struct LoginStartPacket {
14    /// The player's username.
15    pub username: String,
16    /// The player's UUID (sent by client since 1.19.1).
17    pub uuid: Uuid,
18}
19
20impl Packet for LoginStartPacket {
21    const PACKET_ID: i32 = 0x00;
22
23    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
24        let username = types::read_string_max(buf, 16)?;
25        let uuid = types::read_uuid(buf)?;
26        Ok(Self { username, uuid })
27    }
28
29    fn encode(&self, buf: &mut impl BufMut) {
30        types::write_string(buf, &self.username);
31        types::write_uuid(buf, self.uuid);
32    }
33}
34
35/// Clientbound encryption request (packet ID 0x01 in LOGIN state).
36#[derive(Debug, Clone)]
37pub struct EncryptionRequestPacket {
38    /// Server ID (empty string for modern Minecraft).
39    pub server_id: String,
40    /// DER-encoded RSA public key.
41    pub public_key: Vec<u8>,
42    /// Random verify token (4 bytes).
43    pub verify_token: Vec<u8>,
44    /// Whether the server should authenticate the player (1.20.5+).
45    pub should_authenticate: bool,
46}
47
48impl Packet for EncryptionRequestPacket {
49    const PACKET_ID: i32 = 0x01;
50
51    #[allow(clippy::cast_sign_loss)]
52    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
53        let server_id = types::read_string_max(buf, 20)?;
54        let pk_len = varint::read_var_int(buf)? as usize;
55        if buf.remaining() < pk_len {
56            return Err(ProtocolError::UnexpectedEof);
57        }
58        let public_key = buf.copy_to_bytes(pk_len).to_vec();
59        let vt_len = varint::read_var_int(buf)? as usize;
60        if buf.remaining() < vt_len {
61            return Err(ProtocolError::UnexpectedEof);
62        }
63        let verify_token = buf.copy_to_bytes(vt_len).to_vec();
64        let should_authenticate = if buf.has_remaining() {
65            buf.get_u8() != 0
66        } else {
67            true
68        };
69        Ok(Self {
70            server_id,
71            public_key,
72            verify_token,
73            should_authenticate,
74        })
75    }
76
77    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
78    fn encode(&self, buf: &mut impl BufMut) {
79        types::write_string(buf, &self.server_id);
80        varint::write_var_int(buf, self.public_key.len() as i32);
81        buf.put_slice(&self.public_key);
82        varint::write_var_int(buf, self.verify_token.len() as i32);
83        buf.put_slice(&self.verify_token);
84        buf.put_u8(u8::from(self.should_authenticate));
85    }
86}
87
88/// Serverbound encryption response (packet ID 0x01 in LOGIN state).
89#[derive(Debug, Clone)]
90pub struct EncryptionResponsePacket {
91    /// RSA-encrypted shared secret.
92    pub shared_secret: Vec<u8>,
93    /// RSA-encrypted verify token.
94    pub verify_token: Vec<u8>,
95}
96
97impl Packet for EncryptionResponsePacket {
98    const PACKET_ID: i32 = 0x01;
99
100    #[allow(clippy::cast_sign_loss)]
101    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
102        let ss_len = varint::read_var_int(buf)? as usize;
103        if buf.remaining() < ss_len {
104            return Err(ProtocolError::UnexpectedEof);
105        }
106        let shared_secret = buf.copy_to_bytes(ss_len).to_vec();
107        let vt_len = varint::read_var_int(buf)? as usize;
108        if buf.remaining() < vt_len {
109            return Err(ProtocolError::UnexpectedEof);
110        }
111        let verify_token = buf.copy_to_bytes(vt_len).to_vec();
112        Ok(Self {
113            shared_secret,
114            verify_token,
115        })
116    }
117
118    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
119    fn encode(&self, buf: &mut impl BufMut) {
120        varint::write_var_int(buf, self.shared_secret.len() as i32);
121        buf.put_slice(&self.shared_secret);
122        varint::write_var_int(buf, self.verify_token.len() as i32);
123        buf.put_slice(&self.verify_token);
124    }
125}
126
127/// Clientbound login success (packet ID 0x02 in LOGIN state).
128#[derive(Debug, Clone)]
129pub struct LoginSuccessPacket {
130    /// The player's game profile.
131    pub uuid: Uuid,
132    /// The player's username.
133    pub username: String,
134    /// Profile properties.
135    pub properties: Vec<ProfileProperty>,
136}
137
138impl LoginSuccessPacket {
139    /// Create from a game profile.
140    #[must_use]
141    pub fn from_profile(profile: &GameProfile) -> Self {
142        Self {
143            uuid: profile.id,
144            username: profile.name.clone(),
145            properties: profile.properties.clone(),
146        }
147    }
148}
149
150impl Packet for LoginSuccessPacket {
151    const PACKET_ID: i32 = 0x02;
152
153    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
154        let uuid = types::read_uuid(buf)?;
155        let username = types::read_string_max(buf, 16)?;
156        let properties = types::read_properties(buf)?;
157        Ok(Self {
158            uuid,
159            username,
160            properties,
161        })
162    }
163
164    fn encode(&self, buf: &mut impl BufMut) {
165        types::write_uuid(buf, self.uuid);
166        types::write_string(buf, &self.username);
167        types::write_properties(buf, &self.properties);
168    }
169}
170
171/// Clientbound set compression (packet ID 0x03 in LOGIN state).
172#[derive(Debug, Clone)]
173pub struct SetCompressionPacket {
174    /// Compression threshold in bytes. Packets larger than this will be compressed.
175    /// A value of -1 disables compression.
176    pub threshold: i32,
177}
178
179impl Packet for SetCompressionPacket {
180    const PACKET_ID: i32 = 0x03;
181
182    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
183        let threshold = varint::read_var_int(buf)?;
184        Ok(Self { threshold })
185    }
186
187    fn encode(&self, buf: &mut impl BufMut) {
188        varint::write_var_int(buf, self.threshold);
189    }
190}
191
192/// Clientbound login plugin message (packet ID 0x04 in LOGIN state).
193/// Used by Velocity's modern forwarding protocol.
194#[derive(Debug, Clone)]
195pub struct LoginPluginRequestPacket {
196    /// Message ID for correlating request/response.
197    pub message_id: i32,
198    /// Channel identifier (e.g., "`velocity:player_info`").
199    pub channel: String,
200    /// Plugin-specific payload data.
201    pub data: Bytes,
202}
203
204impl Packet for LoginPluginRequestPacket {
205    const PACKET_ID: i32 = 0x04;
206
207    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
208        let message_id = varint::read_var_int(buf)?;
209        let channel = types::read_string(buf)?;
210        let data = buf.copy_to_bytes(buf.remaining());
211        Ok(Self {
212            message_id,
213            channel,
214            data,
215        })
216    }
217
218    fn encode(&self, buf: &mut impl BufMut) {
219        varint::write_var_int(buf, self.message_id);
220        types::write_string(buf, &self.channel);
221        buf.put_slice(&self.data);
222    }
223}
224
225/// Serverbound login plugin response (packet ID 0x02 in LOGIN state).
226#[derive(Debug, Clone)]
227pub struct LoginPluginResponsePacket {
228    /// Message ID matching the request.
229    pub message_id: i32,
230    /// Whether the client understood the request.
231    pub successful: bool,
232    /// Response payload (only meaningful if successful).
233    pub data: Bytes,
234}
235
236impl Packet for LoginPluginResponsePacket {
237    const PACKET_ID: i32 = 0x02;
238
239    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
240        let message_id = varint::read_var_int(buf)?;
241        if !buf.has_remaining() {
242            return Err(ProtocolError::UnexpectedEof);
243        }
244        let successful = buf.get_u8() != 0;
245        let data = if successful {
246            buf.copy_to_bytes(buf.remaining())
247        } else {
248            Bytes::new()
249        };
250        Ok(Self {
251            message_id,
252            successful,
253            data,
254        })
255    }
256
257    fn encode(&self, buf: &mut impl BufMut) {
258        varint::write_var_int(buf, self.message_id);
259        buf.put_u8(u8::from(self.successful));
260        if self.successful {
261            buf.put_slice(&self.data);
262        }
263    }
264}
265
266/// Serverbound login acknowledged (packet ID 0x03 in LOGIN state).
267/// Sent by the client after receiving `LoginSuccess`, triggers transition to CONFIG state.
268#[derive(Debug, Clone)]
269pub struct LoginAcknowledgedPacket;
270
271impl Packet for LoginAcknowledgedPacket {
272    const PACKET_ID: i32 = 0x03;
273
274    fn decode(_buf: &mut impl Buf) -> Result<Self, ProtocolError> {
275        Ok(Self)
276    }
277
278    fn encode(&self, _buf: &mut impl BufMut) {}
279}
280
281/// Clientbound disconnect during login (packet ID 0x00 in LOGIN state).
282#[derive(Debug, Clone)]
283pub struct LoginDisconnectPacket {
284    /// JSON text component with the disconnect reason.
285    pub reason: String,
286}
287
288impl Packet for LoginDisconnectPacket {
289    const PACKET_ID: i32 = 0x00;
290
291    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
292        let reason = types::read_string(buf)?;
293        Ok(Self { reason })
294    }
295
296    fn encode(&self, buf: &mut impl BufMut) {
297        types::write_string(buf, &self.reason);
298    }
299}