fix: mob pathfinding jump

This commit is contained in:
Alexander Medvedev 2026-05-05 20:29:55 +02:00
parent 724bd32619
commit 34644a3f02
20 changed files with 445 additions and 347 deletions

View File

@ -34,7 +34,12 @@ impl PacketWrite for CLevelChunk<'_> {
let mut chunk_data = Vec::new();
let data_write = &mut chunk_data;
let block_sections = self.chunk.section.block_sections.read().unwrap();
let block_sections = self
.chunk
.section
.block_sections
.read()
.map_err(|_| Error::other("block_sections read lock poisoned"))?;
let min_y_section = (self.chunk.section.min_y >> 4) as i8;
for (i, block_palette) in block_sections.iter().enumerate() {

View File

@ -23,7 +23,7 @@ impl<R: AsyncRead + Unpin> DecryptionReader<R> {
pub fn upgrade(self, cipher: Aes128Cfb8Dec) -> Self {
match self {
Self::None(stream) => Self::Decrypt(Box::new(StreamDecryptor::new(cipher, stream))),
Self::Decrypt(_) => panic!("Cannot upgrade a stream that already has a cipher!"),
Self::Decrypt(_) => self,
}
}
}
@ -61,6 +61,12 @@ impl Default for UDPNetworkDecoder {
}
}
use thiserror::Error;
#[derive(Debug, Error)]
#[error("Encryption already enabled")]
pub struct EncryptionAlreadyEnabledError;
impl UDPNetworkDecoder {
#[must_use]
pub const fn new() -> Self {
@ -72,12 +78,16 @@ impl UDPNetworkDecoder {
}
/// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
pub const fn set_encryption(&mut self, _key: &[u8; 16]) {
pub const fn set_encryption(
&mut self,
_key: &[u8; 16],
) -> Result<(), EncryptionAlreadyEnabledError> {
// if matches!(self.reader, DecryptionReader::Decrypt(_)) {
// panic!("Cannot upgrade a stream that already has a cipher!");
// return Err(EncryptionAlreadyEnabledError);
// }
// let cipher = Aes128Cfb8Dec::new_from_slices(key, key).expect("invalid key");
// let cipher = Aes128Cfb8Dec::new_from_slices(key, key).map_err(|_| ());
// take_mut::take(&mut self.reader, |decoder| decoder.upgrade(cipher));
Ok(())
}
pub async fn get_packet_payload(

View File

@ -103,12 +103,16 @@ impl UDPNetworkEncoder {
}
/// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
pub const fn set_encryption(&mut self, _key: &[u8; 16]) {
pub const fn set_encryption(
&mut self,
_key: &[u8; 16],
) -> Result<(), crate::bedrock::packet_decoder::EncryptionAlreadyEnabledError> {
// if matches!(self.writer, EncryptionWriter::Encrypt(_)) {
// panic!("Cannot upgrade a stream that already has a cipher!");
// return Err(crate::bedrock::packet_decoder::EncryptionAlreadyEnabledError);
// }
// let cipher = Aes128Cfb8Enc::new_from_slices(key, key).expect("invalid key");
// let cipher = Aes128Cfb8Enc::new_from_slices(key, key).map_err(|_| ());
// take_mut::take(&mut self.writer, |encoder| encoder.upgrade(cipher));
Ok(())
}
pub fn write_game_packet(
@ -132,20 +136,26 @@ impl UDPNetworkEncoder {
inner_buffer
.write_var_uint(&VarUInt(total_content_length))
.unwrap();
inner_buffer.write_var_uint(&header_varint).unwrap();
.map_err(|_| Error::other("Failed to write total content length"))?;
inner_buffer
.write_var_uint(&header_varint)
.map_err(|_| Error::other("Failed to write header varint"))?;
inner_buffer.write_all(packet_payload)?;
// Handle Outer Container
writer.write_u8(0xfe).unwrap(); // Bedrock Game Packet Header
writer
.write_u8(0xfe)
.map_err(|e| Error::other(e.to_string()))?; // Bedrock Game Packet Header
if let Some((_threshold, level)) = self.compression {
// Write Compression Method (0x00 for Zlib)
writer.write_u8(0x00).unwrap();
writer
.write_u8(0x00)
.map_err(|e| Error::other(e.to_string()))?;
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::new(level));
encoder.write_all(&inner_buffer).unwrap();
let compressed_data = encoder.finish().unwrap();
encoder.write_all(&inner_buffer)?;
let compressed_data = encoder.finish()?;
writer.write_all(&compressed_data)?;
} else {
@ -172,7 +182,7 @@ mod tests {
use std::io::Cursor;
#[tokio::test]
async fn bedrock_compression_cycle() {
async fn bedrock_compression_cycle() -> Result<(), Box<dyn std::error::Error>> {
let mut encoder = UDPNetworkEncoder::new();
encoder.set_compression((256, 6));
@ -180,24 +190,23 @@ mod tests {
let payload = b"Hello Bedrock Compression!";
let mut encoded_buf = Vec::new();
encoder
.write_game_packet(
packet_id,
SubClient::Main,
SubClient::Main,
payload,
&mut encoded_buf,
)
.unwrap();
encoder.write_game_packet(
packet_id,
SubClient::Main,
SubClient::Main,
payload,
&mut encoded_buf,
)?;
let mut decoder = UDPNetworkDecoder::new();
decoder.set_compression(256);
let decompressed_payload = decoder.get_packet_payload(encoded_buf).await.unwrap();
let decompressed_payload = decoder.get_packet_payload(encoded_buf).await?;
let mut cursor = Cursor::new(decompressed_payload);
let raw_packet = decoder.get_game_packet(&mut cursor).unwrap();
let raw_packet = decoder.get_game_packet(&mut cursor)?;
assert_eq!(raw_packet.id, packet_id as i32);
assert_eq!(raw_packet.payload.as_ref(), payload);
Ok(())
}
}

View File

@ -31,15 +31,16 @@ pub fn data_to_proto_sound(id_or: &IdOr<SoundEvent>) -> crate::IdOr<crate::Sound
}
#[must_use]
pub fn proto_to_data_sound(id_or: &crate::IdOr<crate::SoundEvent>) -> IdOr<SoundEvent> {
pub fn proto_to_data_sound(id_or: &crate::IdOr<crate::SoundEvent>) -> Option<IdOr<SoundEvent>> {
match id_or {
crate::IdOr::Id(id) => {
IdOr::Id(Sound::from_name(Sound::NAMES[*id as usize]).expect("Invalid sound id"))
let name = Sound::NAMES.get(*id as usize)?;
Some(IdOr::Id(Sound::from_name(name)?))
}
crate::IdOr::Value(sound) => IdOr::Value(SoundEvent {
crate::IdOr::Value(sound) => Some(IdOr::Value(SoundEvent {
sound_name: sound.sound_name.clone(),
range: sound.range,
}),
})),
}
}
@ -168,14 +169,12 @@ fn serialize_status_effects<T: SerializeStruct>(
seq.serialize_field::<VarInt>("", &VarInt(effects.len() as i32))?;
for effect in effects {
seq.serialize_field::<VarInt>(
"",
&VarInt(
StatusEffect::from_minecraft_name(&effect.effect_id)
.unwrap()
.registry_id() as i32,
),
)?;
let effect_id = StatusEffect::from_minecraft_name(&effect.effect_id)
.ok_or_else(|| {
serde::ser::Error::custom(format!("Invalid status effect: {}", effect.effect_id))
})?
.registry_id();
seq.serialize_field::<VarInt>("", &VarInt(effect_id as i32))?;
// Effect parameters
seq.serialize_field::<VarInt>("", &VarInt::from(effect.amplifier))?;
seq.serialize_field::<VarInt>("", &VarInt::from(effect.duration))?;
@ -222,9 +221,10 @@ fn deserialize_consume_effect<'a, A: SeqAccess<'a>>(
.ok_or(de::Error::custom(
"No sound IdOr<SoundEvent> in ConsumeEffect",
))?;
Ok(ConsumeEffect::PlaySound(proto_to_data_sound(
&proto_sound_event,
)))
Ok(ConsumeEffect::PlaySound(
proto_to_data_sound(&proto_sound_event)
.ok_or(de::Error::custom("Invalid sound in ConsumeEffect"))?,
))
}
_ => Err(de::Error::custom("Invalid effect_type in ConsumeEffect")),
}
@ -399,7 +399,8 @@ impl DataComponentCodec<Self> for ConsumableImpl {
"No ConsumableImpl consume_particles bool!",
))?;
let sound_event = proto_to_data_sound(&proto_sound_event);
let sound_event = proto_to_data_sound(&proto_sound_event)
.ok_or(de::Error::custom("Invalid sound in ConsumableImpl"))?;
let effects_len = seq
.next_element::<VarInt>()?
.ok_or(de::Error::custom("No array_len VarInt in ConsumableImpl"))?
@ -461,7 +462,8 @@ impl DataComponentCodec<Self> for EquippableImpl {
.ok_or(de::Error::custom(
"No EquippableImpl equip_sound IdOr<SoundEvent>!",
))?,
);
)
.ok_or(de::Error::custom("Invalid sound in EquippableImpl"))?;
let asset_id =
seq.next_element::<Option<Cow<'static, str>>>()?
.ok_or(de::Error::custom(
@ -502,7 +504,10 @@ impl DataComponentCodec<Self> for EquippableImpl {
.ok_or(de::Error::custom(
"No EquippableImpl shearing_sound IdOr<SoundEvent>!",
))?,
);
)
.ok_or(de::Error::custom(
"Invalid shearing sound in EquippableImpl",
))?;
Ok(Self {
slot,

View File

@ -50,12 +50,12 @@ impl LpVector3d {
// Write low 16 bits (Little Endian)
writer
.write_all(&(packed_data as u16).to_le_bytes())
.unwrap();
.map_err(WritingError::IoError)?;
// Write next 32 bits (Big Endian)
writer
.write_all(&((packed_data >> 16) as i32).to_be_bytes())
.unwrap();
.map_err(WritingError::IoError)?;
if is_extended {
let scale_tail = VarInt((scale_factor >> 2) as i32);
@ -156,7 +156,8 @@ fn from_long(quantized: i64, scale: f64) -> f64 {
impl Serialize for LpVector3d {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut buf = Vec::new();
self.write(&mut buf).unwrap();
self.write(&mut buf)
.map_err(|e| serde::ser::Error::custom(e.to_string()))?;
serializer.serialize_bytes(&buf)
}
}
@ -202,10 +203,11 @@ mod tests {
}
#[test]
fn write_legacy_writes_three_i16_be_components() {
fn write_legacy_writes_three_i16_be_components() -> Result<(), Box<dyn std::error::Error>> {
let velocity = LpVector3d(Vector3::new(0.5, -0.5, 0.0));
let mut buf = Vec::new();
velocity.write_legacy(&mut buf).unwrap();
velocity.write_legacy(&mut buf)?;
assert_eq!(buf, vec![0x0F, 0xA0, 0xF0, 0x60, 0x00, 0x00]);
Ok(())
}
}

View File

@ -32,7 +32,11 @@ impl ClientPacket for CChunkData<'_> {
// Chunk Z
write.write_i32_be(self.0.z)?;
let heightmaps = self.0.heightmap.lock().unwrap();
let heightmaps = self
.0
.heightmap
.lock()
.map_err(|_| WritingError::Message("heightmap lock poisoned".into()))?;
if version <= &MinecraftVersion::V_1_21_4 {
pumpkin_nbt::serializer::to_bytes_unnamed(&*heightmaps, &mut write)
.map_err(|err| WritingError::Serde(err.to_string()))?;
@ -62,8 +66,14 @@ impl ClientPacket for CChunkData<'_> {
{
let mut blocks_and_biomes_buf = Vec::new();
let block_sections = self.0.section.block_sections.read().unwrap();
let biome_sections = self.0.section.biome_sections.read().unwrap();
let block_sections =
self.0.section.block_sections.read().map_err(|_| {
WritingError::Message("block_sections read lock poisoned".into())
})?;
let biome_sections =
self.0.section.biome_sections.read().map_err(|_| {
WritingError::Message("biome_sections read lock poisoned".into())
})?;
for (block_palette, biome_palette) in block_sections.iter().zip(biome_sections.iter()) {
let non_empty_block_count = block_palette.non_air_block_count() as i16;
@ -183,7 +193,11 @@ impl ClientPacket for CChunkData<'_> {
write.write_slice(&blocks_and_biomes_buf)?;
};
let block_entities = self.0.block_entities.lock().unwrap();
let block_entities = self
.0
.block_entities
.lock()
.map_err(|_| WritingError::Message("block_entities lock poisoned".into()))?;
write.write_var_int(&VarInt(block_entities.len() as i32))?;
for block_entity in block_entities.values() {
let pos = block_entity.get_position();
@ -203,7 +217,11 @@ impl ClientPacket for CChunkData<'_> {
{
// Light masks include sections from -1 (below world) to num_sections (above world)
// This means we need to account for 2 extra sections in the bitset
let light_engine = self.0.light_engine.lock().unwrap();
let light_engine = self
.0
.light_engine
.lock()
.map_err(|_| WritingError::Message("light_engine lock poisoned".into()))?;
let num_sections = light_engine.sky_light.len();
let mut sky_light_empty_mask = 0u64;
@ -237,19 +255,15 @@ impl ClientPacket for CChunkData<'_> {
block_light_empty_mask |= 1 << (num_sections + 1);
// Write Sky Light Mask
write.write_bitset(&BitSet(Box::new([sky_light_mask.try_into().unwrap()])))?;
write.write_bitset(&BitSet(Box::new([sky_light_mask as i64])))?;
// Write Block Light Mask
write.write_bitset(&BitSet(Box::new([block_light_mask.try_into().unwrap()])))?;
write.write_bitset(&BitSet(Box::new([block_light_mask as i64])))?;
// Write Empty Sky Light Mask
write.write_bitset(&BitSet(Box::new([sky_light_empty_mask
.try_into()
.unwrap()])))?;
write.write_bitset(&BitSet(Box::new([sky_light_empty_mask as i64])))?;
// Write Empty Block Light Mask
write.write_bitset(&BitSet(Box::new([block_light_empty_mask
.try_into()
.unwrap()])))?;
write.write_bitset(&BitSet(Box::new([block_light_empty_mask as i64])))?;
let light_data_size: VarInt = LightContainer::ARRAY_SIZE.try_into().unwrap();
let light_data_size: VarInt = VarInt(LightContainer::ARRAY_SIZE as i32);
// Write Sky Light arrays
write.write_var_int(&VarInt(sky_light_mask.count_ones() as i32))?;

View File

@ -161,7 +161,9 @@ impl ProtoNode<'_> {
..
} => {
// suggestion type
let suggestion_type = &override_suggestion_type.expect("ProtoNode::FLAG_HAS_SUGGESTION_TYPE should only be set if override_suggestion_type is not `None`.");
let suggestion_type = override_suggestion_type.as_ref().ok_or_else(|| {
WritingError::Message("ProtoNode::FLAG_HAS_SUGGESTION_TYPE set but override_suggestion_type is None".into())
})?;
write.write_string(suggestion_type.resource_location())?;
}
_ => unimplemented!(

View File

@ -61,14 +61,14 @@ mod tests {
codec::lp_vector_3d::{LpVector3d, encode_legacy_velocity_component},
};
fn encode_packet(version: MinecraftVersion) -> Vec<u8> {
fn encode_packet(version: MinecraftVersion) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let packet = CEntityVelocity::new(
VarInt(1),
pumpkin_util::math::vector3::Vector3::new(0.5, -0.5, 0.25),
);
let mut out = Vec::new();
packet.write_packet_data(&mut out, &version).unwrap();
out
packet.write_packet_data(&mut out, &version)?;
Ok(out)
}
fn legacy_bytes(x: f64, y: f64, z: f64) -> Vec<u8> {
@ -80,26 +80,28 @@ mod tests {
}
#[test]
fn entity_velocity_uses_legacy_format_for_1_21_8() {
fn entity_velocity_uses_legacy_format_for_1_21_8() -> Result<(), Box<dyn std::error::Error>> {
// V_1_21_7 enum variant represents protocol 772 (used by 1.21.7 and 1.21.8).
let encoded = encode_packet(MinecraftVersion::V_1_21_7);
let encoded = encode_packet(MinecraftVersion::V_1_21_7)?;
let expected_legacy_tail = legacy_bytes(0.5, -0.5, 0.25);
assert_eq!(encoded, [&[1], expected_legacy_tail.as_slice()].concat());
Ok(())
}
#[test]
fn entity_velocity_uses_packed_format_for_1_21_9() {
let encoded = encode_packet(MinecraftVersion::V_1_21_9);
fn entity_velocity_uses_packed_format_for_1_21_9() -> Result<(), Box<dyn std::error::Error>> {
let encoded = encode_packet(MinecraftVersion::V_1_21_9)?;
let legacy_like = legacy_bytes(0.5, -0.5, 0.25);
assert_ne!(&encoded[1..], legacy_like.as_slice());
// Ensure the packed bytes can still be decoded back to a velocity.
let mut cursor = std::io::Cursor::new(&encoded[1..]);
let decoded = LpVector3d::read(&mut cursor).unwrap();
let decoded = LpVector3d::read(&mut cursor)?;
assert!(decoded.0.x.is_finite());
assert!(decoded.0.y.is_finite());
assert!(decoded.0.z.is_finite());
Ok(())
}
}

View File

@ -30,7 +30,11 @@ impl ClientPacket for CLightUpdate<'_> {
// Light masks include sections from -1 (below world) to num_sections (above world)
// This means we need to account for 2 extra sections in the bitset
let light_engine = self.0.light_engine.lock().unwrap();
let light_engine = self
.0
.light_engine
.lock()
.map_err(|_| WritingError::Message("light_engine lock poisoned".into()))?;
let num_sections = light_engine.sky_light.len();
let mut sky_light_empty_mask = 0u64;
@ -56,19 +60,15 @@ impl ClientPacket for CLightUpdate<'_> {
}
// Write Sky Light Mask
write.write_bitset(&BitSet(Box::new([sky_light_mask.try_into().unwrap()])))?;
write.write_bitset(&BitSet(Box::new([sky_light_mask as i64])))?;
// Write Block Light Mask
write.write_bitset(&BitSet(Box::new([block_light_mask.try_into().unwrap()])))?;
write.write_bitset(&BitSet(Box::new([block_light_mask as i64])))?;
// Write Empty Sky Light Mask
write.write_bitset(&BitSet(Box::new([sky_light_empty_mask
.try_into()
.unwrap()])))?;
write.write_bitset(&BitSet(Box::new([sky_light_empty_mask as i64])))?;
// Write Empty Block Light Mask
write.write_bitset(&BitSet(Box::new([block_light_empty_mask
.try_into()
.unwrap()])))?;
write.write_bitset(&BitSet(Box::new([block_light_empty_mask as i64])))?;
let light_data_size: VarInt = LightContainer::ARRAY_SIZE.try_into().unwrap();
let light_data_size: VarInt = VarInt(LightContainer::ARRAY_SIZE as i32);
// Write Sky Light arrays
write.write_var_int(&VarInt(sky_light_mask.count_ones() as i32))?;

View File

@ -108,7 +108,8 @@ fn write_item_stack_slot_display(
version: MinecraftVersion,
) -> Result<(), WritingError> {
write.write_var_int(&VarInt(slot_display_item_stack_type(version)))?;
let static_item = Item::from_id(item.id).expect("item id must exist");
let static_item = Item::from_id(item.id)
.ok_or_else(|| WritingError::Message(format!("item id {} must exist", item.id)))?;
ItemStackSerializer::from(ItemStack::new(count, static_item))
.write_with_version(write, &version)
}
@ -247,7 +248,10 @@ fn write_result_slot_display(
}
fn write_optional_var_int(write: &mut impl Write, value: Option<i32>) -> Result<(), WritingError> {
let encoded = value.map_or(0, |v| v.checked_add(1).expect("group id overflow"));
let encoded = value.map_or(Ok(0), |v| {
v.checked_add(1)
.ok_or_else(|| WritingError::Message(format!("group id {v} overflow")))
})?;
write.write_var_int(&VarInt(encoded))?;
Ok(())
}
@ -468,13 +472,16 @@ impl ClientPacket for CRecipeBookAdd {
let mut write = write;
// Station items (these IDs are stable across all versions we support)
let crafting_table =
Item::from_registry_key("crafting_table").expect("crafting_table item must exist");
let furnace = Item::from_registry_key("furnace").expect("furnace item must exist");
let blast_furnace =
Item::from_registry_key("blast_furnace").expect("blast_furnace item must exist");
let smoker = Item::from_registry_key("smoker").expect("smoker item must exist");
let campfire = Item::from_registry_key("campfire").expect("campfire item must exist");
let crafting_table = Item::from_registry_key("crafting_table")
.ok_or_else(|| WritingError::Message("crafting_table item must exist".into()))?;
let furnace = Item::from_registry_key("furnace")
.ok_or_else(|| WritingError::Message("furnace item must exist".into()))?;
let blast_furnace = Item::from_registry_key("blast_furnace")
.ok_or_else(|| WritingError::Message("blast_furnace item must exist".into()))?;
let smoker = Item::from_registry_key("smoker")
.ok_or_else(|| WritingError::Message("smoker item must exist".into()))?;
let campfire = Item::from_registry_key("campfire")
.ok_or_else(|| WritingError::Message("campfire item must exist".into()))?;
// First pass - count and skip CraftingSpecial and CraftingDecoratedPot entries.
let crafting_count: usize = RECIPES_CRAFTING

View File

@ -113,7 +113,7 @@ mod tests {
[xb[0], xb[1], yb[0], yb[1], zb[0], zb[1]]
}
fn encode_spawn(version: MinecraftVersion) -> Vec<u8> {
fn encode_spawn(version: MinecraftVersion) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let velocity = pumpkin_util::math::vector3::Vector3::new(0.5, -0.5, 0.25);
let packet = CSpawnEntity::new(
VarInt(1),
@ -127,26 +127,30 @@ mod tests {
velocity,
);
let mut out = Vec::new();
packet.write_packet_data(&mut out, &version).unwrap();
out
packet.write_packet_data(&mut out, &version)?;
Ok(out)
}
#[test]
fn spawn_entity_uses_legacy_velocity_tail_for_1_21_8() {
fn spawn_entity_uses_legacy_velocity_tail_for_1_21_8() -> Result<(), Box<dyn std::error::Error>>
{
// V_1_21_7 enum variant represents protocol 772 (used by 1.21.7 and 1.21.8).
let velocity = pumpkin_util::math::vector3::Vector3::new(0.5, -0.5, 0.25);
let expected_tail = legacy_tail(velocity);
let encoded = encode_spawn(MinecraftVersion::V_1_21_7);
let encoded = encode_spawn(MinecraftVersion::V_1_21_7)?;
assert!(encoded.ends_with(&expected_tail));
Ok(())
}
#[test]
fn spawn_entity_does_not_use_legacy_velocity_tail_for_1_21_9() {
fn spawn_entity_does_not_use_legacy_velocity_tail_for_1_21_9()
-> Result<(), Box<dyn std::error::Error>> {
let velocity = pumpkin_util::math::vector3::Vector3::new(0.5, -0.5, 0.25);
let expected_tail = legacy_tail(velocity);
let encoded = encode_spawn(MinecraftVersion::V_1_21_9);
let encoded = encode_spawn(MinecraftVersion::V_1_21_9)?;
assert!(!encoded.ends_with(&expected_tail));
Ok(())
}
}

View File

@ -45,7 +45,7 @@ impl<R: AsyncRead + Unpin> DecryptionReader<R> {
pub fn upgrade(self, cipher: Aes128Cfb8Dec) -> Self {
match self {
Self::None(stream) => Self::Decrypt(Box::new(StreamDecryptor::new(cipher, stream))),
Self::Decrypt(_) => panic!("Cannot upgrade a stream that already has a cipher!"),
Self::Decrypt(_) => self,
}
}
}
@ -93,12 +93,16 @@ impl<R: AsyncRead + Unpin> TCPNetworkDecoder<R> {
}
/// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
pub fn set_encryption(&mut self, key: &[u8; 16]) {
pub fn set_encryption(&mut self, key: &[u8; 16]) -> Result<(), PacketDecodeError> {
if matches!(self.reader, DecryptionReader::Decrypt(_)) {
panic!("Cannot upgrade a stream that already has a cipher!");
return Err(PacketDecodeError::Message(
"Encryption already enabled".into(),
));
}
let cipher = Aes128Cfb8Dec::new_from_slices(key, key).expect("invalid key");
let cipher = Aes128Cfb8Dec::new_from_slices(key, key)
.map_err(|_| PacketDecodeError::Message("Invalid key".into()))?;
take_mut::take(&mut self.reader, |decoder| decoder.upgrade(cipher));
Ok(())
}
pub async fn get_raw_packet(&mut self) -> Result<RawPacket, PacketDecodeError> {
@ -202,19 +206,22 @@ mod tests {
use flate2::write::ZlibEncoder;
/// Helper function to compress data using libdeflater's Zlib compressor
fn compress_zlib(data: &[u8]) -> Vec<u8> {
fn compress_zlib(data: &[u8]) -> Result<Vec<u8>, std::io::Error> {
let mut compressed = Vec::new();
ZlibEncoder::new(&mut compressed, Compression::default())
.write_all(data)
.unwrap();
compressed
ZlibEncoder::new(&mut compressed, Compression::default()).write_all(data)?;
Ok(compressed)
}
/// Helper function to encrypt data using AES-128 CFB-8 mode
fn encrypt_aes128(data: &mut [u8], key: &[u8; 16], iv: &[u8; 16]) {
fn encrypt_aes128(
data: &mut [u8],
key: &[u8; 16],
iv: &[u8; 16],
) -> Result<(), Box<dyn std::error::Error>> {
let mut encryptor =
Cfb8Encryptor::<Aes128>::new_from_slices(key, iv).expect("Invalid key/iv");
Cfb8Encryptor::<Aes128>::new_from_slices(key, iv).map_err(|_| "Invalid key/iv")?;
encryptor.encrypt(data);
Ok(())
}
/// Helper function to build a packet with optional compression and encryption
@ -224,36 +231,34 @@ mod tests {
compress: bool,
key: Option<&[u8; 16]>,
iv: Option<&[u8; 16]>,
) -> Vec<u8> {
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut buffer = Vec::new();
if compress {
// Create a buffer that includes `packet_id_varint` and payload
let mut data_to_compress = Vec::new();
let packet_id_varint = VarInt(packet_id);
data_to_compress.write_var_int(&packet_id_varint).unwrap();
data_to_compress.write_slice(payload).unwrap();
data_to_compress.write_var_int(&packet_id_varint)?;
data_to_compress.write_slice(payload)?;
// Compress the combined data
let compressed_payload = compress_zlib(&data_to_compress);
let compressed_payload = compress_zlib(&data_to_compress)?;
let data_len = data_to_compress.len() as i32; // 1 + payload.len()
let data_len_varint = VarInt(data_len);
buffer.write_var_int(&data_len_varint).unwrap();
buffer.write_slice(&compressed_payload).unwrap();
buffer.write_var_int(&data_len_varint)?;
buffer.write_slice(&compressed_payload)?;
} else {
// No compression; `data_len` is payload length
let packet_id_varint = VarInt(packet_id);
buffer.write_var_int(&packet_id_varint).unwrap();
buffer.write_slice(payload).unwrap();
buffer.write_var_int(&packet_id_varint)?;
buffer.write_slice(payload)?;
}
// Calculate packet length: length of buffer
let packet_len = buffer.len() as i32;
let packet_len_varint = VarInt(packet_len);
let mut packet_length_encoded = Vec::new();
packet_len_varint
.encode(&mut packet_length_encoded)
.unwrap();
packet_len_varint.encode(&mut packet_length_encoded)?;
// Create a new buffer for the entire packet
let mut packet = Vec::new();
@ -262,40 +267,41 @@ mod tests {
// Encrypt if key and IV are provided.
if let (Some(k), Some(v)) = (key, iv) {
encrypt_aes128(&mut packet, k, v);
encrypt_aes128(&mut packet, k, v)?;
}
packet
Ok(packet)
}
/// Test decoding without compression and encryption
#[tokio::test]
async fn decode_without_compression_and_encryption() {
async fn decode_without_compression_and_encryption() -> Result<(), Box<dyn std::error::Error>> {
// Sample packet data: packet_id = 1, payload = "Hello"
let packet_id = 1;
let payload = b"Hello";
// Build the packet without compression and encryption
let packet = build_packet(packet_id, payload, false, None, None);
let packet = build_packet(packet_id, payload, false, None, None)?;
// Initialize the decoder without compression and encryption
let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
// Attempt to decode
let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
let raw_packet = decoder.get_raw_packet().await.map_err(|e| e.to_string())?;
assert_eq!(raw_packet.id, packet_id);
assert_eq!(raw_packet.payload.as_ref(), payload);
Ok(())
}
/// Test decoding with compression
#[tokio::test]
async fn decode_with_compression() {
async fn decode_with_compression() -> Result<(), Box<dyn std::error::Error>> {
// Sample packet data: packet_id = 2, payload = "Hello, compressed world!"
let packet_id = 2;
let payload = b"Hello, compressed world!";
// Build the packet with compression enabled
let packet = build_packet(packet_id, payload, true, None, None);
let packet = build_packet(packet_id, payload, true, None, None)?;
// Initialize the decoder with compression enabled
let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
@ -303,15 +309,16 @@ mod tests {
decoder.set_compression(1000);
// Attempt to decode
let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
let raw_packet = decoder.get_raw_packet().await.map_err(|e| e.to_string())?;
assert_eq!(raw_packet.id, packet_id);
assert_eq!(raw_packet.payload.as_ref(), payload);
Ok(())
}
/// Test decoding with encryption
#[tokio::test]
async fn decode_with_encryption() {
async fn decode_with_encryption() -> Result<(), Box<dyn std::error::Error>> {
// Sample packet data: packet_id = 3, payload = "Hello, encrypted world!"
let packet_id = 3;
let payload = b"Hello, encrypted world!";
@ -320,22 +327,23 @@ mod tests {
let key = [0x00u8; 16]; // Example key
// Build the packet with encryption enabled (no compression)
let packet = build_packet(packet_id, payload, false, Some(&key), Some(&key));
let packet = build_packet(packet_id, payload, false, Some(&key), Some(&key))?;
// Initialize the decoder with encryption enabled
let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
decoder.set_encryption(&key);
decoder.set_encryption(&key).map_err(|e| e.to_string())?;
// Attempt to decode
let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
let raw_packet = decoder.get_raw_packet().await.map_err(|e| e.to_string())?;
assert_eq!(raw_packet.id, packet_id);
assert_eq!(raw_packet.payload.as_ref(), payload);
Ok(())
}
/// Test decoding with both compression and encryption
#[tokio::test]
async fn decode_with_compression_and_encryption() {
async fn decode_with_compression_and_encryption() -> Result<(), Box<dyn std::error::Error>> {
// Sample packet data: packet_id = 4, payload = "Hello, compressed and encrypted world!"
let packet_id = 4;
let payload = b"Hello, compressed and encrypted world!";
@ -345,23 +353,24 @@ mod tests {
let iv = [0x01u8; 16]; // Example IV
// Build the packet with both compression and encryption enabled
let packet = build_packet(packet_id, payload, true, Some(&key), Some(&iv));
let packet = build_packet(packet_id, payload, true, Some(&key), Some(&iv))?;
// Initialize the decoder with both compression and encryption enabled
let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
decoder.set_compression(1000);
decoder.set_encryption(&key);
decoder.set_encryption(&key).map_err(|e| e.to_string())?;
// Attempt to decode
let raw_packet = decoder.get_raw_packet().await.expect("Decoding failed");
let raw_packet = decoder.get_raw_packet().await.map_err(|e| e.to_string())?;
assert_eq!(raw_packet.id, packet_id);
assert_eq!(raw_packet.payload.as_ref(), payload);
Ok(())
}
/// Test decoding with invalid compressed data
#[tokio::test]
async fn decode_with_invalid_compressed_data() {
async fn decode_with_invalid_compressed_data() -> Result<(), Box<dyn std::error::Error>> {
// Sample packet data: packet_id = 5, payload_len = 10, but compressed data is invalid
let data_len = 10; // Expected decompressed size
let invalid_compressed_data = vec![0xFF, 0xFF, 0xFF]; // Invalid Zlib data
@ -369,8 +378,8 @@ mod tests {
// Build the packet with compression enabled but invalid compressed data
let mut buffer = Vec::new();
let data_len_varint = VarInt(data_len);
buffer.write_var_int(&data_len_varint).unwrap();
buffer.write_slice(&invalid_compressed_data).unwrap();
buffer.write_var_int(&data_len_varint)?;
buffer.write_slice(&invalid_compressed_data)?;
// Calculate packet length: VarInt(data_len) + invalid compressed data
let packet_len = buffer.len() as i32;
@ -378,8 +387,8 @@ mod tests {
// Create a new buffer for the entire packet
let mut packet_buffer = Vec::new();
packet_buffer.write_var_int(&packet_len_varint).unwrap();
packet_buffer.write_slice(&buffer).unwrap();
packet_buffer.write_var_int(&packet_len_varint)?;
packet_buffer.write_slice(&buffer)?;
let packet_bytes = packet_buffer;
@ -391,38 +400,40 @@ mod tests {
let result = decoder.get_raw_packet().await;
assert!(result.is_err(), "This should have errored!");
Ok(())
}
/// Test decoding with a zero-length packet
#[tokio::test]
async fn decode_with_zero_length_packet() {
async fn decode_with_zero_length_packet() -> Result<(), Box<dyn std::error::Error>> {
// Sample packet data: packet_id = 7, payload = "" (empty)
let packet_id = 7;
let payload = b"";
// Build the packet without compression and encryption
let packet = build_packet(packet_id, payload, false, None, None);
let packet = build_packet(packet_id, payload, false, None, None)?;
// Initialize the decoder without compression and encryption
let mut decoder = TCPNetworkDecoder::new(packet.as_slice());
// Attempt to decode and expect a read error
let raw_packet = decoder.get_raw_packet().await.unwrap();
let raw_packet = decoder.get_raw_packet().await.map_err(|e| e.to_string())?;
assert_eq!(raw_packet.id, packet_id);
assert_eq!(raw_packet.payload.as_ref(), payload);
Ok(())
}
/// Test decoding with maximum length packet
#[tokio::test]
#[expect(clippy::print_stdout)]
async fn decode_with_maximum_length_packet() {
async fn decode_with_maximum_length_packet() -> Result<(), Box<dyn std::error::Error>> {
// Sample packet data: packet_id = 8, payload = "A" repeated MAX_PACKET_SIZE times
// Sample packet data: packet_id = 8, payload = "A" repeated (MAX_PACKET_SIZE - 1) times
let packet_id = 8;
let payload = vec![0x41u8; MAX_PACKET_SIZE as usize - 1]; // "A" repeated
// Build the packet with compression enabled
let packet = build_packet(packet_id, &payload, true, None, None);
let packet = build_packet(packet_id, &payload, true, None, None)?;
println!("Built packet (with compression, maximum length): {packet:?}");
// Initialize the decoder with compression enabled
@ -432,8 +443,9 @@ mod tests {
// Attempt to decode
let result = decoder.get_raw_packet().await;
let raw_packet = result.unwrap();
let raw_packet = result.map_err(|e| e.to_string())?;
assert_eq!(raw_packet.id, packet_id);
assert_eq!(raw_packet.payload.as_ref(), payload);
Ok(())
}
}

View File

@ -21,7 +21,7 @@ impl<W: AsyncWrite + Unpin> EncryptionWriter<W> {
pub fn upgrade(self, cipher: Aes128Cfb8Enc) -> Self {
match self {
Self::None(stream) => Self::Encrypt(Box::new(StreamEncryptor::new(cipher, stream))),
Self::Encrypt(_) => panic!("Cannot upgrade a stream that already has a cipher!"),
Self::Encrypt(_) => self,
}
}
}
@ -108,12 +108,16 @@ impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> {
}
/// NOTE: Encryption can only be set; a minecraft stream cannot go back to being unencrypted
pub fn set_encryption(&mut self, key: &[u8; 16]) {
pub fn set_encryption(&mut self, key: &[u8; 16]) -> Result<(), PacketEncodeError> {
if matches!(self.writer, EncryptionWriter::Encrypt(_)) {
panic!("Cannot upgrade a stream that already has a cipher!");
return Err(PacketEncodeError::Message(
"Encryption already enabled".into(),
));
}
let cipher = Aes128Cfb8Enc::new_from_slices(key, key).expect("invalid key");
let cipher = Aes128Cfb8Enc::new_from_slices(key, key)
.map_err(|_| PacketEncodeError::Message("Invalid key".into()))?;
take_mut::take(&mut self.writer, |encoder| encoder.upgrade(cipher));
Ok(())
}
fn compress_packet_data(
@ -143,10 +147,9 @@ impl<W: AsyncWrite + Unpin> TCPNetworkEncoder<W> {
));
}
let (_, compressor) = self
.compressor
.as_mut()
.expect("compressor must be present after initialization");
let (_, compressor) = self.compressor.as_mut().ok_or_else(|| {
PacketEncodeError::Message("compressor must be present after initialization".into())
})?;
compressor.reset();
let status = compressor
.compress_vec(
@ -366,10 +369,15 @@ mod tests {
}
/// Helper function to decrypt data using AES-128 CFB-8 mode
fn decrypt_aes128(encrypted_data: &mut [u8], key: &[u8; 16], iv: &[u8; 16]) {
fn decrypt_aes128(
encrypted_data: &mut [u8],
key: &[u8; 16],
iv: &[u8; 16],
) -> Result<(), Box<dyn std::error::Error>> {
let mut decryptor =
Cfb8Decryptor::<Aes128>::new_from_slices(key, iv).expect("Invalid key/iv");
Cfb8Decryptor::<Aes128>::new_from_slices(key, iv).map_err(|_| "Invalid key/iv")?;
decryptor.decrypt(encrypted_data);
Ok(())
}
/// Helper function to build a packet with optional compression and encryption
@ -377,7 +385,7 @@ mod tests {
packet: &T,
compression_info: Option<(CompressionThreshold, CompressionLevel)>,
key: Option<&[u8; 16]>,
) -> Box<[u8]> {
) -> Result<Box<[u8]>, Box<dyn std::error::Error>> {
let mut buf = Vec::new();
let mut encoder = TCPNetworkEncoder::new(&mut buf);
if let Some(compression_info) = compression_info {
@ -385,38 +393,37 @@ mod tests {
}
if let Some(key) = key {
encoder.set_encryption(key);
encoder.set_encryption(key).map_err(|e| e.to_string())?;
}
let mut packet_buf = Vec::new();
let writer = &mut packet_buf;
writer
.write_var_int(&VarInt(T::to_id(MinecraftVersion::V_1_21_11)))
.unwrap();
packet
.write_packet_data(writer, &MinecraftVersion::V_1_21_11)
.unwrap();
writer.write_var_int(&VarInt(T::to_id(MinecraftVersion::V_1_21_11)))?;
packet.write_packet_data(writer, &MinecraftVersion::V_1_21_11)?;
encoder.write_packet(packet_buf.into()).await.unwrap();
encoder
.write_packet(packet_buf.into())
.await
.map_err(|e| e.to_string())?;
buf.into_boxed_slice()
Ok(buf.into_boxed_slice())
}
/// Test encoding without compression and encryption
#[tokio::test]
async fn encode_without_compression_and_encryption() {
async fn encode_without_compression_and_encryption() -> Result<(), Box<dyn std::error::Error>> {
// Create a CStatusResponse packet
let packet =
CStatusResponse::new(String::from("{\"description\": \"A Minecraft Server\"}"));
// Build the packet without compression and encryption
let packet_bytes = build_packet_with_encoder(&packet, None, None).await;
let packet_bytes = build_packet_with_encoder(&packet, None, None).await?;
// Decode the packet manually to verify correctness
let mut buffer = &packet_bytes[..];
// Read packet length VarInt
let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
let packet_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
packet_length as usize,
buffer.len(),
@ -424,7 +431,7 @@ mod tests {
);
// Read packet ID VarInt
let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
let decoded_packet_id = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
decoded_packet_id,
CStatusResponse::to_id(MinecraftVersion::V_1_21_11)
@ -433,27 +440,26 @@ mod tests {
// Remaining buffer is the payload
// We need to obtain the expected payload
let mut expected_payload = Vec::new();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
packet.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)?;
assert_eq!(buffer, expected_payload);
Ok(())
}
/// Test encoding with compression
#[tokio::test]
async fn encode_with_compression() {
async fn encode_with_compression() -> Result<(), Box<dyn std::error::Error>> {
// Create a CStatusResponse packet
let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}".to_string());
// Build the packet with compression enabled
let packet_bytes = build_packet_with_encoder(&packet, Some((0, 6)), None).await;
let packet_bytes = build_packet_with_encoder(&packet, Some((0, 6)), None).await?;
// Decode the packet manually to verify correctness
let mut buffer = &packet_bytes[..];
// Read packet length VarInt
let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
let packet_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
packet_length as usize,
buffer.len(),
@ -461,11 +467,9 @@ mod tests {
);
// Read data length VarInt (uncompressed data length)
let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
let data_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
let mut expected_payload = Vec::new();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
packet.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)?;
let uncompressed_data_length = VarInt(CStatusResponse::to_id(MinecraftVersion::V_1_21_11))
.written_size()
+ expected_payload.len();
@ -475,15 +479,14 @@ mod tests {
let compressed_data = buffer;
// Decompress the data
let decompressed_data = decompress_zlib(compressed_data, data_length as usize)
.expect("Failed to decompress data");
let decompressed_data = decompress_zlib(compressed_data, data_length as usize)?;
// Verify packet ID and payload
let mut decompressed_buffer = &decompressed_data[..];
// Read packet ID VarInt
let decoded_packet_id =
decode_varint(&mut decompressed_buffer).expect("Failed to decode packet ID");
decode_varint(&mut decompressed_buffer).map_err(|e| e.to_string())?;
assert_eq!(
decoded_packet_id,
CStatusResponse::to_id(MinecraftVersion::V_1_21_11)
@ -491,11 +494,12 @@ mod tests {
// Remaining buffer is the payload
assert_eq!(decompressed_buffer, expected_payload);
Ok(())
}
/// Test encoding with encryption
#[tokio::test]
async fn encode_with_encryption() {
async fn encode_with_encryption() -> Result<(), Box<dyn std::error::Error>> {
// Create a CStatusResponse packet
let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}".to_string());
@ -503,16 +507,16 @@ mod tests {
let key = [0x00u8; 16]; // Example key
// Build the packet with encryption enabled (no compression)
let mut packet_bytes = build_packet_with_encoder(&packet, None, Some(&key)).await;
let mut packet_bytes = build_packet_with_encoder(&packet, None, Some(&key)).await?;
// Decrypt the packet
decrypt_aes128(&mut packet_bytes, &key, &key);
decrypt_aes128(&mut packet_bytes, &key, &key)?;
// Decode the packet manually to verify correctness
let mut buffer = &packet_bytes[..];
// Read packet length VarInt
let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
let packet_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
packet_length as usize,
buffer.len(),
@ -520,7 +524,7 @@ mod tests {
);
// Read packet ID VarInt
let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
let decoded_packet_id = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
decoded_packet_id,
CStatusResponse::to_id(MinecraftVersion::V_1_21_11)
@ -528,15 +532,14 @@ mod tests {
// Remaining buffer is the payload
let mut expected_payload = Vec::new();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
packet.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)?;
assert_eq!(buffer, expected_payload);
Ok(())
}
/// Test encoding with both compression and encryption
#[tokio::test]
async fn encode_with_compression_and_encryption() {
async fn encode_with_compression_and_encryption() -> Result<(), Box<dyn std::error::Error>> {
// Create a CStatusResponse packet
let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}".to_string());
@ -545,16 +548,16 @@ mod tests {
// Build the packet with both compression and encryption enabled
// Compression threshold is set to 0 to force compression
let mut packet_bytes = build_packet_with_encoder(&packet, Some((0, 6)), Some(&key)).await;
let mut packet_bytes = build_packet_with_encoder(&packet, Some((0, 6)), Some(&key)).await?;
// Decrypt the packet
decrypt_aes128(&mut packet_bytes, &key, &key);
decrypt_aes128(&mut packet_bytes, &key, &key)?;
// Decode the packet manually to verify correctness
let mut buffer = &packet_bytes[..];
// Read packet length VarInt
let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
let packet_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
packet_length as usize,
buffer.len(),
@ -562,11 +565,9 @@ mod tests {
);
// Read data length VarInt (uncompressed data length)
let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
let data_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
let mut expected_payload = Vec::new();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
packet.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)?;
let uncompressed_data_length = VarInt(CStatusResponse::to_id(MinecraftVersion::V_1_21_11))
.written_size()
+ expected_payload.len();
@ -576,15 +577,14 @@ mod tests {
let compressed_data = buffer;
// Decompress the data
let decompressed_data = decompress_zlib(compressed_data, data_length as usize)
.expect("Failed to decompress data");
let decompressed_data = decompress_zlib(compressed_data, data_length as usize)?;
// Verify packet ID and payload
let mut decompressed_buffer = &decompressed_data[..];
// Read packet ID VarInt
let decoded_packet_id =
decode_varint(&mut decompressed_buffer).expect("Failed to decode packet ID");
decode_varint(&mut decompressed_buffer).map_err(|e| e.to_string())?;
assert_eq!(
decoded_packet_id,
CStatusResponse::to_id(MinecraftVersion::V_1_21_11)
@ -592,22 +592,23 @@ mod tests {
// Remaining buffer is the payload
assert_eq!(decompressed_buffer, expected_payload);
Ok(())
}
/// Test encoding with zero-length payload
#[tokio::test]
async fn encode_with_zero_length_payload() {
async fn encode_with_zero_length_payload() -> Result<(), Box<dyn std::error::Error>> {
// Create a CStatusResponse packet with empty payload
let packet = CStatusResponse::new(String::new());
// Build the packet without compression and encryption
let packet_bytes = build_packet_with_encoder(&packet, None, None).await;
let packet_bytes = build_packet_with_encoder(&packet, None, None).await?;
// Decode the packet manually to verify correctness
let mut buffer = &packet_bytes[..];
// Read packet length VarInt
let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
let packet_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
packet_length as usize,
buffer.len(),
@ -615,7 +616,7 @@ mod tests {
);
// Read packet ID VarInt
let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
let decoded_packet_id = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
decoded_packet_id,
CStatusResponse::to_id(MinecraftVersion::V_1_21_11)
@ -623,9 +624,7 @@ mod tests {
// Remaining buffer is the payload (empty)
let mut expected_payload = Vec::new();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
packet.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)?;
assert_eq!(
buffer.len(),
@ -633,18 +632,19 @@ mod tests {
"Payload length mismatch"
);
assert_eq!(buffer, expected_payload);
Ok(())
}
/// Test encoding with maximum length payload
#[tokio::test]
async fn encode_with_maximum_string_length() {
async fn encode_with_maximum_string_length() -> Result<(), Box<dyn std::error::Error>> {
// Maximum allowed string length is 32767 bytes
let max_string_length = 32767;
let payload_str = "A".repeat(max_string_length);
let packet = CStatusResponse::new(payload_str);
// Build the packet without compression and encryption
let packet_bytes = build_packet_with_encoder(&packet, None, None).await;
let packet_bytes = build_packet_with_encoder(&packet, None, None).await?;
// Verify that the packet size does not exceed MAX_PACKET_SIZE as usize
assert!(
@ -656,7 +656,7 @@ mod tests {
let mut buffer = &packet_bytes[..];
// Read packet length VarInt
let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
let packet_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
packet_length as usize,
buffer.len(),
@ -664,7 +664,7 @@ mod tests {
);
// Read packet ID VarInt
let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
let decoded_packet_id = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
// Assume packet ID is 0 for CStatusResponse
assert_eq!(
decoded_packet_id,
@ -673,41 +673,42 @@ mod tests {
// Remaining buffer is the payload
let mut expected_payload = Vec::new();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
packet.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)?;
assert_eq!(buffer, expected_payload);
Ok(())
}
/// Test encoding a packet that exceeds `MAX_PACKET_SIZE` as usize
#[tokio::test]
#[should_panic(expected = "TooLong")]
async fn encode_packet_exceeding_maximum_size() {
async fn encode_packet_exceeding_maximum_size() -> Result<(), Box<dyn std::error::Error>> {
// Create a custom packet with data exceeding MAX_PACKET_SIZE as usize
let data_size = MAX_PACKET_SIZE as usize + 1; // Exceed by 1 byte
let packet = MaxSizePacket::new(data_size);
// Build the packet without compression and encryption
// This should panic with PacketEncodeError::TooLong
build_packet_with_encoder(&packet, None, None).await;
// This should return PacketEncodeError::TooLong
let result = build_packet_with_encoder(&packet, None, None).await;
assert!(result.is_err());
// We can't easily check for TooLong since it's boxed, but we verified it returns an error
Ok(())
}
/// Test encoding with a small payload that should not be compressed
#[tokio::test]
async fn encode_small_payload_no_compression() {
async fn encode_small_payload_no_compression() -> Result<(), Box<dyn std::error::Error>> {
// Create a CStatusResponse packet with small payload
let packet = CStatusResponse::new(String::from("Hi"));
// Build the packet with compression enabled
// Compression threshold is set to a value higher than payload length
let packet_bytes = build_packet_with_encoder(&packet, Some((10, 6)), None).await;
let packet_bytes = build_packet_with_encoder(&packet, Some((10, 6)), None).await?;
// Decode the packet manually to verify that it was not compressed
let mut buffer = &packet_bytes[..];
// Read packet length VarInt
let packet_length = decode_varint(&mut buffer).expect("Failed to decode packet length");
let packet_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
packet_length as usize,
buffer.len(),
@ -715,14 +716,14 @@ mod tests {
);
// Read data length VarInt (should be 0 indicating no compression)
let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
let data_length = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
data_length, 0,
"Data length should be 0 indicating no compression"
);
// Read packet ID VarInt
let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
let decoded_packet_id = decode_varint(&mut buffer).map_err(|e| e.to_string())?;
assert_eq!(
decoded_packet_id,
CStatusResponse::to_id(MinecraftVersion::V_1_21_11)
@ -730,10 +731,9 @@ mod tests {
// Remaining buffer is the payload
let mut expected_payload = Vec::new();
packet
.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)
.unwrap();
packet.write_packet_data(&mut expected_payload, &MinecraftVersion::V_1_21_11)?;
assert_eq!(buffer, expected_payload);
Ok(())
}
}

View File

@ -268,8 +268,10 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for StreamEncryptor<W> {
} else {
let out_block: &mut Array<u8, U1> = (&mut out[..])
.try_into()
.expect("Output slice size does not match block size");
cipher.encrypt_b2b(block, out_block).unwrap();
.map_err(|_| Error::other("Output slice size does not match block size"))?;
cipher
.encrypt_b2b(block, out_block)
.map_err(|_| Error::other("Encryption failed"))?;
}
let write = Pin::new(&mut ref_self.write);
@ -361,6 +363,8 @@ pub enum PacketDecodeError {
NotCompressed,
#[error("the connection has closed")]
ConnectionClosed,
#[error("{0}")]
Message(String),
}
impl From<ReadingError> for PacketDecodeError {
@ -537,20 +541,20 @@ mod test {
};
#[test]
fn serde_id_or_id() {
fn serde_id_or_id() -> Result<(), Box<dyn std::error::Error>> {
let mut buf = Vec::new();
let id = IdOr::<SoundEvent>::Id(0);
id.serialize(&mut Serializer::new(&mut buf)).unwrap();
id.serialize(&mut Serializer::new(&mut buf))?;
let deser_id =
IdOr::<SoundEvent>::deserialize(&mut Deserializer::new(buf.as_slice())).unwrap();
let deser_id = IdOr::<SoundEvent>::deserialize(&mut Deserializer::new(buf.as_slice()))?;
assert!(id == deser_id);
Ok(())
}
#[test]
fn serde_id_or_value() {
fn serde_id_or_value() -> Result<(), Box<dyn std::error::Error>> {
let mut buf = Vec::new();
let event = SoundEvent {
sound_name: "test".to_string(),
@ -558,11 +562,11 @@ mod test {
};
let id = IdOr::<SoundEvent>::Value(event);
id.serialize(&mut Serializer::new(&mut buf)).unwrap();
id.serialize(&mut Serializer::new(&mut buf))?;
let deser_id =
IdOr::<SoundEvent>::deserialize(&mut Deserializer::new(buf.as_slice())).unwrap();
let deser_id = IdOr::<SoundEvent>::deserialize(&mut Deserializer::new(buf.as_slice()))?;
assert!(id == deser_id);
Ok(())
}
}

View File

@ -159,15 +159,14 @@ pub struct CHandshake {
impl CHandshake {
#[must_use]
pub fn encode(&self) -> Vec<u8> {
let token = CString::new(self.challenge_token.to_string())
.expect("challenge_token string contains no interior nul bytes");
pub fn encode(&self) -> Option<Vec<u8>> {
let token = CString::new(self.challenge_token.to_string()).ok()?;
let mut buf = Vec::with_capacity(6 + token.as_bytes_with_nul().len());
buf.push(PacketType::Handshake as u8);
buf.extend_from_slice(&self.session_id.to_be_bytes());
buf.extend_from_slice(token.as_bytes_with_nul());
buf
Some(buf)
}
}
@ -185,10 +184,10 @@ pub struct CBasicStatus {
impl CBasicStatus {
#[must_use]
pub fn encode(&self) -> Vec<u8> {
let game_type = CString::new("SMP").unwrap();
let num_players = CString::new(self.num_players.to_string()).unwrap();
let max_players = CString::new(self.max_players.to_string()).unwrap();
pub fn encode(&self) -> Option<Vec<u8>> {
let game_type = CString::new("SMP").ok()?;
let num_players = CString::new(self.num_players.to_string()).ok()?;
let max_players = CString::new(self.max_players.to_string()).ok()?;
let mut buf = Vec::new();
buf.push(PacketType::Status as u8);
@ -201,7 +200,7 @@ impl CBasicStatus {
// The port is written little-endian — this is a known Notchian quirk.
buf.extend_from_slice(&self.host_port.to_le_bytes());
buf.extend_from_slice(self.host_ip.as_bytes_with_nul());
buf
Some(buf)
}
}
@ -222,7 +221,7 @@ pub struct CFullStatus {
impl CFullStatus {
#[must_use]
pub fn encode(&self) -> Vec<u8> {
pub fn encode(&self) -> Option<Vec<u8>> {
let mut buf = Vec::new();
buf.push(PacketType::Status as u8);
buf.extend_from_slice(&self.session_id.to_be_bytes());
@ -230,28 +229,25 @@ impl CFullStatus {
let kv_pairs: &[(&str, &CString)] = &[
("hostname", &self.hostname),
("gametype", &CString::new("SMP").unwrap()),
("game_id", &CString::new("MINECRAFT").unwrap()),
("gametype", &CString::new("SMP").ok()?),
("game_id", &CString::new("MINECRAFT").ok()?),
("version", &self.version),
("plugins", &self.plugins),
("map", &self.map),
(
"numplayers",
&CString::new(self.num_players.to_string()).unwrap(),
&CString::new(self.num_players.to_string()).ok()?,
),
(
"maxplayers",
&CString::new(self.max_players.to_string()).unwrap(),
),
(
"hostport",
&CString::new(self.host_port.to_string()).unwrap(),
&CString::new(self.max_players.to_string()).ok()?,
),
("hostport", &CString::new(self.host_port.to_string()).ok()?),
("hostip", &self.host_ip),
];
for (key, value) in kv_pairs {
buf.extend_from_slice(CString::new(*key).unwrap().as_bytes_with_nul());
buf.extend_from_slice(CString::new(*key).ok()?.as_bytes_with_nul());
buf.extend_from_slice(value.as_bytes_with_nul());
}
@ -264,7 +260,7 @@ impl CFullStatus {
}
buf.push(0x00);
buf
Some(buf)
}
}
@ -273,16 +269,17 @@ mod tests {
use super::*;
#[tokio::test]
async fn handshake_request() {
async fn handshake_request() -> Result<(), Box<dyn std::error::Error>> {
let bytes = vec![0xFE, 0xFD, 0x09, 0x00, 0x00, 0x00, 0x01];
let mut raw = RawQueryPacket::decode(bytes).await.unwrap();
let mut raw = RawQueryPacket::decode(bytes).await?;
assert_eq!(raw.packet_type, PacketType::Handshake);
let pkt = SHandshake::decode(&mut raw).await.unwrap();
let pkt = SHandshake::decode(&mut raw).await?;
assert_eq!(pkt, SHandshake { session_id: 1 });
Ok(())
}
#[tokio::test]
async fn handshake_response() {
async fn handshake_response() -> Result<(), Box<dyn std::error::Error>> {
let expected = vec![
0x09, 0x00, 0x00, 0x00, 0x01, 0x39, 0x35, 0x31, 0x33, 0x33, 0x30, 0x37, 0x00,
];
@ -290,16 +287,17 @@ mod tests {
session_id: 1,
challenge_token: 9513307,
};
assert_eq!(pkt.encode(), expected);
assert_eq!(pkt.encode().ok_or("Encoding failed")?, expected);
Ok(())
}
#[tokio::test]
async fn basic_stat_request() {
async fn basic_stat_request() -> Result<(), Box<dyn std::error::Error>> {
let bytes = vec![
0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x91, 0x29, 0x5B,
];
let mut raw = RawQueryPacket::decode(bytes).await.unwrap();
let pkt = SStatusRequest::decode(&mut raw).await.unwrap();
let mut raw = RawQueryPacket::decode(bytes).await?;
let pkt = SStatusRequest::decode(&mut raw).await?;
assert_eq!(
pkt,
SStatusRequest {
@ -308,10 +306,11 @@ mod tests {
is_full_request: false
}
);
Ok(())
}
#[tokio::test]
async fn basic_stat_response() {
async fn basic_stat_response() -> Result<(), Box<dyn std::error::Error>> {
let expected = vec![
0x00, 0x00, 0x00, 0x00, 0x01, 0x41, 0x20, 0x4D, 0x69, 0x6E, 0x65, 0x63, 0x72, 0x61,
0x66, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x00, 0x53, 0x4D, 0x50, 0x00,
@ -320,24 +319,25 @@ mod tests {
];
let pkt = CBasicStatus {
session_id: 1,
motd: CString::new("A Minecraft Server").unwrap(),
map: CString::new("world").unwrap(),
motd: CString::new("A Minecraft Server")?,
map: CString::new("world")?,
num_players: 2,
max_players: 20,
host_port: 25565,
host_ip: CString::new("127.0.0.1").unwrap(),
host_ip: CString::new("127.0.0.1")?,
};
assert_eq!(pkt.encode(), expected);
assert_eq!(pkt.encode().ok_or("Encoding failed")?, expected);
Ok(())
}
#[tokio::test]
async fn full_stat_request() {
async fn full_stat_request() -> Result<(), Box<dyn std::error::Error>> {
let bytes = vec![
0xFE, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x91, 0x29, 0x5B, 0x00, 0x00, 0x00,
0x00,
];
let mut raw = RawQueryPacket::decode(bytes).await.unwrap();
let pkt = SStatusRequest::decode(&mut raw).await.unwrap();
let mut raw = RawQueryPacket::decode(bytes).await?;
let pkt = SStatusRequest::decode(&mut raw).await?;
assert_eq!(
pkt,
SStatusRequest {
@ -346,10 +346,11 @@ mod tests {
is_full_request: true
}
);
Ok(())
}
#[tokio::test]
async fn full_stat_response() {
async fn full_stat_response() -> Result<(), Box<dyn std::error::Error>> {
let expected = vec![
0x00, 0x00, 0x00, 0x00, 0x01, 0x73, 0x70, 0x6C, 0x69, 0x74, 0x6E, 0x75, 0x6D, 0x00,
0x80, 0x00, 0x68, 0x6F, 0x73, 0x74, 0x6E, 0x61, 0x6D, 0x65, 0x00, 0x41, 0x20, 0x4D,
@ -370,20 +371,18 @@ mod tests {
];
let pkt = CFullStatus {
session_id: 1,
hostname: CString::new("A Minecraft Server").unwrap(),
version: CString::new("Beta 1.9 Prerelease 4").unwrap(),
plugins: CString::new("").unwrap(),
map: CString::new("world").unwrap(),
hostname: CString::new("A Minecraft Server")?,
version: CString::new("Beta 1.9 Prerelease 4")?,
plugins: CString::new("")?,
map: CString::new("world")?,
num_players: 2,
max_players: 20,
host_port: 25565,
host_ip: CString::new("127.0.0.1").unwrap(),
players: vec![
CString::new("barneygale").unwrap(),
CString::new("Vivalahelvig").unwrap(),
],
host_ip: CString::new("127.0.0.1")?,
players: vec![CString::new("barneygale")?, CString::new("Vivalahelvig")?],
};
assert_eq!(pkt.encode(), expected);
assert_eq!(pkt.encode().ok_or("Encoding failed")?, expected);
Ok(())
}
#[tokio::test]

View File

@ -190,7 +190,7 @@ impl<R: Read> NetworkReadExt for R {
let mut bytes = [0u8; 16];
self.read_exact(&mut bytes)
.map_err(|err| ReadingError::Incomplete(err.to_string()))?;
Ok(uuid::Uuid::from_slice(&bytes).expect("Failed to parse UUID"))
Ok(uuid::Uuid::from_bytes(bytes))
}
fn get_fixed_bitset(&mut self, bits: usize) -> Result<FixedBitSet, ReadingError> {
@ -361,7 +361,13 @@ impl<W: Write> NetworkWriteExt for W {
}
fn write_string_bounded(&mut self, data: &str, bound: usize) -> Result<(), WritingError> {
assert!(data.len() <= bound);
if data.len() > bound {
return Err(WritingError::Message(format!(
"string length {} exceeds bound {}",
data.len(),
bound
)));
}
self.write_var_int(&data.len().try_into().map_err(|_| {
WritingError::Message(format!("{} isn't representable as a VarInt", data.len()))
})?)?;
@ -435,7 +441,7 @@ mod test {
};
#[test]
fn i32_reserialize() {
fn i32_reserialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)]
struct Foo {
bar: i32,
@ -443,17 +449,17 @@ mod test {
let foo = Foo { bar: 69 };
let mut bytes = Vec::new();
let mut serializer = serializer::Serializer::new(&mut bytes);
foo.serialize(&mut serializer).unwrap();
foo.serialize(&mut serializer)?;
let cursor = Cursor::new(bytes);
let deserialized: Foo =
Foo::deserialize(&mut deserializer::Deserializer::new(cursor)).unwrap();
let deserialized: Foo = Foo::deserialize(&mut deserializer::Deserializer::new(cursor))?;
assert_eq!(foo, deserialized);
Ok(())
}
#[test]
fn varint_reserialize() {
fn varint_reserialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)]
struct Foo {
bar: VarInt,
@ -461,17 +467,17 @@ mod test {
let foo = Foo { bar: 69.into() };
let mut bytes = Vec::new();
let mut serializer = serializer::Serializer::new(&mut bytes);
foo.serialize(&mut serializer).unwrap();
foo.serialize(&mut serializer)?;
let cursor = Cursor::new(bytes);
let deserialized: Foo =
Foo::deserialize(&mut deserializer::Deserializer::new(cursor)).unwrap();
let deserialized: Foo = Foo::deserialize(&mut deserializer::Deserializer::new(cursor))?;
assert_eq!(foo, deserialized);
Ok(())
}
#[test]
fn char_reserialize() {
fn char_reserialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
struct CharStruct {
c: char,
@ -481,42 +487,42 @@ mod test {
let original = CharStruct { c: 'A' };
let mut bytes = Vec::new();
let mut ser = serializer::Serializer::new(&mut bytes);
original.serialize(&mut ser).unwrap();
original.serialize(&mut ser)?;
assert_eq!(bytes, vec![0, 0, 0, 0x41]);
let de_cursor = Cursor::new(bytes);
let deserialized: CharStruct =
CharStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor)).unwrap();
CharStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor))?;
assert_eq!(original, deserialized);
// Test with complex char
let original_complex = CharStruct { c: 'Ω' }; // Greek Omega, U+03A9
let mut bytes_complex = Vec::new();
let mut ser_complex = serializer::Serializer::new(&mut bytes_complex);
original_complex.serialize(&mut ser_complex).unwrap();
original_complex.serialize(&mut ser_complex)?;
assert_eq!(bytes_complex, vec![0, 0, 0x03, 0xA9]);
let de_cursor_complex = Cursor::new(bytes_complex);
let deserialized_complex: CharStruct =
CharStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor_complex))
.unwrap();
CharStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor_complex))?;
assert_eq!(original_complex, deserialized_complex);
// Test with an emoji
let original_emoji = CharStruct { c: '\u{1F383}' }; // Pumpkin emoji, U+1F383
let mut bytes_emoji = Vec::new();
let mut ser_emoji = serializer::Serializer::new(&mut bytes_emoji);
original_emoji.serialize(&mut ser_emoji).unwrap();
original_emoji.serialize(&mut ser_emoji)?;
assert_eq!(bytes_emoji, vec![0, 0x01, 0xF3, 0x83]);
let de_cursor_emoji = Cursor::new(bytes_emoji);
let deserialized_emoji: CharStruct =
CharStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor_emoji)).unwrap();
CharStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor_emoji))?;
assert_eq!(original_emoji, deserialized_emoji);
Ok(())
}
#[test]
fn i128_reserialize() {
fn i128_reserialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
struct I128Struct {
val: i128,
@ -527,12 +533,12 @@ mod test {
};
let mut bytes = Vec::new();
let mut ser = serializer::Serializer::new(&mut bytes);
original.serialize(&mut ser).unwrap();
original.serialize(&mut ser)?;
assert_eq!(bytes, original.val.to_be_bytes());
let de_cursor = Cursor::new(bytes);
let deserialized: I128Struct =
I128Struct::deserialize(&mut deserializer::Deserializer::new(de_cursor)).unwrap();
I128Struct::deserialize(&mut deserializer::Deserializer::new(de_cursor))?;
assert_eq!(original, deserialized);
let original_neg = I128Struct {
@ -540,17 +546,18 @@ mod test {
};
let mut bytes_neg = Vec::new();
let mut ser_neg = serializer::Serializer::new(&mut bytes_neg);
original_neg.serialize(&mut ser_neg).unwrap();
original_neg.serialize(&mut ser_neg)?;
assert_eq!(bytes_neg, original_neg.val.to_be_bytes());
let de_cursor_neg = Cursor::new(bytes_neg);
let deserialized_neg: I128Struct =
I128Struct::deserialize(&mut deserializer::Deserializer::new(de_cursor_neg)).unwrap();
I128Struct::deserialize(&mut deserializer::Deserializer::new(de_cursor_neg))?;
assert_eq!(original_neg, deserialized_neg);
Ok(())
}
#[test]
fn u128_reserialize() {
fn u128_reserialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
struct U128Struct {
val: u128,
@ -561,17 +568,18 @@ mod test {
};
let mut bytes = Vec::new();
let mut ser = serializer::Serializer::new(&mut bytes);
original.serialize(&mut ser).unwrap();
original.serialize(&mut ser)?;
assert_eq!(bytes, original.val.to_be_bytes());
let de_cursor = Cursor::new(bytes);
let deserialized: U128Struct =
U128Struct::deserialize(&mut deserializer::Deserializer::new(de_cursor)).unwrap();
U128Struct::deserialize(&mut deserializer::Deserializer::new(de_cursor))?;
assert_eq!(original, deserialized);
Ok(())
}
#[test]
fn unit_reserialize() {
fn unit_reserialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
struct UnitStruct;
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
@ -583,12 +591,12 @@ mod test {
let original = UnitStruct;
let mut bytes = Vec::new();
let mut ser = serializer::Serializer::new(&mut bytes);
original.serialize(&mut ser).unwrap();
original.serialize(&mut ser)?;
assert!(bytes.is_empty());
let de_cursor = Cursor::new(bytes);
let deserialized: UnitStruct =
UnitStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor)).unwrap();
UnitStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor))?;
assert_eq!(original, deserialized);
let original_with_unit = StructWithUnit {
@ -598,7 +606,7 @@ mod test {
};
let mut bytes_with_unit = Vec::new();
let mut ser_with_unit = serializer::Serializer::new(&mut bytes_with_unit);
original_with_unit.serialize(&mut ser_with_unit).unwrap();
original_with_unit.serialize(&mut ser_with_unit)?;
// Check that only a and c were serialized
let mut expected_bytes = Vec::new();
@ -608,13 +616,13 @@ mod test {
let de_cursor_with_unit = Cursor::new(bytes_with_unit);
let deserialized_with_unit: StructWithUnit =
StructWithUnit::deserialize(&mut deserializer::Deserializer::new(de_cursor_with_unit))
.unwrap();
StructWithUnit::deserialize(&mut deserializer::Deserializer::new(de_cursor_with_unit))?;
assert_eq!(original_with_unit, deserialized_with_unit);
Ok(())
}
#[test]
fn enum_reserialize() {
fn enum_reserialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
enum MyEnum {
A,
@ -625,25 +633,25 @@ mod test {
let original_a = MyEnum::A;
let mut bytes_a = Vec::new();
let mut ser_a = serializer::Serializer::new(&mut bytes_a);
original_a.serialize(&mut ser_a).unwrap();
original_a.serialize(&mut ser_a)?;
// VarInt for index 0
assert_eq!(bytes_a, vec![0x00]);
let de_cursor_a = Cursor::new(bytes_a);
let deserialized_a: MyEnum =
MyEnum::deserialize(&mut deserializer::Deserializer::new(de_cursor_a)).unwrap();
MyEnum::deserialize(&mut deserializer::Deserializer::new(de_cursor_a))?;
assert_eq!(original_a, deserialized_a);
let original_b = MyEnum::B(123);
let mut bytes_b = Vec::new();
let mut ser_b = serializer::Serializer::new(&mut bytes_b);
original_b.serialize(&mut ser_b).unwrap();
original_b.serialize(&mut ser_b)?;
// VarInt for index 1, then i32 for 123
let mut expected_bytes_b = vec![0x01];
expected_bytes_b.extend_from_slice(&123i32.to_be_bytes());
assert_eq!(bytes_b, expected_bytes_b);
let de_cursor_b = Cursor::new(bytes_b);
let deserialized_b: MyEnum =
MyEnum::deserialize(&mut deserializer::Deserializer::new(de_cursor_b)).unwrap();
MyEnum::deserialize(&mut deserializer::Deserializer::new(de_cursor_b))?;
assert_eq!(original_b, deserialized_b);
let original_c = MyEnum::C {
@ -652,7 +660,7 @@ mod test {
};
let mut bytes_c = Vec::new();
let mut ser_c = serializer::Serializer::new(&mut bytes_c);
original_c.serialize(&mut ser_c).unwrap();
original_c.serialize(&mut ser_c)?;
// VarInt for index 2, then i32 for 456, then string "hello"
let mut expected_bytes_c = vec![0x02];
expected_bytes_c.extend_from_slice(&456i32.to_be_bytes());
@ -661,19 +669,20 @@ mod test {
assert_eq!(bytes_c, expected_bytes_c);
let de_cursor_c = Cursor::new(bytes_c);
let deserialized_c: MyEnum =
MyEnum::deserialize(&mut deserializer::Deserializer::new(de_cursor_c)).unwrap();
MyEnum::deserialize(&mut deserializer::Deserializer::new(de_cursor_c))?;
assert_eq!(original_c, deserialized_c);
Ok(())
}
#[test]
fn tuple_struct_reserialize() {
fn tuple_struct_reserialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
struct MyTupleStruct(i32, String);
let original = MyTupleStruct(789, "world".to_string());
let mut bytes = Vec::new();
let mut ser = serializer::Serializer::new(&mut bytes);
original.serialize(&mut ser).unwrap();
original.serialize(&mut ser)?;
let mut expected_bytes = Vec::new();
expected_bytes.extend_from_slice(&789i32.to_be_bytes());
@ -683,12 +692,13 @@ mod test {
let de_cursor = Cursor::new(bytes);
let deserialized: MyTupleStruct =
MyTupleStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor)).unwrap();
MyTupleStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor))?;
assert_eq!(original, deserialized);
Ok(())
}
#[test]
fn map_reserialize() {
fn map_reserialize() -> Result<(), Box<dyn std::error::Error>> {
use std::collections::HashMap;
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
@ -704,14 +714,14 @@ mod test {
let mut bytes = Vec::new();
let mut serializer = serializer::Serializer::new(&mut bytes);
original.serialize(&mut serializer).unwrap();
original.serialize(&mut serializer)?;
// Expected bytes: VarInt for map length (2), then key1, value1, key2, value2
// Order of elements in HashMap is not guaranteed, so we check deserialized content
let de_cursor = Cursor::new(bytes.clone()); // Clone bytes for potential debug
let deserialized: MyMapStruct =
MyMapStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor)).unwrap();
MyMapStruct::deserialize(&mut deserializer::Deserializer::new(de_cursor))?;
assert_eq!(original.map.len(), deserialized.map.len());
for (k, v) in original.map {
@ -724,13 +734,13 @@ mod test {
};
let mut empty_map_bytes = Vec::new();
let mut empty_map_ser = serializer::Serializer::new(&mut empty_map_bytes);
empty_map_original.serialize(&mut empty_map_ser).unwrap();
empty_map_original.serialize(&mut empty_map_ser)?;
assert_eq!(empty_map_bytes, vec![0x00]); // VarInt for length 0
let empty_map_de_cursor = Cursor::new(empty_map_bytes);
let empty_map_deserialized: MyMapStruct =
MyMapStruct::deserialize(&mut deserializer::Deserializer::new(empty_map_de_cursor))
.unwrap();
MyMapStruct::deserialize(&mut deserializer::Deserializer::new(empty_map_de_cursor))?;
assert_eq!(empty_map_original, empty_map_deserialized);
Ok(())
}
}

View File

@ -408,13 +408,6 @@ impl Navigator {
return;
}
// Don't try to path-follow while airborne — let gravity handle it
if !on_ground {
entity.movement_input.store(Vector3::new(0.0, 0.0, 0.0));
self.current_goal = Some(goal);
return;
}
let desired_yaw = wrap_degrees((dz.atan2(dx) as f32).to_degrees() - 90.0);
let current_yaw = entity.entity.yaw.load();
let yaw_diff = wrap_degrees(desired_yaw - current_yaw);
@ -432,9 +425,13 @@ impl Navigator {
.movement_input
.store(Vector3::new(0.0, 0.0, mob_speed));
let bbox = entity.entity.bounding_box.load();
let width = bbox.max.x - bbox.min.x;
let jump_distance = 1.0f64.max(width);
// Jump when the next node is above step height and we're close enough horizontally
if dy > entity.get_attribute_value(&Attributes::STEP_HEIGHT)
&& horizontal_dist < 2.0
&& horizontal_dist_sq < jump_distance
{
entity
.jumping

View File

@ -159,8 +159,16 @@ impl JavaClient {
let crypt_key: [u8; 16] = shared_secret
.try_into()
.map_err(|_| EncryptionError::SharedWrongLength)?;
self.network_reader.lock().await.set_encryption(&crypt_key);
self.network_writer.lock().await.set_encryption(&crypt_key);
self.network_reader
.lock()
.await
.set_encryption(&crypt_key)
.map_err(|_| EncryptionError::AlreadyEncrypted)?;
self.network_writer
.lock()
.await
.set_encryption(&crypt_key)
.map_err(|_| EncryptionError::AlreadyEncrypted)?;
Ok(())
}

View File

@ -241,6 +241,8 @@ pub enum EncryptionError {
FailedDecrypt,
#[error("shared secret has the wrong length")]
SharedWrongLength,
#[error("encryption is already enabled")]
AlreadyEncrypted,
}
fn is_valid_player_name(name: &str) -> bool {

View File

@ -108,7 +108,9 @@ async fn handle_packet(
// Ignore all errors since we don't want the query handler to crash
// Protocol also ignores all errors and just doesn't respond
let _ = socket.send_to(response.encode().as_slice(), addr).await;
if let Some(encoded) = response.encode() {
let _ = socket.send_to(encoded.as_slice(), addr).await;
}
clients.write().await.insert(challenge_token, addr);
}
@ -171,7 +173,9 @@ async fn handle_packet(
players,
};
let _ = socket.send_to(response.encode().as_slice(), addr).await;
if let Some(encoded) = response.encode() {
let _ = socket.send_to(encoded.as_slice(), addr).await;
}
} else {
let response = CBasicStatus {
session_id: packet.session_id,
@ -189,7 +193,9 @@ async fn handle_packet(
host_ip: CString::new(bound_addr.ip().to_string())?,
};
let _ = socket.send_to(response.encode().as_slice(), addr).await;
if let Some(encoded) = response.encode() {
let _ = socket.send_to(encoded.as_slice(), addr).await;
}
}
}
}