diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index ee36759..c40266e 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -17,3 +17,4 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" uuid = { version = "0.7", features = ["v4", "serde"] } named-binary-tag = "0.2" +flate2 = "1.0.22" diff --git a/protocol/src/data/chat.rs b/protocol/src/data/chat.rs index 6f21fa6..8c55f29 100644 --- a/protocol/src/data/chat.rs +++ b/protocol/src/data/chat.rs @@ -339,6 +339,7 @@ impl Message { impl_json_encoder_decoder!(Message); +#[derive(Debug)] pub struct MessageBuilder { current: Message, root: Option, diff --git a/protocol/src/data/server_status.rs b/protocol/src/data/server_status.rs index e3fdeac..e17697d 100644 --- a/protocol/src/data/server_status.rs +++ b/protocol/src/data/server_status.rs @@ -8,6 +8,8 @@ pub struct ServerStatus { pub version: ServerVersion, pub players: OnlinePlayers, pub description: Message, + #[serde(skip_serializing_if = "Option::is_none")] + pub favicon: Option, } #[derive(Clone, Serialize, Deserialize, Debug)] diff --git a/protocol/src/error.rs b/protocol/src/error.rs index 55a790d..8167e1a 100644 --- a/protocol/src/error.rs +++ b/protocol/src/error.rs @@ -14,7 +14,7 @@ pub enum EncodeError { /// Max string length. max_length: u16, }, - IOError { + IoError { io_error: IoError, }, JsonError { @@ -24,7 +24,7 @@ pub enum EncodeError { impl From for EncodeError { fn from(io_error: IoError) -> Self { - EncodeError::IOError { io_error } + EncodeError::IoError { io_error } } } @@ -37,6 +37,11 @@ impl From for EncodeError { /// Possible errors while decoding packet. #[derive(Debug)] pub enum DecodeError { + /// Packet is incomplete. + Incomplete { + /// Minimum number of bytes needed to complete the packet. + bytes_needed: usize, + }, /// Packet was not recognized. Invalid data or wrong protocol version. UnknownPacketType { type_id: u8, @@ -48,7 +53,7 @@ pub enum DecodeError { /// Max string length. max_length: u16, }, - IOError { + IoError { io_error: IoError, }, JsonError { @@ -73,11 +78,12 @@ pub enum DecodeError { VarIntTooLong { max_bytes: usize, }, + DecompressionError, } impl From for DecodeError { fn from(io_error: IoError) -> Self { - DecodeError::IOError { io_error } + DecodeError::IoError { io_error } } } diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 2f42969..cf57940 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -1,10 +1,13 @@ //! This crate implements Minecraft protocol. //! //! Information about protocol can be found at https://wiki.vg/Protocol. +#![warn(missing_debug_implementations)] + pub mod data; pub mod decoder; pub mod encoder; pub mod error; +pub mod packet; pub mod version; /// Protocol limits maximum string length. diff --git a/protocol/src/packet.rs b/protocol/src/packet.rs new file mode 100644 index 0000000..c1d1918 --- /dev/null +++ b/protocol/src/packet.rs @@ -0,0 +1,190 @@ +use std::io::{self, Read, Write}; + +use flate2::read::ZlibDecoder; +use flate2::write::ZlibEncoder; +use flate2::Compression; +use minecraft_protocol_derive::{Decoder, Encoder}; + +use crate::decoder::{Decoder, DecoderReadExt}; +use crate::encoder::{Encoder, EncoderWriteExt}; +use crate::error::{DecodeError, EncodeError}; + +#[derive(Debug, Clone)] +pub struct Packet { + pub id: i32, + pub data: Vec, +} + +impl Packet { + pub fn encode( + self, + writer: &mut W, + compression_threshold: Option, + ) -> Result<(), EncodeError> { + let mut buf = Vec::new(); + let packet = RawPacket { + id: self.id, + data: self.data, + }; + if let Some(threshold) = compression_threshold { + CompressedRawPacket { packet, threshold }.encode(&mut buf)?; + } else { + packet.encode(&mut buf)?; + } + + writer.write_var_i32(buf.len() as i32)?; + writer.write_all(&buf)?; + + Ok(()) + } + + pub fn decode(reader: &mut R, compressed: bool) -> Result { + let len = match reader.read_var_i32() { + Ok(len) => len as usize, + Err(DecodeError::IoError { io_error }) + if io_error.kind() == io::ErrorKind::UnexpectedEof => + { + return Err(DecodeError::Incomplete { bytes_needed: 1 }) + } + Err(err) => return Err(err.into()), + }; + + let mut buf = Vec::with_capacity(len); + let bytes_read = reader.take(len as u64).read_to_end(&mut buf)?; + + if bytes_read != len { + return Err(DecodeError::Incomplete { + bytes_needed: len - bytes_read, + }); + } + + let RawPacket { id, data } = if compressed { + CompressedRawPacket::decode(&mut buf.as_slice())? + } else { + RawPacket::decode(&mut buf.as_slice())? + }; + + Ok(Self { id, data }) + } +} + +#[derive(Debug, Clone, Encoder, Decoder)] +struct RawPacket { + #[data_type(with = "var_int")] + pub id: i32, + #[data_type(with = "rest")] + pub data: Vec, +} + +#[derive(Debug, Clone)] +struct CompressedRawPacket { + packet: RawPacket, + threshold: i32, +} + +impl Encoder for CompressedRawPacket { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + let mut packet_buf = Vec::new(); + self.packet.encode(&mut packet_buf)?; + + let data_len = packet_buf.len() as i32; + if self.threshold >= 0 && data_len > self.threshold { + writer.write_var_i32(data_len)?; + let mut encoder = ZlibEncoder::new(writer, Compression::default()); + encoder.write_all(&packet_buf)?; + encoder.finish()?; + } else { + writer.write_var_i32(0)?; + writer.write_all(&packet_buf)?; + }; + + Ok(()) + } +} + +impl Decoder for CompressedRawPacket { + type Output = RawPacket; + + fn decode(reader: &mut R) -> Result { + let data_len = reader.read_var_i32()? as usize; + let packet = if data_len == 0 { + RawPacket::decode(reader)? + } else { + let mut decompressed = Vec::with_capacity(data_len); + ZlibDecoder::new(reader).read_to_end(&mut decompressed)?; + + if decompressed.len() != data_len { + return Err(DecodeError::DecompressionError); + } + + RawPacket::decode(&mut decompressed.as_slice())? + }; + + Ok(packet) + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use super::*; + + use crate::version::v1_14_4::status::*; + + const PING_REQUEST_BYTES: &'static [u8] = + include_bytes!("../test/packet/status/ping_request.dat"); + + fn ping_request_packet_bytes() -> Vec { + let len = (1 + PING_REQUEST_BYTES.len()).try_into().unwrap(); + let mut vec = vec![len, 1]; + vec.extend(PING_REQUEST_BYTES); + vec + } + + #[test] + fn test_uncompressed_packet_encode() { + let ping_request = PingRequest { + time: 1577735845610, + }; + + let mut data = Vec::new(); + ping_request.encode(&mut data).unwrap(); + + let packet = Packet { id: 1, data }; + + let mut vec = Vec::new(); + packet.encode(&mut vec, None).unwrap(); + + assert_eq!(vec, ping_request_packet_bytes()); + } + + #[test] + fn test_uncompressed_packet_decode() { + let vec = ping_request_packet_bytes(); + let packet = Packet::decode(&mut vec.as_slice(), false).unwrap(); + + assert_eq!(packet.id, 1); + assert_eq!(packet.data, PING_REQUEST_BYTES); + } + + #[test] + fn test_compressed_packet_encode_decode() { + let ping_request = PingRequest { + time: 1577735845610, + }; + + let mut data = Vec::new(); + ping_request.encode(&mut data).unwrap(); + + let packet = Packet { id: 1, data }; + + let mut vec = Vec::new(); + packet.encode(&mut vec, Some(0)).unwrap(); + + let packet = Packet::decode(&mut vec.as_slice(), true).unwrap(); + + assert_eq!(packet.id, 1); + assert_eq!(packet.data, PING_REQUEST_BYTES); + } +} diff --git a/protocol/src/version/v1_14_4/game.rs b/protocol/src/version/v1_14_4/game.rs index 4400831..f740002 100644 --- a/protocol/src/version/v1_14_4/game.rs +++ b/protocol/src/version/v1_14_4/game.rs @@ -9,12 +9,14 @@ use nbt::CompoundTag; use std::io::Read; use uuid::Uuid; +#[derive(Debug)] pub enum GameServerBoundPacket { ServerBoundChatMessage(ServerBoundChatMessage), ServerBoundKeepAlive(ServerBoundKeepAlive), ServerBoundAbilities(ServerBoundAbilities), } +#[derive(Debug)] pub enum GameClientBoundPacket { ClientBoundChatMessage(ClientBoundChatMessage), JoinGame(JoinGame), @@ -177,7 +179,7 @@ impl JoinGame { } } -#[derive(Encoder, Decoder)] +#[derive(Encoder, Decoder, Debug)] pub struct ServerBoundKeepAlive { pub id: u64, } @@ -190,7 +192,7 @@ impl ServerBoundKeepAlive { } } -#[derive(Encoder, Decoder)] +#[derive(Encoder, Decoder, Debug)] pub struct ClientBoundKeepAlive { pub id: u64, } diff --git a/protocol/src/version/v1_14_4/handshake.rs b/protocol/src/version/v1_14_4/handshake.rs index f678043..caceda3 100644 --- a/protocol/src/version/v1_14_4/handshake.rs +++ b/protocol/src/version/v1_14_4/handshake.rs @@ -3,6 +3,7 @@ use crate::error::DecodeError; use minecraft_protocol_derive::{Decoder, Encoder}; use std::io::Read; +#[derive(Debug)] pub enum HandshakeServerBoundPacket { Handshake(Handshake), } diff --git a/protocol/src/version/v1_14_4/login.rs b/protocol/src/version/v1_14_4/login.rs index 14248a3..3ee8ad8 100644 --- a/protocol/src/version/v1_14_4/login.rs +++ b/protocol/src/version/v1_14_4/login.rs @@ -6,12 +6,14 @@ use crate::decoder::Decoder; use crate::error::DecodeError; use minecraft_protocol_derive::{Decoder, Encoder}; +#[derive(Debug)] pub enum LoginServerBoundPacket { LoginStart(LoginStart), EncryptionResponse(EncryptionResponse), LoginPluginResponse(LoginPluginResponse), } +#[derive(Debug)] pub enum LoginClientBoundPacket { LoginDisconnect(LoginDisconnect), EncryptionRequest(EncryptionRequest), diff --git a/protocol/src/version/v1_14_4/status.rs b/protocol/src/version/v1_14_4/status.rs index 57c281a..173de4c 100644 --- a/protocol/src/version/v1_14_4/status.rs +++ b/protocol/src/version/v1_14_4/status.rs @@ -4,11 +4,13 @@ use crate::error::DecodeError; use minecraft_protocol_derive::{Decoder, Encoder}; use std::io::Read; +#[derive(Debug)] pub enum StatusServerBoundPacket { StatusRequest, PingRequest(PingRequest), } +#[derive(Debug)] pub enum StatusClientBoundPacket { StatusResponse(StatusResponse), PingResponse(PingResponse), @@ -162,6 +164,7 @@ mod tests { version, description: Message::new(Payload::text("Description")), players, + favicon: None, }; let status_response = StatusResponse { server_status };