diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index 2953b8f..a497019 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -1,11 +1,10 @@ extern crate proc_macro; use proc_macro::TokenStream as TokenStream1; -use proc_macro2::Ident; use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{Ident, Span}; use quote::{quote, TokenStreamExt}; use std::iter::FromIterator; -use syn::export::Span; use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Lit, Meta, NestedMeta}; #[proc_macro_derive(Packet, attributes(packet))] diff --git a/protocol/src/chat.rs b/protocol/src/data/chat.rs similarity index 52% rename from protocol/src/chat.rs rename to protocol/src/data/chat.rs index 8ad49c6..2fdc5d8 100644 --- a/protocol/src/chat.rs +++ b/protocol/src/data/chat.rs @@ -6,7 +6,7 @@ //! ## Serialize //! //! ``` -//! use minecraft_protocol::chat::{Payload, Color, MessageBuilder}; +//! use minecraft_protocol::data::chat::{MessageBuilder, Payload, Color}; //! //! let message = MessageBuilder::builder(Payload::text("Hello")) //! .color(Color::Yellow) @@ -25,7 +25,7 @@ //! ## Deserialize //! //! ``` -//! use minecraft_protocol::chat::{MessageBuilder, Color, Payload, Message}; +//! use minecraft_protocol::data::chat::{MessageBuilder, Color, Payload, Message}; //! //! let json = r#" //! { @@ -61,7 +61,6 @@ //! assert_eq!(expected_message, Message::from_json(json).unwrap()); //! ``` -use crate::impl_json_encoder_decoder; use serde::{ de::{self, Visitor}, Deserialize, Serialize, @@ -93,7 +92,7 @@ pub enum Color { /// # Examples /// /// ``` - /// use minecraft_protocol::chat::Color; + /// use minecraft_protocol::data::chat::Color; /// /// let color = Color::Hex("#f98aff".into()); /// ``` @@ -333,8 +332,6 @@ impl Message { } } -impl_json_encoder_decoder!(Message); - pub struct MessageBuilder { current: Message, root: Option, @@ -429,318 +426,323 @@ impl MessageBuilder { } } -#[test] -fn test_serialize_text_hello_world() { - let message = MessageBuilder::builder(Payload::text("Hello")) - .color(Color::Yellow) - .bold(true) - .then(Payload::text("world")) - .color(Color::Green) - .bold(true) - .italic(true) - .then(Payload::text("!")) - .color(Color::Blue) - .build(); +#[cfg(test)] +mod tests { + use crate::data::chat::{Color, Message, MessageBuilder, Payload}; - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/text_hello_world.json") - ); -} - -#[test] -fn test_deserialize_text_hello_world() { - let expected_message = MessageBuilder::builder(Payload::text("Hello")) - .color(Color::Yellow) - .bold(true) - .then(Payload::text("world")) - .color(Color::Green) - .bold(true) - .italic(true) - .then(Payload::text("!")) - .color(Color::Blue) - .build(); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/text_hello_world.json")).unwrap() - ); -} - -#[test] -fn test_serialize_translate_opped_steve() { - let with = vec![Message::new(Payload::text("Steve"))]; - let message = Message::new(Payload::translation("Opped %s", with)); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/translate_opped_steve.json") - ); -} - -#[test] -fn test_deserialize_translate_opped_steve() { - let with = vec![Message::new(Payload::text("Steve"))]; - let expected_message = Message::new(Payload::translation("Opped %s", with)); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/translate_opped_steve.json")).unwrap() - ); -} - -#[test] -fn test_serialize_keybind_jump() { - let message = MessageBuilder::builder(Payload::text("Press \"")) - .color(Color::Yellow) - .bold(true) - .then(Payload::keybind("key.jump")) - .color(Color::Blue) - .bold(false) - .underlined(true) - .then(Payload::text("\" to jump!")) - .build(); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/keybind_jump.json") - ); -} - -#[test] -fn test_deserialize_keybind_jump() { - let expected_message = MessageBuilder::builder(Payload::text("Press \"")) - .color(Color::Yellow) - .bold(true) - .then(Payload::keybind("key.jump")) - .color(Color::Blue) - .bold(false) - .underlined(true) - .then(Payload::text("\" to jump!")) - .build(); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/keybind_jump.json")).unwrap() - ); -} - -#[test] -fn test_serialize_click_open_url() { - let message = MessageBuilder::builder(Payload::text("click me")) - .color(Color::Yellow) - .bold(true) - .click_open_url("http://minecraft.net") - .build(); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/click_open_url.json") - ); -} - -#[test] -fn test_deserialize_click_open_url() { - let expected_message = MessageBuilder::builder(Payload::text("click me")) - .color(Color::Yellow) - .bold(true) - .click_open_url("http://minecraft.net") - .build(); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/click_open_url.json")).unwrap() - ); -} - -#[test] -fn test_serialize_click_run_command() { - let message = MessageBuilder::builder(Payload::text("click me")) - .color(Color::LightPurple) - .italic(true) - .click_run_command("/help") - .build(); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/click_run_command.json") - ); -} - -#[test] -fn test_deserialize_click_run_command() { - let expected_message = MessageBuilder::builder(Payload::text("click me")) - .color(Color::LightPurple) - .italic(true) - .click_run_command("/help") - .build(); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/click_run_command.json")).unwrap() - ); -} - -#[test] -fn test_serialize_click_suggest_command() { - let message = MessageBuilder::builder(Payload::text("click me")) - .color(Color::Blue) - .obfuscated(true) - .click_suggest_command("/help") - .build(); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/click_suggest_command.json") - ); -} - -#[test] -fn test_deserialize_click_suggest_command() { - let expected_message = MessageBuilder::builder(Payload::text("click me")) - .color(Color::Blue) - .obfuscated(true) - .click_suggest_command("/help") - .build(); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/click_suggest_command.json")).unwrap() - ); -} - -#[test] -fn test_serialize_click_change_page() { - let message = MessageBuilder::builder(Payload::text("click me")) - .color(Color::DarkGray) - .underlined(true) - .click_change_page("2") - .build(); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/click_change_page.json") - ); -} - -#[test] -fn test_deserialize_click_change_page() { - let expected_message = MessageBuilder::builder(Payload::text("click me")) - .color(Color::DarkGray) - .underlined(true) - .click_change_page("2") - .build(); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/click_change_page.json")).unwrap() - ); -} - -#[test] -fn test_serialize_hover_show_text() { - let message = MessageBuilder::builder(Payload::text("hover at me")) - .color(Color::DarkPurple) - .bold(true) - .hover_show_text("Herobrine behind you!") - .build(); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/hover_show_text.json") - ); -} - -#[test] -fn test_deserialize_hover_show_text() { - let expected_message = MessageBuilder::builder(Payload::text("hover at me")) - .color(Color::DarkPurple) - .bold(true) - .hover_show_text("Herobrine behind you!") - .build(); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/hover_show_text.json")).unwrap() - ); -} - -#[test] -fn test_serialize_hover_show_item() { - let message = MessageBuilder::builder(Payload::text("hover at me")) - .color(Color::DarkRed) - .italic(true) - .hover_show_item("{\"id\":\"stone\",\"Count\":1}") - .build(); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/hover_show_item.json") - ); -} - -#[test] -fn test_deserialize_hover_show_item() { - let expected_message = MessageBuilder::builder(Payload::text("hover at me")) - .color(Color::DarkRed) - .italic(true) - .hover_show_item("{\"id\":\"stone\",\"Count\":1}") - .build(); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/hover_show_item.json")).unwrap() - ); -} - -#[test] -fn test_serialize_hover_show_entity() { - let message = MessageBuilder::builder(Payload::text("hover at me")) - .color(Color::DarkAqua) - .obfuscated(true) - .hover_show_entity("{\"id\":\"7e4a61cc-83fa-4441-a299-bf69786e610a\",\"type\":\"minecraft:zombie\",\"name\":\"Zombie}\"") - .build(); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/hover_show_entity.json") - ); -} - -#[test] -fn test_deserialize_hover_show_entity() { - let expected_message = MessageBuilder::builder(Payload::text("hover at me")) - .color(Color::DarkAqua) - .obfuscated(true) - .hover_show_entity("{\"id\":\"7e4a61cc-83fa-4441-a299-bf69786e610a\",\"type\":\"minecraft:zombie\",\"name\":\"Zombie}\"") - .build(); - - assert_eq!( - expected_message, - Message::from_json(include_str!("../test/chat/hover_show_entity.json")).unwrap() - ); -} - -#[test] -fn test_serialize_hex_color() { - let message = MessageBuilder::builder(Payload::text("Hello")) - .color(Color::Hex("#ffffff".into())) - .build(); - - assert_eq!( - message.to_json().unwrap(), - include_str!("../test/chat/hex_color.json") - ); -} - -#[test] -fn test_deserialize_hex_color() { - let expected_message = MessageBuilder::builder(Payload::text("Hello")) - .color(Color::Hex("#ffffff".into())) - .build(); - - assert_eq!( - Message::from_json(include_str!("../test/chat/hex_color.json")).unwrap(), - expected_message - ); + #[test] + fn test_serialize_text_hello_world() { + let message = MessageBuilder::builder(Payload::text("Hello")) + .color(Color::Yellow) + .bold(true) + .then(Payload::text("world")) + .color(Color::Green) + .bold(true) + .italic(true) + .then(Payload::text("!")) + .color(Color::Blue) + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/text_hello_world.json") + ); + } + + #[test] + fn test_deserialize_text_hello_world() { + let expected_message = MessageBuilder::builder(Payload::text("Hello")) + .color(Color::Yellow) + .bold(true) + .then(Payload::text("world")) + .color(Color::Green) + .bold(true) + .italic(true) + .then(Payload::text("!")) + .color(Color::Blue) + .build(); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/text_hello_world.json")).unwrap() + ); + } + + #[test] + fn test_serialize_translate_opped_steve() { + let with = vec![Message::new(Payload::text("Steve"))]; + let message = Message::new(Payload::translation("Opped %s", with)); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/translate_opped_steve.json") + ); + } + + #[test] + fn test_deserialize_translate_opped_steve() { + let with = vec![Message::new(Payload::text("Steve"))]; + let expected_message = Message::new(Payload::translation("Opped %s", with)); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/translate_opped_steve.json")).unwrap() + ); + } + + #[test] + fn test_serialize_keybind_jump() { + let message = MessageBuilder::builder(Payload::text("Press \"")) + .color(Color::Yellow) + .bold(true) + .then(Payload::keybind("key.jump")) + .color(Color::Blue) + .bold(false) + .underlined(true) + .then(Payload::text("\" to jump!")) + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/keybind_jump.json") + ); + } + + #[test] + fn test_deserialize_keybind_jump() { + let expected_message = MessageBuilder::builder(Payload::text("Press \"")) + .color(Color::Yellow) + .bold(true) + .then(Payload::keybind("key.jump")) + .color(Color::Blue) + .bold(false) + .underlined(true) + .then(Payload::text("\" to jump!")) + .build(); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/keybind_jump.json")).unwrap() + ); + } + + #[test] + fn test_serialize_click_open_url() { + let message = MessageBuilder::builder(Payload::text("click me")) + .color(Color::Yellow) + .bold(true) + .click_open_url("http://minecraft.net") + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/click_open_url.json") + ); + } + + #[test] + fn test_deserialize_click_open_url() { + let expected_message = MessageBuilder::builder(Payload::text("click me")) + .color(Color::Yellow) + .bold(true) + .click_open_url("http://minecraft.net") + .build(); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/click_open_url.json")).unwrap() + ); + } + + #[test] + fn test_serialize_click_run_command() { + let message = MessageBuilder::builder(Payload::text("click me")) + .color(Color::LightPurple) + .italic(true) + .click_run_command("/help") + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/click_run_command.json") + ); + } + + #[test] + fn test_deserialize_click_run_command() { + let expected_message = MessageBuilder::builder(Payload::text("click me")) + .color(Color::LightPurple) + .italic(true) + .click_run_command("/help") + .build(); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/click_run_command.json")).unwrap() + ); + } + + #[test] + fn test_serialize_click_suggest_command() { + let message = MessageBuilder::builder(Payload::text("click me")) + .color(Color::Blue) + .obfuscated(true) + .click_suggest_command("/help") + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/click_suggest_command.json") + ); + } + + #[test] + fn test_deserialize_click_suggest_command() { + let expected_message = MessageBuilder::builder(Payload::text("click me")) + .color(Color::Blue) + .obfuscated(true) + .click_suggest_command("/help") + .build(); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/click_suggest_command.json")).unwrap() + ); + } + + #[test] + fn test_serialize_click_change_page() { + let message = MessageBuilder::builder(Payload::text("click me")) + .color(Color::DarkGray) + .underlined(true) + .click_change_page("2") + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/click_change_page.json") + ); + } + + #[test] + fn test_deserialize_click_change_page() { + let expected_message = MessageBuilder::builder(Payload::text("click me")) + .color(Color::DarkGray) + .underlined(true) + .click_change_page("2") + .build(); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/click_change_page.json")).unwrap() + ); + } + + #[test] + fn test_serialize_hover_show_text() { + let message = MessageBuilder::builder(Payload::text("hover at me")) + .color(Color::DarkPurple) + .bold(true) + .hover_show_text("Herobrine behind you!") + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/hover_show_text.json") + ); + } + + #[test] + fn test_deserialize_hover_show_text() { + let expected_message = MessageBuilder::builder(Payload::text("hover at me")) + .color(Color::DarkPurple) + .bold(true) + .hover_show_text("Herobrine behind you!") + .build(); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/hover_show_text.json")).unwrap() + ); + } + + #[test] + fn test_serialize_hover_show_item() { + let message = MessageBuilder::builder(Payload::text("hover at me")) + .color(Color::DarkRed) + .italic(true) + .hover_show_item("{\"id\":\"stone\",\"Count\":1}") + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/hover_show_item.json") + ); + } + + #[test] + fn test_deserialize_hover_show_item() { + let expected_message = MessageBuilder::builder(Payload::text("hover at me")) + .color(Color::DarkRed) + .italic(true) + .hover_show_item("{\"id\":\"stone\",\"Count\":1}") + .build(); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/hover_show_item.json")).unwrap() + ); + } + + #[test] + fn test_serialize_hover_show_entity() { + let message = MessageBuilder::builder(Payload::text("hover at me")) + .color(Color::DarkAqua) + .obfuscated(true) + .hover_show_entity("{\"id\":\"7e4a61cc-83fa-4441-a299-bf69786e610a\",\"type\":\"minecraft:zombie\",\"name\":\"Zombie}\"") + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/hover_show_entity.json") + ); + } + + #[test] + fn test_deserialize_hover_show_entity() { + let expected_message = MessageBuilder::builder(Payload::text("hover at me")) + .color(Color::DarkAqua) + .obfuscated(true) + .hover_show_entity("{\"id\":\"7e4a61cc-83fa-4441-a299-bf69786e610a\",\"type\":\"minecraft:zombie\",\"name\":\"Zombie}\"") + .build(); + + assert_eq!( + expected_message, + Message::from_json(include_str!("../test/chat/hover_show_entity.json")).unwrap() + ); + } + + #[test] + fn test_serialize_hex_color() { + let message = MessageBuilder::builder(Payload::text("Hello")) + .color(Color::Hex("#ffffff".into())) + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/hex_color.json") + ); + } + + #[test] + fn test_deserialize_hex_color() { + let expected_message = MessageBuilder::builder(Payload::text("Hello")) + .color(Color::Hex("#ffffff".into())) + .build(); + + assert_eq!( + Message::from_json(include_str!("../test/chat/hex_color.json")).unwrap(), + expected_message + ); + } } diff --git a/protocol/src/data/mod.rs b/protocol/src/data/mod.rs new file mode 100644 index 0000000..a9b9724 --- /dev/null +++ b/protocol/src/data/mod.rs @@ -0,0 +1,2 @@ +pub mod chat; +pub mod server_status; diff --git a/protocol/src/data/server_status.rs b/protocol/src/data/server_status.rs new file mode 100644 index 0000000..2751864 --- /dev/null +++ b/protocol/src/data/server_status.rs @@ -0,0 +1,29 @@ +use crate::data::chat::Message; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, Debug)] +pub struct ServerStatus { + pub version: ServerVersion, + pub players: OnlinePlayers, + pub description: Message, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ServerVersion { + pub name: String, + pub protocol: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct OnlinePlayers { + pub max: u32, + pub online: u32, + pub sample: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct OnlinePlayer { + pub name: String, + pub id: Uuid, +} diff --git a/protocol/src/decoder.rs b/protocol/src/decoder.rs new file mode 100644 index 0000000..0af4536 --- /dev/null +++ b/protocol/src/decoder.rs @@ -0,0 +1,269 @@ +use crate::error::DecodeError; +use byteorder::{BigEndian, ReadBytesExt}; +use nbt::CompoundTag; +use num_traits::FromPrimitive; +use std::io::Read; +use uuid::Uuid; + +trait Decoder { + type Output; + + fn decode(reader: &mut R) -> Result; +} + +/// Trait adds additional helper methods for `Read` to read protocol data. +trait DecoderReadExt { + fn read_bool(&mut self) -> Result; + + fn read_string(&mut self, max_length: u16) -> Result; + + fn read_byte_array(&mut self) -> Result, DecodeError>; + + fn read_enum(&mut self) -> Result; + + fn read_compound_tag(&mut self) -> Result; + + fn read_var_i32(&mut self) -> Result; + + fn read_var_i64(&mut self) -> Result; +} + +macro_rules! read_signed_var_int ( + ($type: ident, $name: ident, $max_bytes: expr) => ( + fn $name(&mut self) -> Result<$type, DecodeError> { + let mut bytes = 0; + let mut output = 0; + + loop { + let byte = self.read_u8()?; + let value = (byte & 0b01111111) as $type; + + output |= value << 7 * bytes; + bytes += 1; + + if bytes > $max_bytes { + return Err(DecodeError::VarIntTooLong { max_bytes: $max_bytes }) + } + + if (byte & 0b10000000) == 0 { + break; + } + } + + Ok(output) + } + ); +); + +impl DecoderReadExt for R { + fn read_bool(&mut self) -> Result { + match self.read_u8()? { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(DecodeError::NonBoolValue), + } + } + + fn read_string(&mut self, max_length: u16) -> Result { + let length = self.read_var_i32()? as usize; + + if length as u16 > max_length { + return Err(DecodeError::StringTooLong { length, max_length }); + } + + let mut buf = vec![0; length as usize]; + self.read_exact(&mut buf)?; + + Ok(String::from_utf8(buf)?) + } + + fn read_byte_array(&mut self) -> Result, DecodeError> { + let length = self.read_var_i32()?; + + let mut buf = vec![0; length as usize]; + self.read_exact(&mut buf)?; + + Ok(buf) + } + + fn read_enum(&mut self) -> Result { + let type_id = self.read_u8()?; + let result = FromPrimitive::from_u8(type_id); + + result.ok_or_else(|| DecodeError::UnknownEnumType { type_id }) + } + + fn read_compound_tag(&mut self) -> Result { + Ok(nbt::decode::read_compound_tag(self)?) + } + + read_signed_var_int!(i32, read_var_i32, 5); + read_signed_var_int!(i64, read_var_i64, 10); +} + +impl Decoder for u8 { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_u8()?) + } +} + +impl Decoder for i32 { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_i32::()?) + } +} + +impl Decoder for u32 { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_u32::()?) + } +} + +impl Decoder for i64 { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_i64::()?) + } +} + +impl Decoder for u64 { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_u64::()?) + } +} + +impl Decoder for String { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_string(32_768)?) + } +} + +impl Decoder for bool { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_bool()?) + } +} + +impl Decoder for Vec { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_byte_array()?) + } +} + +impl Decoder for Uuid { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + let mut buf = [0; 16]; + reader.read_exact(&mut buf)?; + + Ok(Uuid::from_bytes(buf)) + } +} + +impl Decoder for CompoundTag { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_compound_tag()?) + } +} + +impl Decoder for Vec { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + let length = reader.read_var_i32()? as usize; + let mut vec = Vec::with_capacity(length); + + for _ in 0..length { + let compound_tag = reader.read_compound_tag()?; + vec.push(compound_tag); + } + + Ok(vec) + } +} + +mod var_int { + use crate::decoder::DecoderReadExt; + use crate::error::DecodeError; + use std::io::Read; + + pub fn decode(reader: &mut R) -> Result { + Ok(reader.read_var_i32()?) + } +} + +mod var_long { + use crate::decoder::DecoderReadExt; + use crate::error::DecodeError; + use std::io::Read; + + pub fn decode(reader: &mut R) -> Result { + Ok(reader.read_var_i64()?) + } +} + +mod rest { + use crate::error::DecodeError; + use std::io::Read; + + pub fn decode(reader: &mut R) -> Result, DecodeError> { + let mut data = Vec::new(); + reader.read_to_end(data.as_mut())?; + + Ok(data) + } +} + +mod uuid_hyp_str { + use crate::decoder::DecoderReadExt; + use crate::error::DecodeError; + use std::io::Read; + use uuid::Uuid; + + pub fn decode(reader: &mut R) -> Result { + let uuid_hyphenated_string = reader.read_string(36)?; + let uuid = Uuid::parse_str(&uuid_hyphenated_string)?; + + Ok(uuid) + } +} + +#[cfg(test)] +mod tests { + use crate::decoder::DecoderReadExt; + use std::io::Cursor; + + #[test] + fn test_read_variable_i32_2_bytes_value() { + let mut cursor = Cursor::new(vec![0b10101100, 0b00000010]); + let value = cursor.read_var_i32().unwrap(); + + assert_eq!(value, 300); + } + + #[test] + fn test_read_variable_i32_5_bytes_value() { + let mut cursor = Cursor::new(vec![0xff, 0xff, 0xff, 0xff, 0x07]); + let value = cursor.read_var_i32().unwrap(); + + assert_eq!(value, 2147483647); + } +} diff --git a/protocol/src/encoder.rs b/protocol/src/encoder.rs new file mode 100644 index 0000000..d317d93 --- /dev/null +++ b/protocol/src/encoder.rs @@ -0,0 +1,241 @@ +use crate::error::EncodeError; +use byteorder::{BigEndian, WriteBytesExt}; +use nbt::CompoundTag; +use num_traits::ToPrimitive; +use std::io::Write; +use uuid::Uuid; + +trait Encoder { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError>; +} + +/// Trait adds additional helper methods for `Write` to write protocol data. +trait EncoderWriteExt { + fn write_bool(&mut self, value: bool) -> Result<(), EncodeError>; + + fn write_string(&mut self, value: &str, max_length: u16) -> Result<(), EncodeError>; + + fn write_byte_array(&mut self, value: &[u8]) -> Result<(), EncodeError>; + + fn write_enum(&mut self, value: &T) -> Result<(), EncodeError>; + + fn write_compound_tag(&mut self, value: &CompoundTag) -> Result<(), EncodeError>; + + fn write_var_i32(&mut self, value: i32) -> Result<(), EncodeError>; + + fn write_var_i64(&mut self, value: i64) -> Result<(), EncodeError>; +} + +macro_rules! write_signed_var_int ( + ($type: ident, $name: ident) => ( + fn $name(&mut self, mut value: $type) -> Result<(), EncodeError> { + loop { + let mut byte = (value & 0b01111111) as u8; + value = value >> 7; + + if value != 0 { + byte |= 0b10000000; + } + + self.write_u8(byte)?; + + if value == 0 { + break; + } + } + + Ok(()) + } + ) +); + +impl EncoderWriteExt for W { + fn write_bool(&mut self, value: bool) -> Result<(), EncodeError> { + if value { + self.write_u8(1)?; + } else { + self.write_u8(0)?; + } + + Ok(()) + } + + fn write_string(&mut self, value: &str, max_length: u16) -> Result<(), EncodeError> { + let length = value.len(); + + if length > max_length as usize { + return Err(EncodeError::StringTooLong { length, max_length }); + } + + self.write_var_i32(value.len() as i32)?; + self.write_all(value.as_bytes())?; + + Ok(()) + } + + fn write_byte_array(&mut self, value: &[u8]) -> Result<(), EncodeError> { + self.write_var_i32(value.len() as i32)?; + self.write_all(value)?; + + Ok(()) + } + + fn write_enum(&mut self, value: &T) -> Result<(), EncodeError> { + let type_value = ToPrimitive::to_u8(value).unwrap(); + self.write_u8(type_value)?; + + Ok(()) + } + + fn write_compound_tag(&mut self, value: &CompoundTag) -> Result<(), EncodeError> { + nbt::encode::write_compound_tag(self, value.clone())?; + + Ok(()) + } + + write_signed_var_int!(i32, write_var_i32); + write_signed_var_int!(i64, write_var_i64); +} + +impl Encoder for u8 { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_u8(*self)?) + } +} + +impl Encoder for i32 { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_i32::(*self)?) + } +} + +impl Encoder for u32 { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_u32::(*self)?) + } +} + +impl Encoder for i64 { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_i64::(*self)?) + } +} + +impl Encoder for u64 { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_u64::(*self)?) + } +} + +impl Encoder for String { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_string(self, 32_768)?) + } +} + +impl Encoder for bool { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_bool(*self)?) + } +} + +impl Encoder for Vec { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_byte_array(self)?) + } +} + +impl Encoder for Uuid { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_all(self.as_bytes())?) + } +} + +impl Encoder for CompoundTag { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_compound_tag(self)?) + } +} + +impl Encoder for Vec { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + writer.write_var_i32(self.len() as i32)?; + + for compound_tag in self { + writer.write_compound_tag(&compound_tag)?; + } + + Ok(()) + } +} + +mod var_int { + use crate::encoder::EncoderWriteExt; + use crate::error::EncodeError; + use std::io::Write; + + pub fn encode(value: &i32, writer: &mut W) -> Result<(), EncodeError> { + writer.write_var_i32(*value)?; + + Ok(()) + } +} + +mod var_long { + use crate::encoder::EncoderWriteExt; + use crate::error::EncodeError; + use std::io::Write; + + pub fn encode(value: &i64, writer: &mut W) -> Result<(), EncodeError> { + writer.write_var_i64(*value)?; + + Ok(()) + } +} + +mod rest { + use crate::error::EncodeError; + use std::io::Write; + + pub fn encode(value: &[u8], writer: &mut W) -> Result<(), EncodeError> { + writer.write_all(value)?; + + Ok(()) + } +} + +mod uuid_hyp_str { + use crate::encoder::EncoderWriteExt; + use crate::error::EncodeError; + use std::io::Write; + use uuid::Uuid; + + pub fn encode(value: &Uuid, writer: &mut W) -> Result<(), EncodeError> { + let uuid_hyphenated_string = value.to_hyphenated().to_string(); + writer.write_string(&uuid_hyphenated_string, 36)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::encoder::EncoderWriteExt; + use std::io::Cursor; + + #[test] + fn test_write_variable_i32_2_bytes_value() { + let mut cursor = Cursor::new(Vec::with_capacity(5)); + cursor.write_var_i32(300).unwrap(); + + assert_eq!(cursor.into_inner(), vec![0b10101100, 0b00000010]); + } + + #[test] + fn test_write_variable_i32_5_bytes_value() { + let mut cursor = Cursor::new(Vec::with_capacity(5)); + cursor.write_var_i32(2147483647).unwrap(); + + assert_eq!(cursor.into_inner(), vec![0xff, 0xff, 0xff, 0xff, 0x07]); + } +} diff --git a/protocol/src/error.rs b/protocol/src/error.rs new file mode 100644 index 0000000..0f80953 --- /dev/null +++ b/protocol/src/error.rs @@ -0,0 +1,106 @@ +use nbt::decode::TagDecodeError; +use serde_json::error::Error as JsonError; +use std::io::Error as IoError; +use std::string::FromUtf8Error; +use uuid::parser::ParseError as UuidParseError; + +/// Possible errors while encoding packet. +#[derive(Debug)] +pub enum EncodeError { + /// String length can't be more than provided value. + StringTooLong { + /// String length. + length: usize, + /// Max string length. + max_length: u16, + }, + IOError { + io_error: IoError, + }, + JsonError { + json_error: JsonError, + }, +} + +impl From for EncodeError { + fn from(io_error: IoError) -> Self { + EncodeError::IOError { io_error } + } +} + +impl From for EncodeError { + fn from(json_error: JsonError) -> Self { + EncodeError::JsonError { json_error } + } +} + +/// Possible errors while decoding packet. +#[derive(Debug)] +pub enum DecodeError { + /// Packet was not recognized. Invalid data or wrong protocol version. + UnknownPacketType { + type_id: u8, + }, + /// String length can't be more than provided value. + StringTooLong { + /// String length. + length: usize, + /// Max string length. + max_length: u16, + }, + IOError { + io_error: IoError, + }, + JsonError { + json_error: JsonError, + }, + /// Byte array was not recognized as valid UTF-8 string. + Utf8Error { + utf8_error: FromUtf8Error, + }, + /// Boolean are parsed from byte. Valid byte value are 0 or 1. + NonBoolValue, + UuidParseError { + uuid_parse_error: UuidParseError, + }, + /// Type id was not parsed as valid enum value. + UnknownEnumType { + type_id: u8, + }, + TagDecodeError { + tag_decode_error: TagDecodeError, + }, + VarIntTooLong { + max_bytes: usize, + }, +} + +impl From for DecodeError { + fn from(io_error: IoError) -> Self { + DecodeError::IOError { io_error } + } +} + +impl From for DecodeError { + fn from(json_error: JsonError) -> Self { + DecodeError::JsonError { json_error } + } +} + +impl From for DecodeError { + fn from(utf8_error: FromUtf8Error) -> Self { + DecodeError::Utf8Error { utf8_error } + } +} + +impl From for DecodeError { + fn from(uuid_parse_error: UuidParseError) -> Self { + DecodeError::UuidParseError { uuid_parse_error } + } +} + +impl From for DecodeError { + fn from(tag_decode_error: TagDecodeError) -> Self { + DecodeError::TagDecodeError { tag_decode_error } + } +} diff --git a/protocol/src/game.rs b/protocol/src/game.rs deleted file mode 100644 index e5d32ca..0000000 --- a/protocol/src/game.rs +++ /dev/null @@ -1,504 +0,0 @@ -use num_derive::{FromPrimitive, ToPrimitive}; - -use crate::chat::Message; -use crate::impl_enum_encoder_decoder; -use crate::DecodeError; -use crate::Decoder; -use minecraft_protocol_derive::Packet; -use nbt::CompoundTag; -use std::io::Read; - -pub enum GameServerBoundPacket { - ServerBoundChatMessage(ServerBoundChatMessage), - ServerBoundKeepAlive(ServerBoundKeepAlive), -} - -pub enum GameClientBoundPacket { - ClientBoundChatMessage(ClientBoundChatMessage), - JoinGame(JoinGame), - ClientBoundKeepAlive(ClientBoundKeepAlive), - ChunkData(ChunkData), - GameDisconnect(GameDisconnect), -} - -impl GameServerBoundPacket { - pub fn get_type_id(&self) -> u8 { - match self { - GameServerBoundPacket::ServerBoundChatMessage(_) => 0x03, - GameServerBoundPacket::ServerBoundKeepAlive(_) => 0x0F, - } - } - - pub fn decode(type_id: u8, reader: &mut R) -> Result { - match type_id { - 0x03 => { - let chat_message = ServerBoundChatMessage::decode(reader)?; - - Ok(GameServerBoundPacket::ServerBoundChatMessage(chat_message)) - } - 0x0F => { - let keep_alive = ServerBoundKeepAlive::decode(reader)?; - - Ok(GameServerBoundPacket::ServerBoundKeepAlive(keep_alive)) - } - _ => Err(DecodeError::UnknownPacketType { type_id }), - } - } -} - -impl GameClientBoundPacket { - pub fn get_type_id(&self) -> u8 { - match self { - GameClientBoundPacket::ClientBoundChatMessage(_) => 0x0E, - GameClientBoundPacket::GameDisconnect(_) => 0x1A, - GameClientBoundPacket::ClientBoundKeepAlive(_) => 0x20, - GameClientBoundPacket::ChunkData(_) => 0x21, - GameClientBoundPacket::JoinGame(_) => 0x25, - } - } - - pub fn decode(type_id: u8, reader: &mut R) -> Result { - match type_id { - 0x0E => { - let chat_message = ClientBoundChatMessage::decode(reader)?; - - Ok(GameClientBoundPacket::ClientBoundChatMessage(chat_message)) - } - 0x1A => { - let game_disconnect = GameDisconnect::decode(reader)?; - - Ok(GameClientBoundPacket::GameDisconnect(game_disconnect)) - } - 0x20 => { - let keep_alive = ClientBoundKeepAlive::decode(reader)?; - - Ok(GameClientBoundPacket::ClientBoundKeepAlive(keep_alive)) - } - 0x21 => { - let chunk_data = ChunkData::decode(reader)?; - - Ok(GameClientBoundPacket::ChunkData(chunk_data)) - } - 0x25 => { - let join_game = JoinGame::decode(reader)?; - - Ok(GameClientBoundPacket::JoinGame(join_game)) - } - _ => Err(DecodeError::UnknownPacketType { type_id }), - } - } -} - -#[derive(Packet, Debug)] -pub struct ServerBoundChatMessage { - #[packet(max_length = 256)] - pub message: String, -} - -impl ServerBoundChatMessage { - pub fn new(message: String) -> GameServerBoundPacket { - let chat_message = ServerBoundChatMessage { message }; - - GameServerBoundPacket::ServerBoundChatMessage(chat_message) - } -} - -#[derive(Packet, Debug)] -pub struct ClientBoundChatMessage { - pub message: Message, - pub position: MessagePosition, -} - -#[derive(Debug, Eq, PartialEq, FromPrimitive, ToPrimitive)] -pub enum MessagePosition { - Chat, - System, - HotBar, -} - -impl_enum_encoder_decoder!(MessagePosition); - -impl ClientBoundChatMessage { - pub fn new(message: Message, position: MessagePosition) -> GameClientBoundPacket { - let chat_message = ClientBoundChatMessage { message, position }; - - GameClientBoundPacket::ClientBoundChatMessage(chat_message) - } -} - -#[derive(Packet, Debug)] -pub struct JoinGame { - pub entity_id: u32, - pub game_mode: GameMode, - pub dimension: i32, - pub max_players: u8, - #[packet(max_length = 16)] - pub level_type: String, - #[packet(with = "var_int")] - pub view_distance: i32, - pub reduced_debug_info: bool, -} - -#[derive(Debug, Eq, PartialEq, FromPrimitive, ToPrimitive)] -pub enum GameMode { - Survival = 0, - Creative = 1, - Adventure = 2, - Spectator = 3, - Hardcore = 8, -} - -impl_enum_encoder_decoder!(GameMode); - -impl JoinGame { - pub fn new( - entity_id: u32, - game_mode: GameMode, - dimension: i32, - max_players: u8, - level_type: String, - view_distance: i32, - reduced_debug_info: bool, - ) -> GameClientBoundPacket { - let join_game = JoinGame { - entity_id, - game_mode, - dimension, - max_players, - level_type, - view_distance, - reduced_debug_info, - }; - - GameClientBoundPacket::JoinGame(join_game) - } -} - -#[derive(Packet)] -pub struct ServerBoundKeepAlive { - pub id: u64, -} - -impl ServerBoundKeepAlive { - pub fn new(id: u64) -> GameServerBoundPacket { - let keep_alive = ServerBoundKeepAlive { id }; - - GameServerBoundPacket::ServerBoundKeepAlive(keep_alive) - } -} - -#[derive(Packet)] -pub struct ClientBoundKeepAlive { - pub id: u64, -} - -impl ClientBoundKeepAlive { - pub fn new(id: u64) -> GameClientBoundPacket { - let keep_alive = ClientBoundKeepAlive { id }; - - GameClientBoundPacket::ClientBoundKeepAlive(keep_alive) - } -} - -#[derive(Packet, Debug)] -pub struct ChunkData { - pub x: i32, - pub z: i32, - pub full: bool, - #[packet(with = "var_int")] - pub primary_mask: i32, - pub heights: CompoundTag, - pub data: Vec, - pub tiles: Vec, -} - -impl ChunkData { - pub fn new( - x: i32, - z: i32, - full: bool, - primary_mask: i32, - heights: CompoundTag, - data: Vec, - tiles: Vec, - ) -> GameClientBoundPacket { - let chunk_data = ChunkData { - x, - z, - full, - primary_mask, - heights, - data, - tiles, - }; - - GameClientBoundPacket::ChunkData(chunk_data) - } -} - -#[derive(Packet, Debug)] -pub struct GameDisconnect { - pub reason: Message, -} - -impl GameDisconnect { - pub fn new(reason: Message) -> GameClientBoundPacket { - let game_disconnect = GameDisconnect { reason }; - - GameClientBoundPacket::GameDisconnect(game_disconnect) - } -} - -#[cfg(test)] -mod tests { - use crate::chat::{Message, Payload}; - use crate::game::{ - ChunkData, ClientBoundChatMessage, ClientBoundKeepAlive, GameDisconnect, GameMode, - JoinGame, MessagePosition, ServerBoundChatMessage, ServerBoundKeepAlive, - }; - use crate::{DecodeError, Encoder, EncoderWriteExt, STRING_MAX_LENGTH}; - use crate::{Decoder, EncodeError}; - use nbt::CompoundTag; - use std::io::Cursor; - - #[test] - fn test_server_bound_chat_message_encode() { - let chat_message = ServerBoundChatMessage { - message: String::from("hello server!"), - }; - - let mut vec = Vec::new(); - chat_message.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/game/server_bound_chat_message.dat").to_vec() - ); - } - - #[test] - fn test_server_bound_chat_message_decode() { - let mut cursor = Cursor::new( - include_bytes!("../test/packet/game/server_bound_chat_message.dat").to_vec(), - ); - let chat_message = ServerBoundChatMessage::decode(&mut cursor).unwrap(); - - assert_eq!(chat_message.message, "hello server!"); - } - - #[test] - fn test_server_bound_chat_message_encode_invalid_length() { - let chat_message = ServerBoundChatMessage { - message: "abc".repeat(100), - }; - - let mut vec = Vec::new(); - - let encode_error = chat_message - .encode(&mut vec) - .err() - .expect("Expected error `StringTooLong` because message has invalid length"); - - match encode_error { - EncodeError::StringTooLong { length, max_length } => { - assert_eq!(length, 300); - assert_eq!(max_length, 256); - } - _ => panic!("Expected `StringTooLong` but got `{:?}`", encode_error), - } - } - - #[test] - fn test_server_bound_chat_message_decode_invalid_length() { - let message = "abc".repeat(100); - - let mut vec = Vec::new(); - vec.write_string(&message, STRING_MAX_LENGTH).unwrap(); - - let mut cursor = Cursor::new(vec); - - let decode_error = ServerBoundChatMessage::decode(&mut cursor) - .err() - .expect("Expected error `StringTooLong` because message has invalid length"); - - match decode_error { - DecodeError::StringTooLong { length, max_length } => { - assert_eq!(length, 300); - assert_eq!(max_length, 256); - } - _ => panic!("Expected `StringTooLong` but got `{:?}`", decode_error), - } - } - - #[test] - fn test_client_bound_chat_message_encode() { - let chat_message = ClientBoundChatMessage { - message: Message::new(Payload::text("hello client!")), - position: MessagePosition::System, - }; - - let mut vec = Vec::new(); - chat_message.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/game/client_bound_chat_message.dat").to_vec() - ); - } - - #[test] - fn test_client_bound_chat_message_decode() { - let mut cursor = Cursor::new( - include_bytes!("../test/packet/game/client_bound_chat_message.dat").to_vec(), - ); - let chat_message = ClientBoundChatMessage::decode(&mut cursor).unwrap(); - - assert_eq!( - chat_message.message, - Message::new(Payload::text("hello client!")) - ); - - assert_eq!(chat_message.position, MessagePosition::System); - } - - #[test] - fn test_server_bound_keep_alive_encode() { - let keep_alive = ServerBoundKeepAlive { id: 31122019 }; - - let mut vec = Vec::new(); - keep_alive.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/game/server_bound_keep_alive.dat").to_vec() - ); - } - - #[test] - fn test_server_bound_keep_alive_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/game/server_bound_keep_alive.dat").to_vec()); - let keep_alive = ServerBoundKeepAlive::decode(&mut cursor).unwrap(); - - assert_eq!(keep_alive.id, 31122019); - } - - #[test] - fn test_client_bound_keep_alive_encode() { - let keep_alive = ClientBoundKeepAlive { id: 240714 }; - - let mut vec = Vec::new(); - keep_alive.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/game/client_bound_keep_alive.dat").to_vec() - ); - } - - #[test] - fn test_client_bound_keep_alive_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/game/client_bound_keep_alive.dat").to_vec()); - let keep_alive = ClientBoundKeepAlive::decode(&mut cursor).unwrap(); - - assert_eq!(keep_alive.id, 240714); - } - - #[test] - fn test_join_game_encode() { - let join_game = JoinGame { - entity_id: 27, - game_mode: GameMode::Spectator, - dimension: 23, - max_players: 100, - level_type: String::from("default"), - view_distance: 10, - reduced_debug_info: true, - }; - - let mut vec = Vec::new(); - join_game.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/game/join_game.dat").to_vec() - ); - } - - #[test] - fn test_join_game_decode() { - let mut cursor = Cursor::new(include_bytes!("../test/packet/game/join_game.dat").to_vec()); - let join_game = JoinGame::decode(&mut cursor).unwrap(); - - assert_eq!(join_game.entity_id, 27); - assert_eq!(join_game.game_mode, GameMode::Spectator); - assert_eq!(join_game.dimension, 23); - assert_eq!(join_game.max_players, 100); - assert_eq!(join_game.level_type, String::from("default")); - assert_eq!(join_game.view_distance, 10); - assert!(join_game.reduced_debug_info); - } - - #[test] - fn test_chunk_data_encode() { - let chunk_data = ChunkData { - x: -2, - z: 5, - full: true, - primary_mask: 65535, - heights: CompoundTag::named("HeightMaps"), - data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - tiles: vec![CompoundTag::named("TileEntity")], - }; - - let mut vec = Vec::new(); - chunk_data.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/game/chunk_data.dat").to_vec() - ); - } - - #[test] - fn test_chunk_data_decode() { - let mut cursor = Cursor::new(include_bytes!("../test/packet/game/chunk_data.dat").to_vec()); - let chunk_data = ChunkData::decode(&mut cursor).unwrap(); - - assert_eq!(chunk_data.x, -2); - assert_eq!(chunk_data.z, 5); - assert!(chunk_data.full); - assert_eq!(chunk_data.heights.name, Some(String::from("HeightMaps"))); - assert_eq!(chunk_data.data, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - assert_eq!(chunk_data.primary_mask, 65535); - assert_eq!(chunk_data.tiles[0].name, Some(String::from("TileEntity"))); - } - - #[test] - fn test_game_disconnect_encode() { - let game_disconnect = GameDisconnect { - reason: Message::new(Payload::text("Message")), - }; - - let mut vec = Vec::new(); - game_disconnect.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/game/game_disconnect.dat").to_vec() - ); - } - - #[test] - fn test_game_disconnect_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/game/game_disconnect.dat").to_vec()); - let game_disconnect = GameDisconnect::decode(&mut cursor).unwrap(); - - assert_eq!( - game_disconnect.reason, - Message::new(Payload::text("Message")) - ); - } -} diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index fa0aa78..e96e047 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -1,650 +1,8 @@ //! This crate implements Minecraft protocol. //! //! Information about protocol can be found at https://wiki.vg/Protocol. -use io::Error as IoError; -use std::io; -use std::io::{Cursor, Read, Write}; -use std::string::FromUtf8Error; - -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use serde_json::error::Error as JsonError; -use uuid::parser::ParseError as UuidParseError; - -use crate::chat::Message; -use nbt::decode::TagDecodeError; -use nbt::CompoundTag; -use num_traits::{FromPrimitive, ToPrimitive}; -use uuid::Uuid; - -pub mod chat; -pub mod game; -pub mod login; -pub mod status; - -/// Current supported protocol version. -pub const PROTOCOL_VERSION: u32 = 498; -/// Protocol limits maximum string length. -const STRING_MAX_LENGTH: u16 = 32_768; -const HYPHENATED_UUID_LENGTH: u16 = 36; - -/// Possible errors while encoding packet. -#[derive(Debug)] -pub enum EncodeError { - /// String length can't be more than provided value. - StringTooLong { - /// String length. - length: usize, - /// Max string length. - max_length: u16, - }, - IOError { - io_error: IoError, - }, - JsonError { - json_error: JsonError, - }, -} - -impl From for EncodeError { - fn from(io_error: IoError) -> Self { - EncodeError::IOError { io_error } - } -} - -impl From for EncodeError { - fn from(json_error: JsonError) -> Self { - EncodeError::JsonError { json_error } - } -} - -/// Possible errors while decoding packet. -#[derive(Debug)] -pub enum DecodeError { - /// Packet was not recognized. Invalid data or wrong protocol version. - UnknownPacketType { - type_id: u8, - }, - /// String length can't be more than provided value. - StringTooLong { - /// String length. - length: usize, - /// Max string length. - max_length: u16, - }, - IOError { - io_error: IoError, - }, - JsonError { - json_error: JsonError, - }, - /// Byte array was not recognized as valid UTF-8 string. - Utf8Error { - utf8_error: FromUtf8Error, - }, - /// Boolean are parsed from byte. Valid byte value are 0 or 1. - NonBoolValue, - UuidParseError { - uuid_parse_error: UuidParseError, - }, - // Type id was not parsed as valid enum value. - UnknownEnumType { - type_id: u8, - }, - TagDecodeError { - tag_decode_error: TagDecodeError, - }, - VarIntTooLong { - max_bytes: usize, - }, -} - -impl From for DecodeError { - fn from(io_error: IoError) -> Self { - DecodeError::IOError { io_error } - } -} - -impl From for DecodeError { - fn from(json_error: JsonError) -> Self { - DecodeError::JsonError { json_error } - } -} - -impl From for DecodeError { - fn from(utf8_error: FromUtf8Error) -> Self { - DecodeError::Utf8Error { utf8_error } - } -} - -impl From for DecodeError { - fn from(uuid_parse_error: UuidParseError) -> Self { - DecodeError::UuidParseError { uuid_parse_error } - } -} - -impl From for DecodeError { - fn from(tag_decode_error: TagDecodeError) -> Self { - DecodeError::TagDecodeError { tag_decode_error } - } -} - -trait Encoder { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError>; -} - -trait Decoder { - type Output; - - fn decode(reader: &mut R) -> Result; -} - -macro_rules! write_signed_var_int ( - ($type: ident, $name: ident) => ( - fn $name(&mut self, mut value: $type) -> Result<(), EncodeError> { - loop { - let mut byte = (value & 0b01111111) as u8; - value = value >> 7; - - if value != 0 { - byte |= 0b10000000; - } - - self.write_u8(byte)?; - - if value == 0 { - break; - } - } - - Ok(()) - } - ) -); - -macro_rules! read_signed_var_int ( - ($type: ident, $name: ident, $max_bytes: expr) => ( - fn $name(&mut self) -> Result<$type, DecodeError> { - let mut bytes = 0; - let mut output = 0; - - loop { - let byte = self.read_u8()?; - let value = (byte & 0b01111111) as $type; - - output |= value << 7 * bytes; - bytes += 1; - - if bytes > $max_bytes { - return Err(DecodeError::VarIntTooLong { max_bytes: $max_bytes }) - } - - if (byte & 0b10000000) == 0 { - break; - } - } - - Ok(output) - } - ); -); - -/// Trait adds additional helper methods for `Write` to write protocol data. -trait EncoderWriteExt { - fn write_bool(&mut self, value: bool) -> Result<(), EncodeError>; - - fn write_string(&mut self, value: &str, max_length: u16) -> Result<(), EncodeError>; - - fn write_byte_array(&mut self, value: &[u8]) -> Result<(), EncodeError>; - - fn write_chat_message(&mut self, value: &Message) -> Result<(), EncodeError>; - - fn write_enum(&mut self, value: &T) -> Result<(), EncodeError>; - - fn write_compound_tag(&mut self, value: &CompoundTag) -> Result<(), EncodeError>; - - fn write_var_i32(&mut self, value: i32) -> Result<(), EncodeError>; - - fn write_var_i64(&mut self, value: i64) -> Result<(), EncodeError>; -} - -/// Trait adds additional helper methods for `Read` to read protocol data. -trait DecoderReadExt { - fn read_bool(&mut self) -> Result; - - fn read_string(&mut self, max_length: u16) -> Result; - - fn read_byte_array(&mut self) -> Result, DecodeError>; - - fn read_chat_message(&mut self) -> Result; - - fn read_enum(&mut self) -> Result; - - fn read_compound_tag(&mut self) -> Result; - - fn read_var_i32(&mut self) -> Result; - - fn read_var_i64(&mut self) -> Result; -} - -impl EncoderWriteExt for W { - fn write_bool(&mut self, value: bool) -> Result<(), EncodeError> { - if value { - self.write_u8(1)?; - } else { - self.write_u8(0)?; - } - - Ok(()) - } - - fn write_string(&mut self, value: &str, max_length: u16) -> Result<(), EncodeError> { - let length = value.len(); - - if length > max_length as usize { - return Err(EncodeError::StringTooLong { length, max_length }); - } - - self.write_var_i32(value.len() as i32)?; - self.write_all(value.as_bytes())?; - - Ok(()) - } - - fn write_byte_array(&mut self, value: &[u8]) -> Result<(), EncodeError> { - self.write_var_i32(value.len() as i32)?; - self.write_all(value)?; - - Ok(()) - } - - fn write_chat_message(&mut self, value: &Message) -> Result<(), EncodeError> { - self.write_string(&value.to_json()?, STRING_MAX_LENGTH) - } - - fn write_enum(&mut self, value: &T) -> Result<(), EncodeError> { - let type_value = ToPrimitive::to_u8(value).unwrap(); - self.write_u8(type_value)?; - - Ok(()) - } - - fn write_compound_tag(&mut self, value: &CompoundTag) -> Result<(), EncodeError> { - nbt::encode::write_compound_tag(self, value.clone())?; - - Ok(()) - } - - write_signed_var_int!(i32, write_var_i32); - write_signed_var_int!(i64, write_var_i64); -} - -impl DecoderReadExt for R { - fn read_bool(&mut self) -> Result { - match self.read_u8()? { - 0 => Ok(false), - 1 => Ok(true), - _ => Err(DecodeError::NonBoolValue), - } - } - - fn read_string(&mut self, max_length: u16) -> Result { - let length = self.read_var_i32()? as usize; - - if length as u16 > max_length { - return Err(DecodeError::StringTooLong { length, max_length }); - } - - let mut buf = vec![0; length as usize]; - self.read_exact(&mut buf)?; - - Ok(String::from_utf8(buf)?) - } - - fn read_byte_array(&mut self) -> Result, DecodeError> { - let length = self.read_var_i32()?; - - let mut buf = vec![0; length as usize]; - self.read_exact(&mut buf)?; - - Ok(buf) - } - - fn read_chat_message(&mut self) -> Result { - let json = self.read_string(STRING_MAX_LENGTH)?; - let message = Message::from_json(&json)?; - - Ok(message) - } - - fn read_enum(&mut self) -> Result { - let type_id = self.read_u8()?; - let result = FromPrimitive::from_u8(type_id); - - result.ok_or_else(|| DecodeError::UnknownEnumType { type_id }) - } - - fn read_compound_tag(&mut self) -> Result { - Ok(nbt::decode::read_compound_tag(self)?) - } - - read_signed_var_int!(i32, read_var_i32, 5); - read_signed_var_int!(i64, read_var_i64, 10); -} - -impl Encoder for u8 { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_u8(*self)?) - } -} - -impl Decoder for u8 { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_u8()?) - } -} - -impl Encoder for i32 { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_i32::(*self)?) - } -} - -impl Decoder for i32 { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_i32::()?) - } -} - -impl Encoder for u32 { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_u32::(*self)?) - } -} - -impl Decoder for u32 { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_u32::()?) - } -} - -impl Encoder for i64 { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_i64::(*self)?) - } -} - -impl Decoder for i64 { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_i64::()?) - } -} - -impl Encoder for u64 { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_u64::(*self)?) - } -} - -impl Decoder for u64 { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_u64::()?) - } -} - -impl Encoder for String { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_string(self, STRING_MAX_LENGTH)?) - } -} - -impl Decoder for String { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_string(STRING_MAX_LENGTH)?) - } -} - -impl Encoder for bool { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_bool(*self)?) - } -} - -impl Decoder for bool { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_bool()?) - } -} - -impl Encoder for Vec { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_byte_array(self)?) - } -} - -impl Decoder for Vec { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_byte_array()?) - } -} - -impl Encoder for Uuid { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_all(self.as_bytes())?) - } -} - -impl Decoder for Uuid { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - let mut buf = [0; 16]; - reader.read_exact(&mut buf)?; - - Ok(Uuid::from_bytes(buf)) - } -} - -impl Encoder for CompoundTag { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_compound_tag(self)?) - } -} - -impl Decoder for CompoundTag { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_compound_tag()?) - } -} - -impl Encoder for Vec { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_var_i32(self.len() as i32)?; - - for compound_tag in self { - writer.write_compound_tag(&compound_tag)?; - } - - Ok(()) - } -} - -impl Decoder for Vec { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - let length = reader.read_var_i32()? as usize; - let mut vec = Vec::with_capacity(length); - - for _ in 0..length { - let compound_tag = reader.read_compound_tag()?; - vec.push(compound_tag); - } - - Ok(vec) - } -} - -#[macro_export] -macro_rules! impl_enum_encoder_decoder ( - ($ty: ident) => ( - impl crate::Encoder for $ty { - fn encode(&self, writer: &mut W) -> Result<(), crate::EncodeError> { - Ok(crate::EncoderWriteExt::write_enum(writer, self)?) - } - } - - impl crate::Decoder for $ty { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(crate::DecoderReadExt::read_enum(reader)?) - } - } - ); -); - -#[macro_export] -macro_rules! impl_json_encoder_decoder ( - ($ty: ident) => ( - impl crate::Encoder for $ty { - fn encode(&self, writer: &mut W) -> Result<(), crate::EncodeError> { - let json = serde_json::to_string(self)?; - crate::EncoderWriteExt::write_string(writer, &json, crate::STRING_MAX_LENGTH)?; - - Ok(()) - } - } - - impl crate::Decoder for $ty { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - let json = crate::DecoderReadExt::read_string(reader, crate::STRING_MAX_LENGTH)?; - - Ok(serde_json::from_str(&json)?) - } - } - ); -); - -mod var_int { - use crate::{DecodeError, EncodeError}; - use crate::{DecoderReadExt, EncoderWriteExt}; - use std::io::{Read, Write}; - - pub fn encode(value: &i32, writer: &mut W) -> Result<(), EncodeError> { - writer.write_var_i32(*value)?; - - Ok(()) - } - - pub fn decode(reader: &mut R) -> Result { - Ok(reader.read_var_i32()?) - } -} - -mod var_long { - use crate::{DecodeError, EncodeError}; - use crate::{DecoderReadExt, EncoderWriteExt}; - use std::io::{Read, Write}; - - pub fn encode(value: &i64, writer: &mut W) -> Result<(), EncodeError> { - writer.write_var_i64(*value)?; - - Ok(()) - } - - pub fn decode(reader: &mut R) -> Result { - Ok(reader.read_var_i64()?) - } -} - -mod rest { - use crate::{DecodeError, EncodeError}; - use std::io::{Read, Write}; - - pub fn encode(value: &[u8], writer: &mut W) -> Result<(), EncodeError> { - writer.write_all(value)?; - - Ok(()) - } - - pub fn decode(reader: &mut R) -> Result, DecodeError> { - let mut data = Vec::new(); - reader.read_to_end(data.as_mut())?; - - Ok(data) - } -} - -mod uuid_hyp_str { - use crate::{ - DecodeError, DecoderReadExt, EncodeError, EncoderWriteExt, HYPHENATED_UUID_LENGTH, - }; - use std::io::{Read, Write}; - use uuid::Uuid; - - pub fn encode(value: &Uuid, writer: &mut W) -> Result<(), EncodeError> { - let uuid_hyphenated_string = value.to_hyphenated().to_string(); - writer.write_string(&uuid_hyphenated_string, HYPHENATED_UUID_LENGTH)?; - - Ok(()) - } - - pub fn decode(reader: &mut R) -> Result { - let uuid_hyphenated_string = reader.read_string(HYPHENATED_UUID_LENGTH)?; - let uuid = Uuid::parse_str(&uuid_hyphenated_string)?; - - Ok(uuid) - } -} - -#[test] -fn test_read_variable_i32_2_bytes_value() { - let mut cursor = Cursor::new(vec![0b10101100, 0b00000010]); - let value = cursor.read_var_i32().unwrap(); - - assert_eq!(value, 300); -} - -#[test] -fn test_read_variable_i32_5_bytes_value() { - let mut cursor = Cursor::new(vec![0xff, 0xff, 0xff, 0xff, 0x07]); - let value = cursor.read_var_i32().unwrap(); - - assert_eq!(value, 2147483647); -} - -#[test] -fn test_write_variable_i32_2_bytes_value() { - let mut cursor = Cursor::new(Vec::with_capacity(5)); - cursor.write_var_i32(300).unwrap(); - - assert_eq!(cursor.into_inner(), vec![0b10101100, 0b00000010]); -} - -#[test] -fn test_write_variable_i32_5_bytes_value() { - let mut cursor = Cursor::new(Vec::with_capacity(5)); - cursor.write_var_i32(2147483647).unwrap(); - - assert_eq!(cursor.into_inner(), vec![0xff, 0xff, 0xff, 0xff, 0x07]); -} +#[cfg(feature = "data")] +pub mod data; +pub mod decoder; +pub mod encoder; +pub mod error; diff --git a/protocol/src/login.rs b/protocol/src/login.rs deleted file mode 100644 index d18331e..0000000 --- a/protocol/src/login.rs +++ /dev/null @@ -1,479 +0,0 @@ -use crate::chat::Message; -use crate::DecodeError; -use crate::Decoder; -use std::io::Read; -use uuid::Uuid; - -use minecraft_protocol_derive::Packet; - -pub enum LoginServerBoundPacket { - LoginStart(LoginStart), - EncryptionResponse(EncryptionResponse), - LoginPluginResponse(LoginPluginResponse), -} - -pub enum LoginClientBoundPacket { - LoginDisconnect(LoginDisconnect), - EncryptionRequest(EncryptionRequest), - LoginSuccess(LoginSuccess), - SetCompression(SetCompression), - LoginPluginRequest(LoginPluginRequest), -} - -impl LoginServerBoundPacket { - pub fn get_type_id(&self) -> u8 { - match self { - LoginServerBoundPacket::LoginStart(_) => 0x00, - LoginServerBoundPacket::EncryptionResponse(_) => 0x01, - LoginServerBoundPacket::LoginPluginResponse(_) => 0x02, - } - } - - pub fn decode(type_id: u8, reader: &mut R) -> Result { - match type_id { - 0x00 => { - let login_start = LoginStart::decode(reader)?; - - Ok(LoginServerBoundPacket::LoginStart(login_start)) - } - 0x01 => { - let encryption_response = EncryptionResponse::decode(reader)?; - - Ok(LoginServerBoundPacket::EncryptionResponse( - encryption_response, - )) - } - 0x02 => { - let login_plugin_response = LoginPluginResponse::decode(reader)?; - - Ok(LoginServerBoundPacket::LoginPluginResponse( - login_plugin_response, - )) - } - _ => Err(DecodeError::UnknownPacketType { type_id }), - } - } -} - -impl LoginClientBoundPacket { - pub fn get_type_id(&self) -> u8 { - match self { - LoginClientBoundPacket::LoginDisconnect(_) => 0x00, - LoginClientBoundPacket::EncryptionRequest(_) => 0x01, - LoginClientBoundPacket::LoginSuccess(_) => 0x02, - LoginClientBoundPacket::SetCompression(_) => 0x03, - LoginClientBoundPacket::LoginPluginRequest(_) => 0x04, - } - } - - pub fn decode(type_id: u8, reader: &mut R) -> Result { - match type_id { - 0x00 => { - let login_disconnect = LoginDisconnect::decode(reader)?; - - Ok(LoginClientBoundPacket::LoginDisconnect(login_disconnect)) - } - 0x01 => { - let encryption_request = EncryptionRequest::decode(reader)?; - - Ok(LoginClientBoundPacket::EncryptionRequest( - encryption_request, - )) - } - 0x02 => { - let login_success = LoginSuccess::decode(reader)?; - - Ok(LoginClientBoundPacket::LoginSuccess(login_success)) - } - 0x03 => { - let set_compression = SetCompression::decode(reader)?; - - Ok(LoginClientBoundPacket::SetCompression(set_compression)) - } - 0x04 => { - let login_plugin_request = LoginPluginRequest::decode(reader)?; - - Ok(LoginClientBoundPacket::LoginPluginRequest( - login_plugin_request, - )) - } - _ => Err(DecodeError::UnknownPacketType { type_id }), - } - } -} - -#[derive(Packet, Debug)] -pub struct LoginStart { - pub name: String, -} - -impl LoginStart { - pub fn new(name: String) -> LoginServerBoundPacket { - let login_start = LoginStart { name }; - - LoginServerBoundPacket::LoginStart(login_start) - } -} - -#[derive(Packet, Debug)] -pub struct EncryptionResponse { - pub shared_secret: Vec, - pub verify_token: Vec, -} - -impl EncryptionResponse { - pub fn new(shared_secret: Vec, verify_token: Vec) -> LoginServerBoundPacket { - let encryption_response = EncryptionResponse { - shared_secret, - verify_token, - }; - - LoginServerBoundPacket::EncryptionResponse(encryption_response) - } -} - -#[derive(Packet, Debug)] -pub struct LoginPluginResponse { - #[packet(with = "var_int")] - pub message_id: i32, - pub successful: bool, - #[packet(with = "rest")] - pub data: Vec, -} - -impl LoginPluginResponse { - pub fn new(message_id: i32, successful: bool, data: Vec) -> LoginServerBoundPacket { - let login_plugin_response = LoginPluginResponse { - message_id, - successful, - data, - }; - - LoginServerBoundPacket::LoginPluginResponse(login_plugin_response) - } -} - -#[derive(Packet, Debug)] -pub struct LoginDisconnect { - pub reason: Message, -} - -impl LoginDisconnect { - pub fn new(reason: Message) -> LoginClientBoundPacket { - let login_disconnect = LoginDisconnect { reason }; - - LoginClientBoundPacket::LoginDisconnect(login_disconnect) - } -} - -#[derive(Packet, Debug)] -pub struct EncryptionRequest { - #[packet(max_length = 20)] - pub server_id: String, - pub public_key: Vec, - pub verify_token: Vec, -} - -impl EncryptionRequest { - pub fn new( - server_id: String, - public_key: Vec, - verify_token: Vec, - ) -> LoginClientBoundPacket { - let encryption_request = EncryptionRequest { - server_id, - public_key, - verify_token, - }; - - LoginClientBoundPacket::EncryptionRequest(encryption_request) - } -} - -#[derive(Packet, Debug)] -pub struct LoginSuccess { - #[packet(with = "uuid_hyp_str")] - pub uuid: Uuid, - #[packet(max_length = 16)] - pub username: String, -} - -impl LoginSuccess { - pub fn new(uuid: Uuid, username: String) -> LoginClientBoundPacket { - let login_success = LoginSuccess { uuid, username }; - - LoginClientBoundPacket::LoginSuccess(login_success) - } -} - -#[derive(Packet, Debug)] -pub struct SetCompression { - #[packet(with = "var_int")] - pub threshold: i32, -} - -impl SetCompression { - pub fn new(threshold: i32) -> LoginClientBoundPacket { - let set_compression = SetCompression { threshold }; - - LoginClientBoundPacket::SetCompression(set_compression) - } -} - -#[derive(Packet, Debug)] -pub struct LoginPluginRequest { - #[packet(with = "var_int")] - pub message_id: i32, - pub channel: String, - #[packet(with = "rest")] - pub data: Vec, -} - -impl LoginPluginRequest { - pub fn new(message_id: i32, channel: String, data: Vec) -> LoginClientBoundPacket { - let login_plugin_request = LoginPluginRequest { - message_id, - channel, - data, - }; - - LoginClientBoundPacket::LoginPluginRequest(login_plugin_request) - } -} - -#[cfg(test)] -mod tests { - use crate::chat::{Message, Payload}; - use crate::login::{EncryptionRequest, LoginDisconnect, LoginPluginRequest, SetCompression}; - use crate::login::{EncryptionResponse, LoginPluginResponse}; - use crate::login::{LoginStart, LoginSuccess}; - use crate::Decoder; - use crate::Encoder; - use std::io::Cursor; - use uuid::Uuid; - - #[test] - fn test_login_start_packet_encode() { - let login_start = LoginStart { - name: String::from("Username"), - }; - - let mut vec = Vec::new(); - login_start.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/login/login_start.dat").to_vec() - ); - } - - #[test] - fn test_login_start_packet_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/login/login_start.dat").to_vec()); - let login_start = LoginStart::decode(&mut cursor).unwrap(); - - assert_eq!(login_start.name, String::from("Username")); - } - - #[test] - fn test_encryption_response_encode() { - let encryption_response = EncryptionResponse { - shared_secret: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - verify_token: vec![1, 2, 3, 4], - }; - - let mut vec = Vec::new(); - encryption_response.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/login/encryption_response.dat").to_vec() - ); - } - - #[test] - fn test_encryption_response_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/login/encryption_response.dat").to_vec()); - let encryption_response = EncryptionResponse::decode(&mut cursor).unwrap(); - - assert_eq!( - encryption_response.shared_secret, - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - assert_eq!(encryption_response.verify_token, vec![1, 2, 3, 4]); - } - - #[test] - fn test_login_plugin_response_encode() { - let login_plugin_response = LoginPluginResponse { - message_id: 55, - successful: true, - data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - }; - - let mut vec = Vec::new(); - login_plugin_response.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/login/login_plugin_response.dat").to_vec() - ); - } - - #[test] - fn test_login_plugin_response_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/login/login_plugin_response.dat").to_vec()); - let login_plugin_response = LoginPluginResponse::decode(&mut cursor).unwrap(); - - assert_eq!(login_plugin_response.message_id, 55); - assert!(login_plugin_response.successful); - assert_eq!( - login_plugin_response.data, - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } - - #[test] - fn test_login_disconnect_encode() { - let login_disconnect = LoginDisconnect { - reason: Message::new(Payload::text("Message")), - }; - - let mut vec = Vec::new(); - login_disconnect.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/login/login_disconnect.dat").to_vec() - ); - } - - #[test] - fn test_login_disconnect_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/login/login_disconnect.dat").to_vec()); - let login_disconnect = LoginDisconnect::decode(&mut cursor).unwrap(); - - assert_eq!( - login_disconnect.reason, - Message::new(Payload::text("Message")) - ); - } - - #[test] - fn test_encryption_request_encode() { - let encryption_request = EncryptionRequest { - server_id: String::from("ServerID"), - public_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - verify_token: vec![1, 2, 3, 4], - }; - - let mut vec = Vec::new(); - encryption_request.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/login/encryption_request.dat").to_vec() - ); - } - - #[test] - fn test_encryption_request_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/login/encryption_request.dat").to_vec()); - let encryption_request = EncryptionRequest::decode(&mut cursor).unwrap(); - - assert_eq!(encryption_request.server_id, String::from("ServerID")); - assert_eq!( - encryption_request.public_key, - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - assert_eq!(encryption_request.verify_token, vec![1, 2, 3, 4]); - } - - #[test] - fn test_login_success_encode() { - let login_success = LoginSuccess { - uuid: Uuid::parse_str("35ee313b-d89a-41b8-b25e-d32e8aff0389").unwrap(), - username: String::from("Username"), - }; - - let mut vec = Vec::new(); - login_success.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/login/login_success.dat").to_vec() - ); - } - - #[test] - fn test_login_success_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/login/login_success.dat").to_vec()); - let login_success = LoginSuccess::decode(&mut cursor).unwrap(); - - assert_eq!(login_success.username, String::from("Username")); - - assert_eq!( - login_success.uuid, - Uuid::parse_str("35ee313b-d89a-41b8-b25e-d32e8aff0389").unwrap() - ); - } - - #[test] - fn test_set_compression_encode() { - let set_compression = SetCompression { threshold: 1 }; - - let mut vec = Vec::new(); - set_compression.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/login/login_set_compression.dat").to_vec() - ); - } - - #[test] - fn test_set_compression_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/login/login_set_compression.dat").to_vec()); - let set_compression = SetCompression::decode(&mut cursor).unwrap(); - - assert_eq!(set_compression.threshold, 1); - } - - #[test] - fn test_login_plugin_request_encode() { - let login_plugin_request = LoginPluginRequest { - message_id: 55, - channel: String::from("Channel"), - data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - }; - - let mut vec = Vec::new(); - login_plugin_request.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/login/login_plugin_request.dat").to_vec() - ); - } - - #[test] - fn test_login_plugin_request_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/login/login_plugin_request.dat").to_vec()); - let login_plugin_request = LoginPluginRequest::decode(&mut cursor).unwrap(); - - assert_eq!(login_plugin_request.message_id, 55); - assert_eq!(login_plugin_request.channel, String::from("Channel")); - assert_eq!( - login_plugin_request.data, - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - ); - } -} diff --git a/protocol/src/status.rs b/protocol/src/status.rs deleted file mode 100644 index 1d8f195..0000000 --- a/protocol/src/status.rs +++ /dev/null @@ -1,235 +0,0 @@ -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use crate::chat::Message; -use crate::impl_json_encoder_decoder; -use crate::DecodeError; -use crate::Decoder; -use minecraft_protocol_derive::Packet; -use std::io::Read; - -pub enum StatusServerBoundPacket { - StatusRequest, - PingRequest(PingRequest), -} - -pub enum StatusClientBoundPacket { - StatusResponse(StatusResponse), - PingResponse(PingResponse), -} - -impl StatusServerBoundPacket { - pub fn get_type_id(&self) -> u8 { - match self { - StatusServerBoundPacket::StatusRequest => 0x00, - StatusServerBoundPacket::PingRequest(_) => 0x01, - } - } - - pub fn decode(type_id: u8, reader: &mut R) -> Result { - match type_id { - 0x00 => Ok(StatusServerBoundPacket::StatusRequest), - 0x01 => { - let ping_request = PingRequest::decode(reader)?; - - Ok(StatusServerBoundPacket::PingRequest(ping_request)) - } - _ => Err(DecodeError::UnknownPacketType { type_id }), - } - } -} - -impl StatusClientBoundPacket { - pub fn get_type_id(&self) -> u8 { - match self { - StatusClientBoundPacket::StatusResponse(_) => 0x00, - StatusClientBoundPacket::PingResponse(_) => 0x01, - } - } -} - -#[derive(Packet, Debug)] -pub struct PingRequest { - pub time: u64, -} - -impl PingRequest { - pub fn new(time: u64) -> StatusServerBoundPacket { - let ping_request = PingRequest { time }; - - StatusServerBoundPacket::PingRequest(ping_request) - } -} - -#[derive(Packet, Debug)] -pub struct PingResponse { - pub time: u64, -} - -impl PingResponse { - pub fn new(time: u64) -> StatusClientBoundPacket { - let ping_response = PingResponse { time }; - - StatusClientBoundPacket::PingResponse(ping_response) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ServerStatus { - pub version: ServerVersion, - pub players: OnlinePlayers, - pub description: Message, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ServerVersion { - pub name: String, - pub protocol: u32, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct OnlinePlayers { - pub max: u32, - pub online: u32, - pub sample: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] -pub struct OnlinePlayer { - pub name: String, - pub id: Uuid, -} - -#[derive(Packet, Debug)] -pub struct StatusResponse { - pub server_status: ServerStatus, -} - -impl_json_encoder_decoder!(ServerStatus); - -impl StatusResponse { - pub fn new(server_status: ServerStatus) -> StatusClientBoundPacket { - let status_response = StatusResponse { server_status }; - - StatusClientBoundPacket::StatusResponse(status_response) - } -} - -#[cfg(test)] -mod tests { - use crate::chat::{Message, Payload}; - use crate::status::{ - OnlinePlayer, OnlinePlayers, PingRequest, PingResponse, ServerStatus, ServerVersion, - StatusResponse, - }; - use crate::Decoder; - use crate::Encoder; - use std::io::Cursor; - use uuid::Uuid; - - #[test] - fn test_ping_request_encode() { - let ping_request = PingRequest { - time: 1577735845610, - }; - - let mut vec = Vec::new(); - ping_request.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/status/ping_request.dat").to_vec() - ); - } - - #[test] - fn test_status_ping_request_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/status/ping_request.dat").to_vec()); - let ping_request = PingRequest::decode(&mut cursor).unwrap(); - - assert_eq!(ping_request.time, 1577735845610); - } - - #[test] - fn test_ping_response_encode() { - let ping_response = PingResponse { - time: 1577735845610, - }; - - let mut vec = Vec::new(); - ping_response.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/status/ping_response.dat").to_vec() - ); - } - - #[test] - fn test_status_ping_response_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/status/ping_response.dat").to_vec()); - let ping_response = PingResponse::decode(&mut cursor).unwrap(); - - assert_eq!(ping_response.time, 1577735845610); - } - - #[test] - fn test_status_response_encode() { - let version = ServerVersion { - name: String::from("1.15.1"), - protocol: 575, - }; - - let player = OnlinePlayer { - id: Uuid::parse_str("2a1e1912-7103-4add-80fc-91ebc346cbce").unwrap(), - name: String::from("Username"), - }; - - let players = OnlinePlayers { - online: 10, - max: 100, - sample: vec![player], - }; - - let server_status = ServerStatus { - version, - description: Message::new(Payload::text("Description")), - players, - }; - - let status_response = StatusResponse { server_status }; - - let mut vec = Vec::new(); - status_response.encode(&mut vec).unwrap(); - - assert_eq!( - vec, - include_bytes!("../test/packet/status/status_response.dat").to_vec() - ); - } - - #[test] - fn test_status_response_decode() { - let mut cursor = - Cursor::new(include_bytes!("../test/packet/status/status_response.dat").to_vec()); - let status_response = StatusResponse::decode(&mut cursor).unwrap(); - let server_status = status_response.server_status; - - let player = OnlinePlayer { - id: Uuid::parse_str("2a1e1912-7103-4add-80fc-91ebc346cbce").unwrap(), - name: String::from("Username"), - }; - - assert_eq!(server_status.version.name, String::from("1.15.1")); - assert_eq!(server_status.version.protocol, 575); - assert_eq!(server_status.players.max, 100); - assert_eq!(server_status.players.online, 10); - assert_eq!(server_status.players.sample, vec![player]); - assert_eq!( - server_status.description, - Message::new(Payload::text("Description")) - ); - } -} diff --git a/protocol/test/packet/game/chunk_data.dat b/protocol/test/packet/game/chunk_data.dat deleted file mode 100644 index fb01ab1..0000000 Binary files a/protocol/test/packet/game/chunk_data.dat and /dev/null differ diff --git a/protocol/test/packet/game/client_bound_chat_message.dat b/protocol/test/packet/game/client_bound_chat_message.dat deleted file mode 100644 index f66ac04..0000000 --- a/protocol/test/packet/game/client_bound_chat_message.dat +++ /dev/null @@ -1 +0,0 @@ -{"text":"hello client!"} \ No newline at end of file diff --git a/protocol/test/packet/game/client_bound_keep_alive.dat b/protocol/test/packet/game/client_bound_keep_alive.dat deleted file mode 100644 index 51d9e4c..0000000 Binary files a/protocol/test/packet/game/client_bound_keep_alive.dat and /dev/null differ diff --git a/protocol/test/packet/game/game_disconnect.dat b/protocol/test/packet/game/game_disconnect.dat deleted file mode 100644 index 104c7f9..0000000 --- a/protocol/test/packet/game/game_disconnect.dat +++ /dev/null @@ -1 +0,0 @@ -{"text":"Message"} \ No newline at end of file diff --git a/protocol/test/packet/game/join_game.dat b/protocol/test/packet/game/join_game.dat deleted file mode 100644 index e2e4383..0000000 Binary files a/protocol/test/packet/game/join_game.dat and /dev/null differ diff --git a/protocol/test/packet/game/server_bound_chat_message.dat b/protocol/test/packet/game/server_bound_chat_message.dat deleted file mode 100644 index 927c54f..0000000 --- a/protocol/test/packet/game/server_bound_chat_message.dat +++ /dev/null @@ -1 +0,0 @@ - hello server! \ No newline at end of file diff --git a/protocol/test/packet/game/server_bound_keep_alive.dat b/protocol/test/packet/game/server_bound_keep_alive.dat deleted file mode 100644 index 01c6bfe..0000000 Binary files a/protocol/test/packet/game/server_bound_keep_alive.dat and /dev/null differ diff --git a/protocol/test/packet/login/encryption_request.dat b/protocol/test/packet/login/encryption_request.dat deleted file mode 100644 index 154ec3a..0000000 --- a/protocol/test/packet/login/encryption_request.dat +++ /dev/null @@ -1,3 +0,0 @@ -ServerID - - \ No newline at end of file diff --git a/protocol/test/packet/login/encryption_response.dat b/protocol/test/packet/login/encryption_response.dat deleted file mode 100644 index da10464..0000000 --- a/protocol/test/packet/login/encryption_response.dat +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/protocol/test/packet/login/login_disconnect.dat b/protocol/test/packet/login/login_disconnect.dat deleted file mode 100644 index 104c7f9..0000000 --- a/protocol/test/packet/login/login_disconnect.dat +++ /dev/null @@ -1 +0,0 @@ -{"text":"Message"} \ No newline at end of file diff --git a/protocol/test/packet/login/login_plugin_request.dat b/protocol/test/packet/login/login_plugin_request.dat deleted file mode 100644 index 909fb07..0000000 --- a/protocol/test/packet/login/login_plugin_request.dat +++ /dev/null @@ -1 +0,0 @@ -7Channel diff --git a/protocol/test/packet/login/login_plugin_response.dat b/protocol/test/packet/login/login_plugin_response.dat deleted file mode 100644 index 10c09e4..0000000 --- a/protocol/test/packet/login/login_plugin_response.dat +++ /dev/null @@ -1 +0,0 @@ -7 diff --git a/protocol/test/packet/login/login_set_compression.dat b/protocol/test/packet/login/login_set_compression.dat deleted file mode 100644 index 6b2aaa7..0000000 --- a/protocol/test/packet/login/login_set_compression.dat +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/protocol/test/packet/login/login_start.dat b/protocol/test/packet/login/login_start.dat deleted file mode 100644 index 81e0277..0000000 --- a/protocol/test/packet/login/login_start.dat +++ /dev/null @@ -1 +0,0 @@ -Username \ No newline at end of file diff --git a/protocol/test/packet/login/login_success.dat b/protocol/test/packet/login/login_success.dat deleted file mode 100644 index 8bc273f..0000000 --- a/protocol/test/packet/login/login_success.dat +++ /dev/null @@ -1 +0,0 @@ -$35ee313b-d89a-41b8-b25e-d32e8aff0389Username \ No newline at end of file diff --git a/protocol/test/packet/status/ping_request.dat b/protocol/test/packet/status/ping_request.dat deleted file mode 100644 index 36856e8..0000000 Binary files a/protocol/test/packet/status/ping_request.dat and /dev/null differ diff --git a/protocol/test/packet/status/ping_response.dat b/protocol/test/packet/status/ping_response.dat deleted file mode 100644 index 36856e8..0000000 Binary files a/protocol/test/packet/status/ping_response.dat and /dev/null differ diff --git a/protocol/test/packet/status/status_response.dat b/protocol/test/packet/status/status_response.dat deleted file mode 100644 index f283a2c..0000000 --- a/protocol/test/packet/status/status_response.dat +++ /dev/null @@ -1 +0,0 @@ -¾{"version":{"name":"1.15.1","protocol":575},"players":{"max":100,"online":10,"sample":[{"name":"Username","id":"2a1e1912-7103-4add-80fc-91ebc346cbce"}]},"description":{"text":"Description"}} \ No newline at end of file