Pumpkin/pumpkin-protocol/src/packet_encoder.rs
2024-12-26 20:59:25 +01:00

609 lines
22 KiB
Rust

use aes::cipher::{generic_array::GenericArray, BlockEncryptMut, BlockSizeUser, KeyIvInit};
use bytes::{BufMut, BytesMut};
use thiserror::Error;
use libdeflater::{CompressionLvl, Compressor};
use crate::{
codec::Codec, ClientPacket, CompressionLevel, CompressionThreshold, VarInt, MAX_PACKET_SIZE,
};
type Cipher = cfb8::Encryptor<aes::Aes128>;
/// Encoder: Server -> Client
/// Supports ZLib endecoding/compression
/// Supports Aes128 Encryption
pub struct PacketEncoder {
buf: BytesMut,
compress_buf: Vec<u8>,
cipher: Option<Cipher>,
// compression and compression threshold
compression: Option<(Compressor, CompressionThreshold)>,
}
// Manual implementation of Default trait for PacketEncoder
// Since compressor does not implement Default
impl Default for PacketEncoder {
fn default() -> Self {
Self {
buf: BytesMut::with_capacity(1024),
compress_buf: Vec::with_capacity(1024),
cipher: None,
compression: None, // init compressor with fastest compression level
}
}
}
impl PacketEncoder {
/// Appends a Clientbound `ClientPacket` to the internal buffer and applies compression when needed.
///
/// If compression is enabled and the packet size exceeds the threshold, the packet is compressed.
/// The packet is prefixed with its length and, if compressed, the uncompressed data length.
/// The packet format is as follows:
///
/// **Uncompressed:**
/// |-----------------------|
/// | Packet Length (VarInt)|
/// |-----------------------|
/// | Packet ID (VarInt) |
/// |-----------------------|
/// | Data (Byte Array) |
/// |-----------------------|
///
/// **Compressed:**
/// |------------------------|
/// | Packet Length (VarInt) |
/// |------------------------|
/// | Data Length (VarInt) |
/// |------------------------|
/// | Packet ID (VarInt) |
/// |------------------------|
/// | Data (Byte Array) |
/// |------------------------|
///
/// - `Packet Length`: The total length of the packet *excluding* the `Packet Length` field itself.
/// - `Data Length`: (Only present in compressed packets) The length of the uncompressed `Packet ID` and `Data`.
/// - `Packet ID`: The ID of the packet.
/// - `Data`: The packet's data.
pub fn append_packet<P: ClientPacket>(&mut self, packet: &P) -> Result<(), PacketEncodeError> {
let start_len = self.buf.len();
// Write the Packet ID first
VarInt(P::PACKET_ID).encode(&mut self.buf);
// Now write the packet into an empty buffer
packet.write(&mut self.buf);
let data_len = self.buf.len() - start_len;
if let Some((compressor, compression_threshold)) = &mut self.compression {
if data_len > compression_threshold.0 as usize {
// Get the data to compress
let data_to_compress = &self.buf[start_len..];
// Clear the compression buffer
self.compress_buf.clear();
// Compute the maximum size of compressed data
let max_compressed_size = compressor.zlib_compress_bound(data_to_compress.len());
// Ensure compress_buf has enough capacity
self.compress_buf.resize(max_compressed_size, 0);
// Compress the data
let compressed_size = compressor
.zlib_compress(data_to_compress, &mut self.compress_buf)
.map_err(|e| PacketEncodeError::CompressionFailed(e.to_string()))?;
// Resize compress_buf to actual compressed size
self.compress_buf.resize(compressed_size, 0);
let data_len_size = VarInt(data_len as i32).written_size();
let packet_len = data_len_size + compressed_size;
if packet_len >= MAX_PACKET_SIZE as usize {
return Err(PacketEncodeError::TooLong(packet_len));
}
self.buf.truncate(start_len);
VarInt(packet_len as i32).encode(&mut self.buf);
VarInt(data_len as i32).encode(&mut self.buf);
self.buf.extend_from_slice(&self.compress_buf);
} else {
let data_len_size = 1;
let packet_len = data_len_size + data_len;
if packet_len >= MAX_PACKET_SIZE as usize {
Err(PacketEncodeError::TooLong(packet_len))?
}
let packet_len_size = VarInt(packet_len as i32).written_size();
let data_prefix_len = packet_len_size + data_len_size;
self.buf.put_bytes(0, data_prefix_len);
self.buf
.copy_within(start_len..start_len + data_len, start_len + data_prefix_len);
let mut front = &mut self.buf[start_len..];
VarInt(packet_len as i32).encode(&mut front);
// Zero for no compression on this packet.
VarInt(0).encode(&mut front);
}
return Ok(());
}
let packet_len = data_len;
if packet_len >= MAX_PACKET_SIZE as usize {
Err(PacketEncodeError::TooLong(packet_len))?
}
let packet_len_size = VarInt(packet_len as i32).written_size();
self.buf.put_bytes(0, packet_len_size);
self.buf
.copy_within(start_len..start_len + data_len, start_len + packet_len_size);
let mut front = &mut self.buf[start_len..];
VarInt(packet_len as i32).encode(&mut front);
Ok(())
}
/// Enable encryption for taking all packets buffer `
pub fn set_encryption(&mut self, key: Option<&[u8; 16]>) {
if let Some(key) = key {
assert!(self.cipher.is_none(), "encryption is already enabled");
self.cipher = Some(Cipher::new_from_slices(key, key).expect("invalid key"));
} else {
assert!(self.cipher.is_some(), "encryption is disabled");
self.cipher = None;
}
}
/// Enables or disables Zlib compression.
///
/// If `compression` is `Some`, compression is enabled with the given `threshold`
/// for triggering compression and the specified `level`. If `compression` is
/// `None`, compression is disabled.
///
/// # Errors
///
/// Returns an `CompressionLevelError` if an invalid compression level is provided.
pub fn set_compression(
&mut self,
compression: Option<(CompressionThreshold, CompressionLevel)>,
) -> Result<(), CompressionLevelError> {
match compression {
Some((threshold, level)) => {
let level =
CompressionLvl::new(level.0 as i32).map_err(|_| CompressionLevelError)?;
self.compression = Some((Compressor::new(level), threshold));
}
None => {
self.compression = None;
}
}
Ok(())
}
/// Encrypts the data in the internal buffer and returns it as a `BytesMut`.
///
/// If a cipher is set, the data is encrypted in-place using block cipher encryption.
/// The buffer is processed in chunks of the cipher's block size. If the buffer's
/// length is not a multiple of the block size, the last partial block is *not* encrypted.
/// It's important to ensure that the data being encrypted is padded appropriately
/// beforehand if necessary.
///
/// If no cipher is set, the buffer is returned as is.
pub fn take(&mut self) -> BytesMut {
if let Some(cipher) = &mut self.cipher {
for chunk in self.buf.chunks_mut(Cipher::block_size()) {
let gen_arr = GenericArray::from_mut_slice(chunk);
cipher.encrypt_block_mut(gen_arr);
}
}
self.buf.split()
}
}
#[derive(Error, Debug)]
#[error("Invalid compression Level")]
pub struct CompressionLevelError;
/// Errors that can occur during packet encoding.
#[derive(Error, Debug)]
pub enum PacketEncodeError {
#[error("Packet exceeds maximum length: {0}")]
TooLong(usize),
#[error("Compression failed {0}")]
CompressionFailed(String),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::status::CStatusResponse;
use crate::{bytebuf::packet_id::Packet, codec::DecodeError};
use aes::Aes128;
use cfb8::cipher::AsyncStreamCipher;
use cfb8::Decryptor as Cfb8Decryptor;
use libdeflater::{DecompressionError, Decompressor};
use pumpkin_macros::client_packet;
use serde::Serialize;
/// Define a custom packet for testing maximum packet size
#[derive(Serialize)]
#[client_packet("status:status_response")]
pub struct MaxSizePacket {
data: Vec<u8>,
}
impl MaxSizePacket {
pub fn new(size: usize) -> Self {
Self {
data: vec![0xAB; size], // Fill with arbitrary data
}
}
}
/// Helper function to decode a VarInt from bytes
fn decode_varint(buffer: &mut &[u8]) -> Result<i32, DecodeError> {
VarInt::decode(buffer).map(|varint| varint.0)
}
/// Helper function to decompress data using libdeflater's Zlib decompressor
fn decompress_zlib(data: &[u8], expected_size: usize) -> Result<Vec<u8>, DecompressionError> {
let mut decompressor = Decompressor::new();
let mut decompressed = vec![0u8; expected_size];
let actual_size = decompressor.zlib_decompress(data, &mut decompressed)?;
decompressed.truncate(actual_size);
Ok(decompressed)
}
/// Helper function to decrypt data using AES-128 CFB-8 mode
fn decrypt_aes128(encrypted_data: &mut [u8], key: &[u8; 16], iv: &[u8; 16]) {
let decryptor = Cfb8Decryptor::<Aes128>::new_from_slices(key, iv).expect("Invalid key/iv");
decryptor.decrypt(encrypted_data);
}
/// Helper function to build a packet with optional compression and encryption
fn build_packet_with_encoder<T: ClientPacket>(
packet: &T,
compression_info: Option<(CompressionThreshold, CompressionLevel)>,
key: Option<&[u8; 16]>,
) -> BytesMut {
let mut encoder = PacketEncoder::default();
if let Some(compression) = compression_info {
encoder.set_compression(Some(compression)).unwrap();
} else {
encoder.set_compression(None).unwrap();
}
if let Some(key) = key {
encoder.set_encryption(Some(key));
}
encoder
.append_packet(packet)
.expect("Failed to append packet");
encoder.take()
}
/// Test encoding without compression and encryption
#[test]
fn test_encode_without_compression_and_encryption() {
// Create a CStatusResponse packet
let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}");
// Build the packet without compression and encryption
let packet_bytes = build_packet_with_encoder(&packet, None, None);
// 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");
assert_eq!(
packet_length as usize,
buffer.len(),
"Packet length mismatch"
);
// Read packet ID VarInt
let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
// Remaining buffer is the payload
// We need to obtain the expected payload
let mut expected_payload = BytesMut::new();
packet.write(&mut expected_payload);
assert_eq!(buffer, expected_payload);
}
/// Test encoding with compression
#[test]
fn test_encode_with_compression() {
// Create a CStatusResponse packet
let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}");
// Build the packet with compression enabled
let packet_bytes = build_packet_with_encoder(
&packet,
Some((CompressionThreshold(0), CompressionLevel(6))),
None,
);
// 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");
assert_eq!(
packet_length as usize,
buffer.len(),
"Packet length mismatch"
);
// Read data length VarInt (uncompressed data length)
let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
let mut expected_payload = BytesMut::new();
packet.write(&mut expected_payload);
let uncompressed_data_length =
VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len();
assert_eq!(data_length as usize, uncompressed_data_length);
// Remaining buffer is the compressed data
let compressed_data = buffer;
// Decompress the data
let decompressed_data = decompress_zlib(compressed_data, data_length as usize)
.expect("Failed to decompress data");
// 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");
assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
// Remaining buffer is the payload
assert_eq!(decompressed_buffer, expected_payload);
}
/// Test encoding with encryption
#[test]
fn test_encode_with_encryption() {
// Create a CStatusResponse packet
let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}");
// Encryption key and IV (IV is the same as key in this case)
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));
// Decrypt the packet
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");
assert_eq!(
packet_length as usize,
buffer.len(),
"Packet length mismatch"
);
// Read packet ID VarInt
let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
// Remaining buffer is the payload
let mut expected_payload = BytesMut::new();
packet.write(&mut expected_payload);
assert_eq!(buffer, expected_payload);
}
/// Test encoding with both compression and encryption
#[test]
fn test_encode_with_compression_and_encryption() {
// Create a CStatusResponse packet
let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}");
// Encryption key and IV (IV is the same as key in this case)
let key = [0x01u8; 16]; // Example key
// 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((CompressionThreshold(0), CompressionLevel(6))),
Some(&key),
);
// Decrypt the packet
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");
assert_eq!(
packet_length as usize,
buffer.len(),
"Packet length mismatch"
);
// Read data length VarInt (uncompressed data length)
let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
let mut expected_payload = BytesMut::new();
packet.write(&mut expected_payload);
let uncompressed_data_length =
VarInt(CStatusResponse::PACKET_ID).written_size() + expected_payload.len();
assert_eq!(data_length as usize, uncompressed_data_length);
// Remaining buffer is the compressed data
let compressed_data = buffer;
// Decompress the data
let decompressed_data = decompress_zlib(compressed_data, data_length as usize)
.expect("Failed to decompress data");
// 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");
assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
// Remaining buffer is the payload
assert_eq!(decompressed_buffer, expected_payload);
}
/// Test encoding with zero-length payload
#[test]
fn test_encode_with_zero_length_payload() {
// Create a CStatusResponse packet with empty payload
let packet = CStatusResponse::new("");
// Build the packet without compression and encryption
let packet_bytes = build_packet_with_encoder(&packet, None, None);
// 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");
assert_eq!(
packet_length as usize,
buffer.len(),
"Packet length mismatch"
);
// Read packet ID VarInt
let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
// Remaining buffer is the payload (empty)
let mut expected_payload = BytesMut::new();
packet.write(&mut expected_payload);
assert_eq!(
buffer.len(),
expected_payload.len(),
"Payload length mismatch"
);
assert_eq!(buffer, expected_payload);
}
/// Test encoding with maximum length payload
#[test]
fn test_encode_with_maximum_string_length() {
// 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);
// Verify that the packet size does not exceed MAX_PACKET_SIZE
assert!(
packet_bytes.len() <= MAX_PACKET_SIZE as usize,
"Packet size exceeds maximum allowed size"
);
// 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");
assert_eq!(
packet_length as usize,
buffer.len(),
"Packet length mismatch"
);
// Read packet ID VarInt
let decoded_packet_id = decode_varint(&mut buffer).expect("Failed to decode packet ID");
// Assume packet ID is 0 for CStatusResponse
assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
// Remaining buffer is the payload
let mut expected_payload = BytesMut::new();
packet.write(&mut expected_payload);
assert_eq!(buffer, expected_payload);
}
/// Test encoding a packet that exceeds MAX_PACKET_SIZE
#[test]
#[should_panic(expected = "TooLong")]
fn test_encode_packet_exceeding_maximum_size() {
// Create a custom packet with data exceeding MAX_PACKET_SIZE
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);
}
/// Test encoding with a small payload that should not be compressed
#[test]
fn test_encode_small_payload_no_compression() {
// Create a CStatusResponse packet with small payload
let packet = CStatusResponse::new("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((CompressionThreshold(10), CompressionLevel(6))),
None,
);
// 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");
assert_eq!(
packet_length as usize,
buffer.len(),
"Packet length mismatch"
);
// Read data length VarInt (should be 0 indicating no compression)
let data_length = decode_varint(&mut buffer).expect("Failed to decode data length");
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");
assert_eq!(decoded_packet_id, CStatusResponse::PACKET_ID);
// Remaining buffer is the payload
let mut expected_payload = BytesMut::new();
packet.write(&mut expected_payload);
assert_eq!(buffer, expected_payload);
}
}