kojacoord-protocol 0.1.0

Minecraft protocol types, packet (de)serialization and version registry for the Kojacoord proxy.
Documentation
use bytes::{Buf, BufMut, Bytes, BytesMut};

use crate::codec::{Decode, Encode, PacketId};
use crate::error::ProtocolError;
use crate::types::VarInt;

pub use packets::{
    ClientboundEncryptionRequest, ClientboundLoginDisconnect, ClientboundLoginSuccess,
    ClientboundSetCompression, ProfileProperty, ServerboundEncryptionResponse,
    ServerboundLoginStart,
};

mod packets {
    use super::*;

    fn encode_str(s: &str, dst: &mut BytesMut) -> Result<(), ProtocolError> {
        let bytes = s.as_bytes();
        VarInt(bytes.len() as i32).encode(dst)?;
        dst.put_slice(bytes);
        Ok(())
    }

    fn decode_str(src: &mut Bytes, ctx: &'static str) -> Result<String, ProtocolError> {
        let len = VarInt::decode(src)?.0 as usize;
        if src.remaining() < len {
            return Err(ProtocolError::Io(std::io::Error::new(
                std::io::ErrorKind::UnexpectedEof,
                format!("Missing bytes for {ctx}"),
            )));
        }
        let mut b = vec![0u8; len];
        src.copy_to_slice(&mut b);
        String::from_utf8(b).map_err(|_| {
            ProtocolError::Io(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("Invalid UTF-8 in {ctx}"),
            ))
        })
    }

    fn encode_byte_array(data: &[u8], dst: &mut BytesMut) -> Result<(), ProtocolError> {
        VarInt(data.len() as i32).encode(dst)?;
        dst.put_slice(data);
        Ok(())
    }

    fn decode_byte_array(src: &mut Bytes, ctx: &'static str) -> Result<Vec<u8>, ProtocolError> {
        let len = VarInt::decode(src)?.0 as usize;
        if src.remaining() < len {
            return Err(ProtocolError::Io(std::io::Error::new(
                std::io::ErrorKind::UnexpectedEof,
                format!("Missing bytes for {ctx}"),
            )));
        }
        let mut b = vec![0u8; len];
        src.copy_to_slice(&mut b);
        Ok(b)
    }

    #[derive(Debug, Clone, PartialEq)]
    pub struct ServerboundLoginStart {
        pub username: String,
    }

    impl PacketId for ServerboundLoginStart {
        fn packet_id(_ver: u32) -> u8 {
            0x00
        }
    }

    impl Encode for ServerboundLoginStart {
        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
            encode_str(&self.username, dst)
        }
    }

    impl Decode for ServerboundLoginStart {
        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
            let username = decode_str(src, "ServerboundLoginStart username")?;
            Ok(Self { username })
        }
    }

    #[derive(Debug, Clone, PartialEq)]
    pub struct ClientboundLoginDisconnect {
        pub reason: String,
    }

    impl PacketId for ClientboundLoginDisconnect {
        fn packet_id(_ver: u32) -> u8 {
            0x00
        }
    }

    impl Encode for ClientboundLoginDisconnect {
        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
            encode_str(&self.reason, dst)
        }
    }

    impl Decode for ClientboundLoginDisconnect {
        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
            let reason = decode_str(src, "ClientboundLoginDisconnect reason")?;
            Ok(Self { reason })
        }
    }

    #[derive(Debug, Clone, PartialEq)]
    pub struct ClientboundEncryptionRequest {
        pub server_id: String,
        pub public_key: Vec<u8>,
        pub verify_token: Vec<u8>,
    }

    impl PacketId for ClientboundEncryptionRequest {
        fn packet_id(_ver: u32) -> u8 {
            0x01
        }
    }

    impl Encode for ClientboundEncryptionRequest {
        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
            encode_str(&self.server_id, dst)?;
            encode_byte_array(&self.public_key, dst)?;
            encode_byte_array(&self.verify_token, dst)
        }
    }

    impl Decode for ClientboundEncryptionRequest {
        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
            let server_id = decode_str(src, "ClientboundEncryptionRequest server_id")?;
            let public_key = decode_byte_array(src, "ClientboundEncryptionRequest public_key")?;
            let verify_token = decode_byte_array(src, "ClientboundEncryptionRequest verify_token")?;
            Ok(Self {
                server_id,
                public_key,
                verify_token,
            })
        }
    }

    #[derive(Debug, Clone, PartialEq)]
    pub struct ProfileProperty {
        pub name: String,
        pub value: String,
        pub signature: Option<String>,
    }

    impl Encode for ProfileProperty {
        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
            encode_str(&self.name, dst)?;
            encode_str(&self.value, dst)?;
            match &self.signature {
                Some(sig) => {
                    dst.put_u8(1);
                    encode_str(sig, dst)?;
                },
                None => dst.put_u8(0),
            }
            Ok(())
        }
    }

    impl Decode for ProfileProperty {
        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
            let name = decode_str(src, "ProfileProperty name")?;
            let value = decode_str(src, "ProfileProperty value")?;
            if src.remaining() < 1 {
                return Err(ProtocolError::Io(std::io::Error::new(
                    std::io::ErrorKind::UnexpectedEof,
                    "Missing signature flag in ProfileProperty",
                )));
            }
            let signature = if src.get_u8() != 0 {
                Some(decode_str(src, "ProfileProperty signature")?)
            } else {
                None
            };
            Ok(Self {
                name,
                value,
                signature,
            })
        }
    }

    #[derive(Debug, Clone, PartialEq)]
    pub struct ClientboundLoginSuccess {
        pub uuid: uuid::Uuid,
        pub username: String,
    }

    impl PacketId for ClientboundLoginSuccess {
        fn packet_id(_ver: u32) -> u8 {
            0x02
        }
    }

    impl Encode for ClientboundLoginSuccess {
        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
            encode_str(&self.uuid.hyphenated().to_string(), dst)?;
            encode_str(&self.username, dst)?;
            Ok(())
        }
    }

    impl Decode for ClientboundLoginSuccess {
        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
            let uuid_str = decode_str(src, "ClientboundLoginSuccess uuid")?;
            let uuid = uuid::Uuid::parse_str(&uuid_str).map_err(|_| {
                ProtocolError::Io(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Invalid UUID in ClientboundLoginSuccess",
                ))
            })?;
            let username = decode_str(src, "ClientboundLoginSuccess username")?;

            Ok(Self { uuid, username })
        }
    }

    #[derive(Debug, Clone, PartialEq)]
    pub struct ClientboundSetCompression {
        pub threshold: VarInt,
    }

    impl PacketId for ClientboundSetCompression {
        fn packet_id(_ver: u32) -> u8 {
            0x03
        }
    }

    impl Encode for ClientboundSetCompression {
        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
            self.threshold.encode(dst)
        }
    }

    impl Decode for ClientboundSetCompression {
        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
            let threshold = VarInt::decode(src)?;
            Ok(Self { threshold })
        }
    }

    #[derive(Debug, Clone, PartialEq)]
    pub struct ServerboundEncryptionResponse {
        pub shared_secret: Vec<u8>,
        pub verify_token: Vec<u8>,
    }

    impl PacketId for ServerboundEncryptionResponse {
        fn packet_id(_ver: u32) -> u8 {
            0x01
        }
    }

    impl Encode for ServerboundEncryptionResponse {
        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
            encode_byte_array(&self.shared_secret, dst)?;
            encode_byte_array(&self.verify_token, dst)
        }
    }

    impl Decode for ServerboundEncryptionResponse {
        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
            let shared_secret =
                decode_byte_array(src, "ServerboundEncryptionResponse shared_secret")?;
            let verify_token =
                decode_byte_array(src, "ServerboundEncryptionResponse verify_token")?;
            Ok(Self {
                shared_secret,
                verify_token,
            })
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn rt_login_start(p: ServerboundLoginStart) -> ServerboundLoginStart {
        let mut buf = BytesMut::new();
        p.encode(&mut buf).unwrap();
        let mut b = buf.freeze();
        ServerboundLoginStart::decode(&mut b).unwrap()
    }

    #[test]
    fn login_start_roundtrip() {
        let p = ServerboundLoginStart {
            username: "Notch".to_string(),
        };
        assert_eq!(rt_login_start(p.clone()), p);
    }

    #[test]
    fn login_disconnect_roundtrip() {
        let p = ClientboundLoginDisconnect {
            reason: r#"{"text":"banned"}"#.to_string(),
        };
        let mut buf = BytesMut::new();
        p.encode(&mut buf).unwrap();
        let mut b = buf.freeze();
        assert_eq!(ClientboundLoginDisconnect::decode(&mut b).unwrap(), p);
    }

    #[test]
    fn encryption_request_roundtrip() {
        let p = ClientboundEncryptionRequest {
            server_id: String::new(),
            public_key: vec![1, 2, 3, 4],
            verify_token: vec![5, 6, 7, 8],
        };
        let mut buf = BytesMut::new();
        p.encode(&mut buf).unwrap();
        let mut b = buf.freeze();
        assert_eq!(ClientboundEncryptionRequest::decode(&mut b).unwrap(), p);
    }

    #[test]
    fn encryption_response_roundtrip() {
        let p = ServerboundEncryptionResponse {
            shared_secret: vec![0xAA; 128],
            verify_token: vec![0xBB; 128],
        };
        let mut buf = BytesMut::new();
        p.encode(&mut buf).unwrap();
        let mut b = buf.freeze();
        assert_eq!(ServerboundEncryptionResponse::decode(&mut b).unwrap(), p);
    }

    #[test]
    fn login_success_roundtrip() {
        let p = ClientboundLoginSuccess {
            uuid: uuid::Uuid::new_v4(),
            username: "Notch".to_string(),
        };
        let mut buf = BytesMut::new();
        p.encode(&mut buf).unwrap();
        let mut b = buf.freeze();
        let decoded = ClientboundLoginSuccess::decode(&mut b).unwrap();
        assert_eq!(decoded.uuid, p.uuid);
        assert_eq!(decoded.username, p.username);
    }

    #[test]
    fn set_compression_roundtrip() {
        let p = ClientboundSetCompression {
            threshold: VarInt(256),
        };
        let mut buf = BytesMut::new();
        p.encode(&mut buf).unwrap();
        let mut b = buf.freeze();
        assert_eq!(ClientboundSetCompression::decode(&mut b).unwrap(), p);
    }
}