use bytes::{Buf, BufMut, Bytes};
use uuid::Uuid;
use crate::types::{self, GameProfile, ProfileProperty, ProtocolError};
use crate::varint;
use super::Packet;
const MAX_ENCRYPTED_FIELD: usize = 256;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoginStartPacket {
pub username: String,
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);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptionRequestPacket {
pub server_id: String,
pub public_key: Vec<u8>,
pub verify_token: Vec<u8>,
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 pk_len > MAX_ENCRYPTED_FIELD {
return Err(ProtocolError::ByteArrayTooLong {
length: pk_len,
max: MAX_ENCRYPTED_FIELD,
});
}
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 vt_len > MAX_ENCRYPTED_FIELD {
return Err(ProtocolError::ByteArrayTooLong {
length: vt_len,
max: MAX_ENCRYPTED_FIELD,
});
}
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));
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptionResponsePacket {
pub shared_secret: Vec<u8>,
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 ss_len > MAX_ENCRYPTED_FIELD {
return Err(ProtocolError::ByteArrayTooLong {
length: ss_len,
max: MAX_ENCRYPTED_FIELD,
});
}
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 vt_len > MAX_ENCRYPTED_FIELD {
return Err(ProtocolError::ByteArrayTooLong {
length: vt_len,
max: MAX_ENCRYPTED_FIELD,
});
}
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);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoginSuccessPacket {
pub uuid: Uuid,
pub username: String,
pub properties: Vec<ProfileProperty>,
}
impl LoginSuccessPacket {
#[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);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SetCompressionPacket {
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);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoginPluginRequestPacket {
pub message_id: i32,
pub channel: String,
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);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoginPluginResponsePacket {
pub message_id: i32,
pub successful: bool,
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);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
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) {}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoginDisconnectPacket {
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);
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn profile_property_strategy() -> impl Strategy<Value = ProfileProperty> {
(
".{0,32}", ".{0,1024}", prop::option::weighted(0.5, ".{0,1024}"), )
.prop_map(|(name, value, signature)| ProfileProperty {
name,
value,
signature,
})
}
proptest! {
#[test]
fn login_start_roundtrip(
username in ".{0,16}",
u in any::<u128>()
) {
let packet = LoginStartPacket {
username,
uuid: Uuid::from_u128(u),
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let decoded = LoginStartPacket::decode(&mut &buf[..]).unwrap();
prop_assert_eq!(decoded, packet);
}
#[test]
fn encryption_request_roundtrip(
server_id in ".{0,20}",
public_key in prop::collection::vec(any::<u8>(), 0..=MAX_ENCRYPTED_FIELD),
verify_token in prop::collection::vec(any::<u8>(), 0..=MAX_ENCRYPTED_FIELD),
should_authenticate in any::<bool>()
) {
let packet = EncryptionRequestPacket {
server_id,
public_key,
verify_token,
should_authenticate,
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let decoded = EncryptionRequestPacket::decode(&mut &buf[..]).unwrap();
prop_assert_eq!(decoded, packet);
}
#[test]
fn encryption_response_roundtrip(
shared_secret in prop::collection::vec(any::<u8>(), 0..=MAX_ENCRYPTED_FIELD),
verify_token in prop::collection::vec(any::<u8>(), 0..=MAX_ENCRYPTED_FIELD)
) {
let packet = EncryptionResponsePacket {
shared_secret,
verify_token,
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let decoded = EncryptionResponsePacket::decode(&mut &buf[..]).unwrap();
prop_assert_eq!(decoded, packet);
}
#[test]
fn login_success_roundtrip(
u in any::<u128>(),
username in ".{0,16}",
properties in prop::collection::vec(profile_property_strategy(), 0..4)
) {
let packet = LoginSuccessPacket {
uuid: Uuid::from_u128(u),
username,
properties,
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let decoded = LoginSuccessPacket::decode(&mut &buf[..]).unwrap();
prop_assert_eq!(decoded, packet);
}
#[test]
fn set_compression_roundtrip(threshold in any::<i32>()) {
let packet = SetCompressionPacket { threshold };
let mut buf = Vec::new();
packet.encode(&mut buf);
let decoded = SetCompressionPacket::decode(&mut &buf[..]).unwrap();
prop_assert_eq!(decoded, packet);
}
#[test]
fn login_plugin_request_roundtrip(
message_id in any::<i32>(),
channel in ".{0,128}",
data in prop::collection::vec(any::<u8>(), 0..1024)
) {
let packet = LoginPluginRequestPacket {
message_id,
channel,
data: Bytes::from(data),
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let decoded = LoginPluginRequestPacket::decode(&mut &buf[..]).unwrap();
prop_assert_eq!(decoded, packet);
}
#[test]
fn login_plugin_response_roundtrip(
message_id in any::<i32>(),
successful in any::<bool>(),
data in prop::collection::vec(any::<u8>(), 0..1024)
) {
let packet = LoginPluginResponsePacket {
message_id,
successful,
data: if successful { Bytes::from(data) } else { Bytes::new() },
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let decoded = LoginPluginResponsePacket::decode(&mut &buf[..]).unwrap();
prop_assert_eq!(decoded, packet);
}
#[test]
fn login_acknowledged_roundtrip(packet in Just(LoginAcknowledgedPacket)) {
let mut buf = Vec::new();
packet.encode(&mut buf);
let decoded = LoginAcknowledgedPacket::decode(&mut &buf[..]).unwrap();
prop_assert_eq!(decoded, packet);
}
#[test]
fn login_disconnect_roundtrip(reason in ".{0,1024}") {
let packet = LoginDisconnectPacket { reason };
let mut buf = Vec::new();
packet.encode(&mut buf);
let decoded = LoginDisconnectPacket::decode(&mut &buf[..]).unwrap();
prop_assert_eq!(decoded, packet);
}
}
#[test]
fn encryption_request_rejects_oversized_public_key() {
let packet = EncryptionRequestPacket {
server_id: String::new(),
public_key: vec![0xAA; MAX_ENCRYPTED_FIELD + 1],
verify_token: vec![0xBB; 4],
should_authenticate: true,
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let result = EncryptionRequestPacket::decode(&mut &buf[..]);
assert!(matches!(
result,
Err(ProtocolError::ByteArrayTooLong {
length,
max: MAX_ENCRYPTED_FIELD,
}) if length == MAX_ENCRYPTED_FIELD + 1
));
}
#[test]
fn encryption_request_rejects_oversized_verify_token() {
let packet = EncryptionRequestPacket {
server_id: String::new(),
public_key: vec![0xAA; 128],
verify_token: vec![0xBB; MAX_ENCRYPTED_FIELD + 1],
should_authenticate: true,
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let result = EncryptionRequestPacket::decode(&mut &buf[..]);
assert!(matches!(
result,
Err(ProtocolError::ByteArrayTooLong {
length,
max: MAX_ENCRYPTED_FIELD,
}) if length == MAX_ENCRYPTED_FIELD + 1
));
}
#[test]
fn encryption_response_rejects_oversized_shared_secret() {
let packet = EncryptionResponsePacket {
shared_secret: vec![0xAA; MAX_ENCRYPTED_FIELD + 1],
verify_token: vec![0xBB; 4],
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let result = EncryptionResponsePacket::decode(&mut &buf[..]);
assert!(matches!(
result,
Err(ProtocolError::ByteArrayTooLong {
length,
max: MAX_ENCRYPTED_FIELD,
}) if length == MAX_ENCRYPTED_FIELD + 1
));
}
#[test]
fn encryption_response_rejects_oversized_verify_token() {
let packet = EncryptionResponsePacket {
shared_secret: vec![0xAA; 128],
verify_token: vec![0xBB; MAX_ENCRYPTED_FIELD + 1],
};
let mut buf = Vec::new();
packet.encode(&mut buf);
let result = EncryptionResponsePacket::decode(&mut &buf[..]);
assert!(matches!(
result,
Err(ProtocolError::ByteArrayTooLong {
length,
max: MAX_ENCRYPTED_FIELD,
}) if length == MAX_ENCRYPTED_FIELD + 1
));
}
}