diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8697fef --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.dat binary diff --git a/Cargo.toml b/Cargo.toml index c327452..4436a00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,6 @@ -[package] -name = "minecraft-protocol" -version = "0.1.0" -authors = ["vagola "] -edition = "2018" -description = "Library for decoding and encoding Minecraft packets" -license = "MIT" -homepage = "https://github.com/eihwaz/minecraft-protocol" -repository = "https://github.com/eihwaz/minecraft-protocol" -keywords = ["minecraft", "protocol", "packet", "io"] -readme = "README.md" +[workspace] -[dependencies] -byteorder = "1" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -uuid = { version = "0.7", features = ["v4", "serde"] } -mc-varint = { git = "https://github.com/luojia65/mc-varint" } -num-traits = "0.2" -num-derive = "0.2" -named-binary-tag = "0.2" +members = [ + "protocol", + "protocol-derive", +] \ No newline at end of file diff --git a/protocol-derive/Cargo.toml b/protocol-derive/Cargo.toml new file mode 100644 index 0000000..e0734a5 --- /dev/null +++ b/protocol-derive/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "minecraft-protocol-derive" +version = "0.0.0" +authors = ["vagola "] +edition = "2018" +description = "Derive macro for reading and writing Minecraft packets" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +syn = "1.0" +quote = "1.0" diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs new file mode 100644 index 0000000..2953b8f --- /dev/null +++ b/protocol-derive/src/lib.rs @@ -0,0 +1,186 @@ +extern crate proc_macro; + +use proc_macro::TokenStream as TokenStream1; +use proc_macro2::Ident; +use proc_macro2::TokenStream as TokenStream2; +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))] +pub fn derive_packet(input: proc_macro::TokenStream) -> TokenStream1 { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + match input.data { + Data::Struct(data) => { + let fields = &data.fields; + + let encoder = impl_encoder_trait(name, fields); + let decoder = impl_decoder_trait(name, fields); + + TokenStream1::from(quote! { + #encoder + + #decoder + }) + } + _ => panic!("Expected only structures"), + } +} + +fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { + let encode = quote_field(fields, |field| { + let name = &field.ident; + + let unparsed_meta = get_packet_field_meta(field); + let parsed_meta = parse_packet_field_meta(&unparsed_meta); + + // This is special case because max length are used only for strings. + if let Some(max_length) = parsed_meta.max_length { + return quote! { + crate::EncoderWriteExt::write_string(writer, &self.#name, #max_length)?; + }; + } + + let module = parsed_meta.module.as_deref().unwrap_or("Encoder"); + let module_ident = Ident::new(&module, Span::call_site()); + + quote! { + crate::#module_ident::encode(&self.#name, writer)?; + } + }); + + quote! { + #[automatically_derived] + impl crate::Encoder for #name { + fn encode(&self, writer: &mut W) -> Result<(), crate::EncodeError> { + #encode + + Ok(()) + } + } + } +} + +fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { + let decode = quote_field(fields, |field| { + let name = &field.ident; + let ty = &field.ty; + + let unparsed_meta = get_packet_field_meta(field); + let parsed_meta = parse_packet_field_meta(&unparsed_meta); + + // This is special case because max length are used only for strings. + if let Some(max_length) = parsed_meta.max_length { + return quote! { + let #name = crate::DecoderReadExt::read_string(reader, #max_length)?; + }; + } + + match parsed_meta.module { + Some(module) => { + let module_ident = Ident::new(&module, Span::call_site()); + + quote! { + let #name = crate::#module_ident::decode(reader)?; + } + } + None => { + quote! { + let #name = <#ty as crate::Decoder>::decode(reader)?; + } + } + } + }); + + let create = quote_field(fields, |field| { + let name = &field.ident; + + quote! { + #name, + } + }); + + quote! { + #[automatically_derived] + impl crate::Decoder for #name { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + #decode + + Ok(#name { + #create + }) + } + } + } +} + +#[derive(Debug)] +struct PacketFieldMeta { + module: Option, + max_length: Option, +} + +fn parse_packet_field_meta(meta_list: &Vec) -> PacketFieldMeta { + let mut module = None; + let mut max_length = None; + + for meta in meta_list { + match meta { + NestedMeta::Meta(Meta::NameValue(named_meta)) => match &named_meta.path { + path if path.is_ident("with") => match &named_meta.lit { + Lit::Str(lit_str) => module = Some(lit_str.value()), + _ => panic!("\"with\" attribute value must be string"), + }, + path if path.is_ident("max_length") => match &named_meta.lit { + Lit::Int(lit_int) => { + max_length = Some( + lit_int + .base10_parse::() + .expect("Failed to parse max length attribute"), + ) + } + _ => panic!("\"max_length\" attribute value must be integer"), + }, + path => panic!( + "Received unrecognized attribute : \"{}\"", + path.get_ident().unwrap() + ), + }, + _ => panic!("Expected only named meta values"), + } + } + + PacketFieldMeta { module, max_length } +} + +fn get_packet_field_meta(field: &Field) -> Vec { + field + .attrs + .iter() + .filter(|a| a.path.is_ident("packet")) + .map(|a| a.parse_meta().expect("Failed to parse field attribute")) + .map(|m| match m { + Meta::List(meta_list) => Vec::from_iter(meta_list.nested), + _ => panic!("Expected only list attributes"), + }) + .flatten() + .collect() +} + +fn quote_field TokenStream2>(fields: &Fields, func: F) -> TokenStream2 { + let mut output = quote!(); + + match fields { + Fields::Named(named_fields) => { + output.append_all(named_fields.named.iter().map(|f| func(f))) + } + _ => panic!("Expected only for named fields"), + } + + output +} diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml new file mode 100644 index 0000000..4e380b0 --- /dev/null +++ b/protocol/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "minecraft-protocol" +version = "0.1.0" +authors = ["vagola "] +edition = "2018" +description = "Library for decoding and encoding Minecraft packets" +license = "MIT" +homepage = "https://github.com/eihwaz/minecraft-protocol" +repository = "https://github.com/eihwaz/minecraft-protocol" +keywords = ["minecraft", "protocol", "packet", "io"] +readme = "README.md" + +[dependencies] +minecraft-protocol-derive = { path = "../protocol-derive" } +byteorder = "1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uuid = { version = "0.7", features = ["v4", "serde"] } +mc-varint = { git = "https://github.com/luojia65/mc-varint" } +num-traits = "0.2" +num-derive = "0.2" +named-binary-tag = "0.2" diff --git a/src/chat.rs b/protocol/src/chat.rs similarity index 99% rename from src/chat.rs rename to protocol/src/chat.rs index 415ec0c..2276673 100644 --- a/src/chat.rs +++ b/protocol/src/chat.rs @@ -61,6 +61,7 @@ //! assert_eq!(expected_message, Message::from_json(json).unwrap()); //! ``` +use crate::impl_json_encoder_decoder; use serde::{Deserialize, Serialize}; use serde_json::Error; @@ -243,6 +244,8 @@ impl Message { } } +impl_json_encoder_decoder!(Message); + pub struct MessageBuilder { current: Message, root: Option, diff --git a/protocol/src/game.rs b/protocol/src/game.rs new file mode 100644 index 0000000..3261e38 --- /dev/null +++ b/protocol/src/game.rs @@ -0,0 +1,457 @@ +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), +} + +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::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)) + } + 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) + } +} + +#[cfg(test)] +mod tests { + use crate::chat::{Message, Payload}; + use crate::game::{ + ChunkData, ClientBoundChatMessage, ClientBoundKeepAlive, 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"))); + } +} diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs new file mode 100644 index 0000000..5024b31 --- /dev/null +++ b/protocol/src/lib.rs @@ -0,0 +1,552 @@ +//! 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::{Read, Write}; +use std::string::FromUtf8Error; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use mc_varint::{VarIntRead, VarIntWrite}; +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, + }, +} + +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; +} + +/// 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>; +} + +/// 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; +} + +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(()) + } +} + +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)?) + } +} + +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 mc_varint::{VarIntRead, VarIntWrite}; + 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 mc_varint::{VarIntRead, VarIntWrite}; + 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) + } +} diff --git a/protocol/src/login.rs b/protocol/src/login.rs new file mode 100644 index 0000000..d18331e --- /dev/null +++ b/protocol/src/login.rs @@ -0,0 +1,479 @@ +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 new file mode 100644 index 0000000..1d8f195 --- /dev/null +++ b/protocol/src/status.rs @@ -0,0 +1,235 @@ +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/test/chat/click_change_page.json b/protocol/test/chat/click_change_page.json similarity index 100% rename from test/chat/click_change_page.json rename to protocol/test/chat/click_change_page.json diff --git a/test/chat/click_open_url.json b/protocol/test/chat/click_open_url.json similarity index 100% rename from test/chat/click_open_url.json rename to protocol/test/chat/click_open_url.json diff --git a/test/chat/click_run_command.json b/protocol/test/chat/click_run_command.json similarity index 100% rename from test/chat/click_run_command.json rename to protocol/test/chat/click_run_command.json diff --git a/test/chat/click_suggest_command.json b/protocol/test/chat/click_suggest_command.json similarity index 100% rename from test/chat/click_suggest_command.json rename to protocol/test/chat/click_suggest_command.json diff --git a/test/chat/hover_show_entity.json b/protocol/test/chat/hover_show_entity.json similarity index 100% rename from test/chat/hover_show_entity.json rename to protocol/test/chat/hover_show_entity.json diff --git a/test/chat/hover_show_item.json b/protocol/test/chat/hover_show_item.json similarity index 100% rename from test/chat/hover_show_item.json rename to protocol/test/chat/hover_show_item.json diff --git a/test/chat/hover_show_text.json b/protocol/test/chat/hover_show_text.json similarity index 100% rename from test/chat/hover_show_text.json rename to protocol/test/chat/hover_show_text.json diff --git a/test/chat/keybind_jump.json b/protocol/test/chat/keybind_jump.json similarity index 100% rename from test/chat/keybind_jump.json rename to protocol/test/chat/keybind_jump.json diff --git a/test/chat/text_hello_world.json b/protocol/test/chat/text_hello_world.json similarity index 100% rename from test/chat/text_hello_world.json rename to protocol/test/chat/text_hello_world.json diff --git a/test/chat/translate_opped_steve.json b/protocol/test/chat/translate_opped_steve.json similarity index 100% rename from test/chat/translate_opped_steve.json rename to protocol/test/chat/translate_opped_steve.json diff --git a/protocol/test/packet/game/chunk_data.dat b/protocol/test/packet/game/chunk_data.dat new file mode 100644 index 0000000..fb01ab1 Binary files /dev/null and b/protocol/test/packet/game/chunk_data.dat differ diff --git a/protocol/test/packet/game/client_bound_chat_message.dat b/protocol/test/packet/game/client_bound_chat_message.dat new file mode 100644 index 0000000..f66ac04 --- /dev/null +++ b/protocol/test/packet/game/client_bound_chat_message.dat @@ -0,0 +1 @@ +{"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 new file mode 100644 index 0000000..51d9e4c Binary files /dev/null and b/protocol/test/packet/game/client_bound_keep_alive.dat differ diff --git a/protocol/test/packet/game/join_game.dat b/protocol/test/packet/game/join_game.dat new file mode 100644 index 0000000..e2e4383 Binary files /dev/null and b/protocol/test/packet/game/join_game.dat differ diff --git a/protocol/test/packet/game/server_bound_chat_message.dat b/protocol/test/packet/game/server_bound_chat_message.dat new file mode 100644 index 0000000..927c54f --- /dev/null +++ b/protocol/test/packet/game/server_bound_chat_message.dat @@ -0,0 +1 @@ + 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 new file mode 100644 index 0000000..01c6bfe Binary files /dev/null and b/protocol/test/packet/game/server_bound_keep_alive.dat differ diff --git a/protocol/test/packet/login/encryption_request.dat b/protocol/test/packet/login/encryption_request.dat new file mode 100644 index 0000000..154ec3a --- /dev/null +++ b/protocol/test/packet/login/encryption_request.dat @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..da10464 --- /dev/null +++ b/protocol/test/packet/login/encryption_response.dat @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/protocol/test/packet/login/login_disconnect.dat b/protocol/test/packet/login/login_disconnect.dat new file mode 100644 index 0000000..104c7f9 --- /dev/null +++ b/protocol/test/packet/login/login_disconnect.dat @@ -0,0 +1 @@ +{"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 new file mode 100644 index 0000000..909fb07 --- /dev/null +++ b/protocol/test/packet/login/login_plugin_request.dat @@ -0,0 +1 @@ +7Channel diff --git a/protocol/test/packet/login/login_plugin_response.dat b/protocol/test/packet/login/login_plugin_response.dat new file mode 100644 index 0000000..10c09e4 --- /dev/null +++ b/protocol/test/packet/login/login_plugin_response.dat @@ -0,0 +1 @@ +7 diff --git a/protocol/test/packet/login/login_set_compression.dat b/protocol/test/packet/login/login_set_compression.dat new file mode 100644 index 0000000..6b2aaa7 --- /dev/null +++ b/protocol/test/packet/login/login_set_compression.dat @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/protocol/test/packet/login/login_start.dat b/protocol/test/packet/login/login_start.dat new file mode 100644 index 0000000..81e0277 --- /dev/null +++ b/protocol/test/packet/login/login_start.dat @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..8bc273f --- /dev/null +++ b/protocol/test/packet/login/login_success.dat @@ -0,0 +1 @@ +$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 new file mode 100644 index 0000000..36856e8 Binary files /dev/null and b/protocol/test/packet/status/ping_request.dat differ diff --git a/protocol/test/packet/status/ping_response.dat b/protocol/test/packet/status/ping_response.dat new file mode 100644 index 0000000..36856e8 Binary files /dev/null and b/protocol/test/packet/status/ping_response.dat differ diff --git a/protocol/test/packet/status/status_response.dat b/protocol/test/packet/status/status_response.dat new file mode 100644 index 0000000..f283a2c --- /dev/null +++ b/protocol/test/packet/status/status_response.dat @@ -0,0 +1 @@ +¾{"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 diff --git a/src/game.rs b/src/game.rs deleted file mode 100644 index e14a6ae..0000000 --- a/src/game.rs +++ /dev/null @@ -1,369 +0,0 @@ -use std::io::{Read, Write}; - -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use num_derive::{FromPrimitive, ToPrimitive}; - -use crate::chat::Message; -use crate::{DecodeError, EncodeError, Packet, PacketRead, PacketWrite}; -use mc_varint::{VarIntRead, VarIntWrite}; -use nbt::CompoundTag; - -const SERVER_BOUND_CHAT_MESSAGE_MAX_LENGTH: u32 = 256; -const LEVEL_TYPE_MAX_LENGTH: u32 = 16; - -pub enum GameServerBoundPacket { - ServerBoundChatMessage(ServerBoundChatMessage), - ServerBoundKeepAlive(ServerBoundKeepAlive), -} - -pub enum GameClientBoundPacket { - ClientBoundChatMessage(ClientBoundChatMessage), - JoinGame(JoinGame), - ClientBoundKeepAlive(ClientBoundKeepAlive), - ChunkData(ChunkData), -} - -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::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)) - } - 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 }), - } - } -} - -pub struct ServerBoundChatMessage { - pub message: String, -} - -impl ServerBoundChatMessage { - pub fn new(message: String) -> GameServerBoundPacket { - let chat_message = ServerBoundChatMessage { message }; - - GameServerBoundPacket::ServerBoundChatMessage(chat_message) - } -} - -impl Packet for ServerBoundChatMessage { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_string(&self.message, SERVER_BOUND_CHAT_MESSAGE_MAX_LENGTH) - } - - fn decode(reader: &mut R) -> Result { - let message = reader.read_string(SERVER_BOUND_CHAT_MESSAGE_MAX_LENGTH)?; - - Ok(ServerBoundChatMessage { message }) - } -} - -pub struct ClientBoundChatMessage { - pub message: Message, - pub position: MessagePosition, -} - -#[derive(Debug, Eq, PartialEq, FromPrimitive, ToPrimitive)] -pub enum MessagePosition { - Chat, - System, - HotBar, -} - -impl ClientBoundChatMessage { - pub fn new(message: Message, position: MessagePosition) -> GameClientBoundPacket { - let chat_message = ClientBoundChatMessage { message, position }; - - GameClientBoundPacket::ClientBoundChatMessage(chat_message) - } -} - -impl Packet for ClientBoundChatMessage { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_chat_message(&self.message)?; - writer.write_enum(&self.position)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let message = reader.read_chat_message()?; - let position = reader.read_enum()?; - - let chat_message = ClientBoundChatMessage { message, position }; - - Ok(chat_message) - } -} - -#[derive(Debug)] -pub struct JoinGame { - pub entity_id: u32, - pub game_mode: GameMode, - pub dimension: i32, - pub max_players: u8, - pub level_type: String, - pub view_distance: u8, - 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 JoinGame { - pub fn new( - entity_id: u32, - game_mode: GameMode, - dimension: i32, - max_players: u8, - level_type: String, - view_distance: u8, - 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) - } -} - -impl Packet for JoinGame { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_u32::(self.entity_id)?; - writer.write_enum(&self.game_mode)?; - writer.write_i32::(self.dimension)?; - writer.write_u8(self.max_players)?; - writer.write_string(&self.level_type, LEVEL_TYPE_MAX_LENGTH)?; - writer.write_var_u32(self.view_distance as u32)?; - writer.write_bool(self.reduced_debug_info)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let entity_id = reader.read_u32::()?; - let game_mode = reader.read_enum()?; - let dimension = reader.read_i32::()?; - let max_players = reader.read_u8()?; - let level_type = reader.read_string(LEVEL_TYPE_MAX_LENGTH)?; - let view_distance = reader.read_var_u32()? as u8; - let reduced_debug_info = reader.read_bool()?; - - Ok(JoinGame { - entity_id, - game_mode, - dimension, - max_players, - level_type, - view_distance, - reduced_debug_info, - }) - } -} - -pub struct ServerBoundKeepAlive { - pub id: u64, -} - -impl ServerBoundKeepAlive { - pub fn new(id: u64) -> GameServerBoundPacket { - let keep_alive = ServerBoundKeepAlive { id }; - - GameServerBoundPacket::ServerBoundKeepAlive(keep_alive) - } -} - -impl Packet for ServerBoundKeepAlive { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_u64::(self.id)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let id = reader.read_u64::()?; - - Ok(ServerBoundKeepAlive { id }) - } -} - -pub struct ClientBoundKeepAlive { - pub id: u64, -} - -impl ClientBoundKeepAlive { - pub fn new(id: u64) -> GameClientBoundPacket { - let keep_alive = ClientBoundKeepAlive { id }; - - GameClientBoundPacket::ClientBoundKeepAlive(keep_alive) - } -} - -impl Packet for ClientBoundKeepAlive { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_u64::(self.id)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let id = reader.read_u64::()?; - - Ok(ClientBoundKeepAlive { id }) - } -} - -pub struct ChunkData { - pub x: i32, - pub z: i32, - pub full: bool, - pub primary_mask: u32, - pub heights: CompoundTag, - pub data: Vec, - pub tiles: Vec, -} - -impl ChunkData { - pub fn new( - x: i32, - z: i32, - full: bool, - primary_mask: u32, - heights: CompoundTag, - data: Vec, - tiles: Vec, - ) -> GameClientBoundPacket { - let chunk_data = ChunkData { - x, - z, - full, - primary_mask, - heights, - data, - tiles, - }; - - GameClientBoundPacket::ChunkData(chunk_data) - } -} - -impl Packet for ChunkData { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_i32::(self.x)?; - writer.write_i32::(self.z)?; - writer.write_bool(self.full)?; - writer.write_var_u32(self.primary_mask)?; - writer.write_compound_tag(&self.heights)?; - writer.write_byte_array(&self.data)?; - writer.write_var_u32(self.tiles.len() as u32)?; - - for tile_compound_tag in self.tiles.iter() { - writer.write_compound_tag(&tile_compound_tag)?; - } - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let x = reader.read_i32::()?; - let z = reader.read_i32::()?; - let full = reader.read_bool()?; - let primary_mask = reader.read_var_u32()?; - let heights = reader.read_compound_tag()?; - let data = reader.read_byte_array()?; - - let tiles_length = reader.read_var_u32()?; - let mut tiles = Vec::new(); - - for _ in 0..tiles_length { - let tile_compound_tag = reader.read_compound_tag()?; - tiles.push(tile_compound_tag); - } - - Ok(ChunkData { - x, - z, - full, - primary_mask, - heights, - data, - tiles, - }) - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index a8bb427..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! 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::{Read, Write}; -use std::string::FromUtf8Error; - -use byteorder::ReadBytesExt; -use byteorder::WriteBytesExt; -use mc_varint::{VarIntRead, VarIntWrite}; -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}; - -pub mod chat; -pub mod game; -pub mod login; -pub mod status; - -/// Current supported protocol version. -pub const PROTOCOL_VERSION: usize = 498; -/// String maximum length. -const STRING_MAX_LENGTH: u32 = 32_768; - -/// Possible errors while encoding packet. -pub enum EncodeError { - /// String length can't be more than provided value. - StringTooLong { - /// String length. - length: usize, - /// Max string length. - max_length: u32, - }, - 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. -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: u32, - /// Max string length. - max_length: u32, - }, - 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, - }, -} - -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 Packet { - type Output; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError>; - - fn decode(reader: &mut R) -> Result; -} - -/// Trait adds additional helper methods for `Read` to read protocol data. -trait PacketRead { - fn read_bool(&mut self) -> Result; - - fn read_string(&mut self, max_length: u32) -> 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; -} - -/// Trait adds additional helper methods for `Write` to write protocol data. -trait PacketWrite { - fn write_bool(&mut self, value: bool) -> Result<(), EncodeError>; - - fn write_string(&mut self, value: &str, max_length: u32) -> 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>; -} - -impl PacketRead 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: u32) -> Result { - let length = self.read_var_u32()?; - - if length > max_length as u32 { - 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_u32()?; - - 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)?) - } -} - -impl PacketWrite 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: u32) -> Result<(), EncodeError> { - let length = value.len(); - - if length > max_length as usize { - return Err(EncodeError::StringTooLong { length, max_length }); - } - - self.write_var_u32(value.len() as u32)?; - self.write_all(value.as_bytes())?; - - Ok(()) - } - - fn write_byte_array(&mut self, value: &[u8]) -> Result<(), EncodeError> { - self.write_var_u32(value.len() as u32)?; - 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(()) - } -} diff --git a/src/login.rs b/src/login.rs deleted file mode 100644 index 639f02a..0000000 --- a/src/login.rs +++ /dev/null @@ -1,394 +0,0 @@ -use std::io::{Read, Write}; - -use mc_varint::{VarIntRead, VarIntWrite}; -use uuid::Uuid; - -use crate::chat::Message; -use crate::{DecodeError, EncodeError, Packet, PacketRead, PacketWrite, STRING_MAX_LENGTH}; - -const LOGIN_MAX_LENGTH: u32 = 16; -const SERVER_ID_MAX_LENGTH: u32 = 20; -const HYPHENATED_UUID_LENGTH: u32 = 36; - -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 }), - } - } -} - -pub struct LoginStart { - pub name: String, -} - -impl LoginStart { - pub fn new(name: String) -> LoginServerBoundPacket { - let login_start = LoginStart { name }; - - LoginServerBoundPacket::LoginStart(login_start) - } -} - -impl Packet for LoginStart { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_string(&self.name, LOGIN_MAX_LENGTH) - } - - fn decode(reader: &mut R) -> Result { - let name = reader.read_string(LOGIN_MAX_LENGTH)?; - - Ok(LoginStart { name }) - } -} - -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) - } -} - -impl Packet for EncryptionResponse { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_byte_array(&self.shared_secret)?; - writer.write_byte_array(&self.verify_token)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let shared_secret = reader.read_byte_array()?; - let verify_token = reader.read_byte_array()?; - - Ok(EncryptionResponse { - shared_secret, - verify_token, - }) - } -} - -pub struct LoginPluginResponse { - pub message_id: i32, - pub successful: bool, - 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) - } -} - -impl Packet for LoginPluginResponse { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_var_i32(self.message_id)?; - writer.write_bool(self.successful)?; - writer.write_all(&self.data)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let message_id = reader.read_var_i32()?; - let successful = reader.read_bool()?; - - let mut data = Vec::new(); - reader.read_to_end(data.as_mut())?; - - Ok(LoginPluginResponse { - message_id, - successful, - data, - }) - } -} - -pub struct LoginDisconnect { - pub reason: Message, -} - -impl LoginDisconnect { - pub fn new(reason: Message) -> LoginClientBoundPacket { - let login_disconnect = LoginDisconnect { reason }; - - LoginClientBoundPacket::LoginDisconnect(login_disconnect) - } -} - -impl Packet for LoginDisconnect { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_chat_message(&self.reason)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let reason = reader.read_chat_message()?; - - Ok(LoginDisconnect { reason }) - } -} - -pub struct EncryptionRequest { - 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) - } -} - -impl Packet for EncryptionRequest { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_string(&self.server_id, SERVER_ID_MAX_LENGTH)?; - writer.write_byte_array(&self.public_key)?; - writer.write_byte_array(&self.verify_token)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let server_id = reader.read_string(SERVER_ID_MAX_LENGTH)?; - let public_key = reader.read_byte_array()?; - let verify_token = reader.read_byte_array()?; - - Ok(EncryptionRequest { - server_id, - public_key, - verify_token, - }) - } -} - -pub struct LoginSuccess { - pub uuid: Uuid, - pub username: String, -} - -impl LoginSuccess { - pub fn new(uuid: Uuid, username: String) -> LoginClientBoundPacket { - let login_success = LoginSuccess { uuid, username }; - - LoginClientBoundPacket::LoginSuccess(login_success) - } -} - -impl Packet for LoginSuccess { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - let uuid_hyphenated_string = self.uuid.to_hyphenated().to_string(); - - writer.write_string(&uuid_hyphenated_string, HYPHENATED_UUID_LENGTH)?; - writer.write_string(&self.username, LOGIN_MAX_LENGTH)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let uuid_hyphenated_string = reader.read_string(HYPHENATED_UUID_LENGTH)?; - - let uuid = Uuid::parse_str(&uuid_hyphenated_string)?; - let username = reader.read_string(LOGIN_MAX_LENGTH)?; - - Ok(LoginSuccess { uuid, username }) - } -} - -pub struct SetCompression { - pub threshold: i32, -} - -impl SetCompression { - pub fn new(threshold: i32) -> LoginClientBoundPacket { - let set_compression = SetCompression { threshold }; - - LoginClientBoundPacket::SetCompression(set_compression) - } -} - -impl Packet for SetCompression { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_var_i32(self.threshold)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let threshold = reader.read_var_i32()?; - - Ok(SetCompression { threshold }) - } -} - -pub struct LoginPluginRequest { - pub message_id: i32, - pub channel: String, - 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) - } -} - -impl Packet for LoginPluginRequest { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_var_i32(self.message_id)?; - writer.write_string(&self.channel, STRING_MAX_LENGTH)?; - writer.write_all(&self.data)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let message_id = reader.read_var_i32()?; - let channel = reader.read_string(STRING_MAX_LENGTH)?; - let mut data = Vec::new(); - reader.read_to_end(data.as_mut())?; - - Ok(LoginPluginRequest { - message_id, - channel, - data, - }) - } -} diff --git a/src/status.rs b/src/status.rs deleted file mode 100644 index e617580..0000000 --- a/src/status.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::io::{Read, Write}; - -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use crate::{DecodeError, EncodeError, Packet, PacketWrite, STRING_MAX_LENGTH}; - -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, - } - } -} - -pub struct PingRequest { - pub time: u64, -} - -impl PingRequest { - pub fn new(time: u64) -> StatusServerBoundPacket { - let ping_request = PingRequest { time }; - - StatusServerBoundPacket::PingRequest(ping_request) - } -} - -impl Packet for PingRequest { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_u64::(self.time)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let time = reader.read_u64::()?; - - Ok(PingRequest { time }) - } -} - -pub struct PingResponse { - pub time: u64, -} - -impl PingResponse { - pub fn new(time: u64) -> StatusClientBoundPacket { - let ping_response = PingResponse { time }; - - StatusClientBoundPacket::PingResponse(ping_response) - } -} - -impl Packet for PingResponse { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - writer.write_u64::(self.time)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let time = reader.read_u64::()?; - - Ok(PingResponse { time }) - } -} - -#[derive(Serialize, Deserialize)] -pub struct ServerStatus { - pub version: ServerVersion, - pub description: String, - pub players: OnlinePlayers, -} - -#[derive(Serialize, Deserialize)] -pub struct ServerVersion { - pub name: String, - pub protocol: u32, -} - -#[derive(Serialize, Deserialize)] -pub struct OnlinePlayers { - pub online: u32, - pub max: u32, - pub sample: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct OnlinePlayer { - pub id: Uuid, - pub name: String, -} - -pub struct StatusResponse { - pub server_status: ServerStatus, -} - -impl StatusResponse { - pub fn new(server_status: ServerStatus) -> StatusClientBoundPacket { - let status_response = StatusResponse { server_status }; - - StatusClientBoundPacket::StatusResponse(status_response) - } -} - -impl Packet for StatusResponse { - type Output = Self; - - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - let json = serde_json::to_string(&self.server_status)?; - writer.write_string(&json, STRING_MAX_LENGTH)?; - - Ok(()) - } - - fn decode(reader: &mut R) -> Result { - let server_status = serde_json::from_reader(reader)?; - let status_response = StatusResponse { server_status }; - - Ok(status_response) - } -}