deepslate-protocol 0.1.0

Minecraft protocol primitives for the Deepslate proxy.
Documentation
//! Login state packets.

use bytes::{Buf, BufMut, Bytes};
use uuid::Uuid;

use crate::types::{self, GameProfile, ProfileProperty, ProtocolError};
use crate::varint;

use super::Packet;

/// Serverbound login start (packet ID 0x00 in LOGIN state).
#[derive(Debug, Clone)]
pub struct LoginStartPacket {
    /// The player's username.
    pub username: String,
    /// The player's UUID (sent by client since 1.19.1).
    pub uuid: Uuid,
}

impl Packet for LoginStartPacket {
    const PACKET_ID: i32 = 0x00;

    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
        let username = types::read_string_max(buf, 16)?;
        let uuid = types::read_uuid(buf)?;
        Ok(Self { username, uuid })
    }

    fn encode(&self, buf: &mut impl BufMut) {
        types::write_string(buf, &self.username);
        types::write_uuid(buf, self.uuid);
    }
}

/// Clientbound encryption request (packet ID 0x01 in LOGIN state).
#[derive(Debug, Clone)]
pub struct EncryptionRequestPacket {
    /// Server ID (empty string for modern Minecraft).
    pub server_id: String,
    /// DER-encoded RSA public key.
    pub public_key: Vec<u8>,
    /// Random verify token (4 bytes).
    pub verify_token: Vec<u8>,
    /// Whether the server should authenticate the player (1.20.5+).
    pub should_authenticate: bool,
}

impl Packet for EncryptionRequestPacket {
    const PACKET_ID: i32 = 0x01;

    #[allow(clippy::cast_sign_loss)]
    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
        let server_id = types::read_string_max(buf, 20)?;
        let pk_len = varint::read_var_int(buf)? as usize;
        if buf.remaining() < pk_len {
            return Err(ProtocolError::UnexpectedEof);
        }
        let public_key = buf.copy_to_bytes(pk_len).to_vec();
        let vt_len = varint::read_var_int(buf)? as usize;
        if buf.remaining() < vt_len {
            return Err(ProtocolError::UnexpectedEof);
        }
        let verify_token = buf.copy_to_bytes(vt_len).to_vec();
        let should_authenticate = if buf.has_remaining() {
            buf.get_u8() != 0
        } else {
            true
        };
        Ok(Self {
            server_id,
            public_key,
            verify_token,
            should_authenticate,
        })
    }

    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
    fn encode(&self, buf: &mut impl BufMut) {
        types::write_string(buf, &self.server_id);
        varint::write_var_int(buf, self.public_key.len() as i32);
        buf.put_slice(&self.public_key);
        varint::write_var_int(buf, self.verify_token.len() as i32);
        buf.put_slice(&self.verify_token);
        buf.put_u8(u8::from(self.should_authenticate));
    }
}

/// Serverbound encryption response (packet ID 0x01 in LOGIN state).
#[derive(Debug, Clone)]
pub struct EncryptionResponsePacket {
    /// RSA-encrypted shared secret.
    pub shared_secret: Vec<u8>,
    /// RSA-encrypted verify token.
    pub verify_token: Vec<u8>,
}

impl Packet for EncryptionResponsePacket {
    const PACKET_ID: i32 = 0x01;

    #[allow(clippy::cast_sign_loss)]
    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
        let ss_len = varint::read_var_int(buf)? as usize;
        if buf.remaining() < ss_len {
            return Err(ProtocolError::UnexpectedEof);
        }
        let shared_secret = buf.copy_to_bytes(ss_len).to_vec();
        let vt_len = varint::read_var_int(buf)? as usize;
        if buf.remaining() < vt_len {
            return Err(ProtocolError::UnexpectedEof);
        }
        let verify_token = buf.copy_to_bytes(vt_len).to_vec();
        Ok(Self {
            shared_secret,
            verify_token,
        })
    }

    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
    fn encode(&self, buf: &mut impl BufMut) {
        varint::write_var_int(buf, self.shared_secret.len() as i32);
        buf.put_slice(&self.shared_secret);
        varint::write_var_int(buf, self.verify_token.len() as i32);
        buf.put_slice(&self.verify_token);
    }
}

/// Clientbound login success (packet ID 0x02 in LOGIN state).
#[derive(Debug, Clone)]
pub struct LoginSuccessPacket {
    /// The player's game profile.
    pub uuid: Uuid,
    /// The player's username.
    pub username: String,
    /// Profile properties.
    pub properties: Vec<ProfileProperty>,
}

impl LoginSuccessPacket {
    /// Create from a game profile.
    #[must_use]
    pub fn from_profile(profile: &GameProfile) -> Self {
        Self {
            uuid: profile.id,
            username: profile.name.clone(),
            properties: profile.properties.clone(),
        }
    }
}

impl Packet for LoginSuccessPacket {
    const PACKET_ID: i32 = 0x02;

    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
        let uuid = types::read_uuid(buf)?;
        let username = types::read_string_max(buf, 16)?;
        let properties = types::read_properties(buf)?;
        Ok(Self {
            uuid,
            username,
            properties,
        })
    }

    fn encode(&self, buf: &mut impl BufMut) {
        types::write_uuid(buf, self.uuid);
        types::write_string(buf, &self.username);
        types::write_properties(buf, &self.properties);
    }
}

/// Clientbound set compression (packet ID 0x03 in LOGIN state).
#[derive(Debug, Clone)]
pub struct SetCompressionPacket {
    /// Compression threshold in bytes. Packets larger than this will be compressed.
    /// A value of -1 disables compression.
    pub threshold: i32,
}

impl Packet for SetCompressionPacket {
    const PACKET_ID: i32 = 0x03;

    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
        let threshold = varint::read_var_int(buf)?;
        Ok(Self { threshold })
    }

    fn encode(&self, buf: &mut impl BufMut) {
        varint::write_var_int(buf, self.threshold);
    }
}

/// Clientbound login plugin message (packet ID 0x04 in LOGIN state).
/// Used by Velocity's modern forwarding protocol.
#[derive(Debug, Clone)]
pub struct LoginPluginRequestPacket {
    /// Message ID for correlating request/response.
    pub message_id: i32,
    /// Channel identifier (e.g., "`velocity:player_info`").
    pub channel: String,
    /// Plugin-specific payload data.
    pub data: Bytes,
}

impl Packet for LoginPluginRequestPacket {
    const PACKET_ID: i32 = 0x04;

    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
        let message_id = varint::read_var_int(buf)?;
        let channel = types::read_string(buf)?;
        let data = buf.copy_to_bytes(buf.remaining());
        Ok(Self {
            message_id,
            channel,
            data,
        })
    }

    fn encode(&self, buf: &mut impl BufMut) {
        varint::write_var_int(buf, self.message_id);
        types::write_string(buf, &self.channel);
        buf.put_slice(&self.data);
    }
}

/// Serverbound login plugin response (packet ID 0x02 in LOGIN state).
#[derive(Debug, Clone)]
pub struct LoginPluginResponsePacket {
    /// Message ID matching the request.
    pub message_id: i32,
    /// Whether the client understood the request.
    pub successful: bool,
    /// Response payload (only meaningful if successful).
    pub data: Bytes,
}

impl Packet for LoginPluginResponsePacket {
    const PACKET_ID: i32 = 0x02;

    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
        let message_id = varint::read_var_int(buf)?;
        if !buf.has_remaining() {
            return Err(ProtocolError::UnexpectedEof);
        }
        let successful = buf.get_u8() != 0;
        let data = if successful {
            buf.copy_to_bytes(buf.remaining())
        } else {
            Bytes::new()
        };
        Ok(Self {
            message_id,
            successful,
            data,
        })
    }

    fn encode(&self, buf: &mut impl BufMut) {
        varint::write_var_int(buf, self.message_id);
        buf.put_u8(u8::from(self.successful));
        if self.successful {
            buf.put_slice(&self.data);
        }
    }
}

/// Serverbound login acknowledged (packet ID 0x03 in LOGIN state).
/// Sent by the client after receiving `LoginSuccess`, triggers transition to CONFIG state.
#[derive(Debug, Clone)]
pub struct LoginAcknowledgedPacket;

impl Packet for LoginAcknowledgedPacket {
    const PACKET_ID: i32 = 0x03;

    fn decode(_buf: &mut impl Buf) -> Result<Self, ProtocolError> {
        Ok(Self)
    }

    fn encode(&self, _buf: &mut impl BufMut) {}
}

/// Clientbound disconnect during login (packet ID 0x00 in LOGIN state).
#[derive(Debug, Clone)]
pub struct LoginDisconnectPacket {
    /// JSON text component with the disconnect reason.
    pub reason: String,
}

impl Packet for LoginDisconnectPacket {
    const PACKET_ID: i32 = 0x00;

    fn decode(buf: &mut impl Buf) -> Result<Self, ProtocolError> {
        let reason = types::read_string(buf)?;
        Ok(Self { reason })
    }

    fn encode(&self, buf: &mut impl BufMut) {
        types::write_string(buf, &self.reason);
    }
}