From d78175d68590cc202c9f8da36ffd0272006ce90e Mon Sep 17 00:00:00 2001 From: vagola Date: Mon, 30 Dec 2019 03:16:38 +0300 Subject: [PATCH 01/18] WIP: procmacro for automatic packet generation --- Cargo.toml | 25 ++++------------- protocol-derive/Cargo.toml | 14 ++++++++++ protocol-derive/src/lib.rs | 28 +++++++++++++++++++ protocol/Cargo.toml | 22 +++++++++++++++ {src => protocol/src}/chat.rs | 0 {src => protocol/src}/game.rs | 0 {src => protocol/src}/lib.rs | 0 {src => protocol/src}/login.rs | 15 +--------- {src => protocol/src}/status.rs | 0 .../test}/chat/click_change_page.json | 0 .../test}/chat/click_open_url.json | 0 .../test}/chat/click_run_command.json | 0 .../test}/chat/click_suggest_command.json | 0 .../test}/chat/hover_show_entity.json | 0 .../test}/chat/hover_show_item.json | 0 .../test}/chat/hover_show_text.json | 0 .../test}/chat/keybind_jump.json | 0 .../test}/chat/text_hello_world.json | 0 .../test}/chat/translate_opped_steve.json | 0 19 files changed, 70 insertions(+), 34 deletions(-) create mode 100644 protocol-derive/Cargo.toml create mode 100644 protocol-derive/src/lib.rs create mode 100644 protocol/Cargo.toml rename {src => protocol/src}/chat.rs (100%) rename {src => protocol/src}/game.rs (100%) rename {src => protocol/src}/lib.rs (100%) rename {src => protocol/src}/login.rs (96%) rename {src => protocol/src}/status.rs (100%) rename {test => protocol/test}/chat/click_change_page.json (100%) rename {test => protocol/test}/chat/click_open_url.json (100%) rename {test => protocol/test}/chat/click_run_command.json (100%) rename {test => protocol/test}/chat/click_suggest_command.json (100%) rename {test => protocol/test}/chat/hover_show_entity.json (100%) rename {test => protocol/test}/chat/hover_show_item.json (100%) rename {test => protocol/test}/chat/hover_show_text.json (100%) rename {test => protocol/test}/chat/keybind_jump.json (100%) rename {test => protocol/test}/chat/text_hello_world.json (100%) rename {test => protocol/test}/chat/translate_opped_steve.json (100%) 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..61812aa --- /dev/null +++ b/protocol-derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "minecraft-protocol-derive" +version = "0.0.0" +authors = ["vagola "] +edition = "2018" +description = "Derive macro for Minecraft packet read and write" +publish = false + +[lib] +proc-macro = true + +[dependencies] +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..8be285d --- /dev/null +++ b/protocol-derive/src/lib.rs @@ -0,0 +1,28 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(MinecraftPacket)] +pub fn derive_packet(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident; + + let output = quote! { + impl minecraft_protocol::Packet for #name { + type Output = Self; + + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + + } + + fn decode(reader: &mut R) -> Result { + + } + } + }; + + TokenStream::from(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 100% rename from src/chat.rs rename to protocol/src/chat.rs diff --git a/src/game.rs b/protocol/src/game.rs similarity index 100% rename from src/game.rs rename to protocol/src/game.rs diff --git a/src/lib.rs b/protocol/src/lib.rs similarity index 100% rename from src/lib.rs rename to protocol/src/lib.rs diff --git a/src/login.rs b/protocol/src/login.rs similarity index 96% rename from src/login.rs rename to protocol/src/login.rs index 639f02a..46d9872 100644 --- a/src/login.rs +++ b/protocol/src/login.rs @@ -106,6 +106,7 @@ impl LoginClientBoundPacket { } } +#[derive(minecraft_protocol_derive::MinecraftPacket)] pub struct LoginStart { pub name: String, } @@ -118,20 +119,6 @@ impl LoginStart { } } -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, diff --git a/src/status.rs b/protocol/src/status.rs similarity index 100% rename from src/status.rs rename to protocol/src/status.rs 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 From 73c18de13cf792be4fdcb4aaf087f53e36db4b13 Mon Sep 17 00:00:00 2001 From: vagola Date: Mon, 30 Dec 2019 15:19:12 +0300 Subject: [PATCH 02/18] Add some login packet tests --- .gitattributes | 1 + protocol/src/lib.rs | 6 +- protocol/src/login.rs | 78 ++++++++++++++++++- .../packet/login/login_plugin_response.dat | 1 + protocol/test/packet/login/login_start.dat | 1 + 5 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 .gitattributes create mode 100644 protocol/test/packet/login/login_plugin_response.dat create mode 100644 protocol/test/packet/login/login_start.dat 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/protocol/src/lib.rs b/protocol/src/lib.rs index a8bb427..4347cd1 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -28,6 +28,7 @@ pub const PROTOCOL_VERSION: usize = 498; const STRING_MAX_LENGTH: u32 = 32_768; /// Possible errors while encoding packet. +#[derive(Debug)] pub enum EncodeError { /// String length can't be more than provided value. StringTooLong { @@ -57,6 +58,7 @@ impl From for EncodeError { } /// Possible errors while decoding packet. +#[derive(Debug)] pub enum DecodeError { /// Packet was not recognized. Invalid data or wrong protocol version. UnknownPacketType { @@ -171,7 +173,7 @@ impl PacketRead for R { } fn read_string(&mut self, max_length: u32) -> Result { - let length = self.read_var_u32()?; + let length = self.read_var_i32()? as u32; if length > max_length as u32 { return Err(DecodeError::StringTooLong { length, max_length }); @@ -229,7 +231,7 @@ impl PacketWrite for W { return Err(EncodeError::StringTooLong { length, max_length }); } - self.write_var_u32(value.len() as u32)?; + self.write_var_i32(value.len() as i32)?; self.write_all(value.as_bytes())?; Ok(()) diff --git a/protocol/src/login.rs b/protocol/src/login.rs index 46d9872..8891408 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -106,7 +106,6 @@ impl LoginClientBoundPacket { } } -#[derive(minecraft_protocol_derive::MinecraftPacket)] pub struct LoginStart { pub name: String, } @@ -119,6 +118,20 @@ impl LoginStart { } } +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, @@ -379,3 +392,66 @@ impl Packet for LoginPluginRequest { }) } } + +#[cfg(test)] +mod tests { + use crate::login::LoginPluginResponse; + use crate::login::LoginStart; + use crate::Packet; + use std::io::Cursor; + + #[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_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] + ); + } +} 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_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 From 9e1d934049e1425b5465282a5ccc64622ad0d344 Mon Sep 17 00:00:00 2001 From: vagola Date: Mon, 30 Dec 2019 15:21:54 +0300 Subject: [PATCH 03/18] Fix derive macro --- protocol-derive/Cargo.toml | 2 +- protocol-derive/src/lib.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/protocol-derive/Cargo.toml b/protocol-derive/Cargo.toml index 61812aa..458b140 100644 --- a/protocol-derive/Cargo.toml +++ b/protocol-derive/Cargo.toml @@ -3,7 +3,7 @@ name = "minecraft-protocol-derive" version = "0.0.0" authors = ["vagola "] edition = "2018" -description = "Derive macro for Minecraft packet read and write" +description = "Derive macro for reading and writing Minecraft packets" publish = false [lib] diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index 8be285d..cb1d6b5 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -4,22 +4,21 @@ use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; -#[proc_macro_derive(MinecraftPacket)] +#[proc_macro_derive(Packet)] pub fn derive_packet(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let name = input.ident; let output = quote! { - impl minecraft_protocol::Packet for #name { + impl crate::Packet for #name { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - + todo!(); } fn decode(reader: &mut R) -> Result { - + todo!(); } } }; From f1a817d4472d2d4bc8ce8321e741a63d8e3bf92a Mon Sep 17 00:00:00 2001 From: vagola Date: Mon, 30 Dec 2019 20:42:06 +0300 Subject: [PATCH 04/18] Rename trait packet to packet parser --- protocol-derive/src/lib.rs | 2 +- protocol/src/game.rs | 14 +++++++------- protocol/src/lib.rs | 2 +- protocol/src/login.rs | 20 ++++++++++---------- protocol/src/status.rs | 8 ++++---- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index cb1d6b5..cba3466 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -10,7 +10,7 @@ pub fn derive_packet(input: TokenStream) -> TokenStream { let name = input.ident; let output = quote! { - impl crate::Packet for #name { + impl crate::PacketParser for #name { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { diff --git a/protocol/src/game.rs b/protocol/src/game.rs index e14a6ae..b387153 100644 --- a/protocol/src/game.rs +++ b/protocol/src/game.rs @@ -4,7 +4,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use num_derive::{FromPrimitive, ToPrimitive}; use crate::chat::Message; -use crate::{DecodeError, EncodeError, Packet, PacketRead, PacketWrite}; +use crate::{DecodeError, EncodeError, PacketParser, PacketRead, PacketWrite}; use mc_varint::{VarIntRead, VarIntWrite}; use nbt::CompoundTag; @@ -97,7 +97,7 @@ impl ServerBoundChatMessage { } } -impl Packet for ServerBoundChatMessage { +impl PacketParser for ServerBoundChatMessage { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -131,7 +131,7 @@ impl ClientBoundChatMessage { } } -impl Packet for ClientBoundChatMessage { +impl PacketParser for ClientBoundChatMessage { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -195,7 +195,7 @@ impl JoinGame { } } -impl Packet for JoinGame { +impl PacketParser for JoinGame { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -243,7 +243,7 @@ impl ServerBoundKeepAlive { } } -impl Packet for ServerBoundKeepAlive { +impl PacketParser for ServerBoundKeepAlive { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -271,7 +271,7 @@ impl ClientBoundKeepAlive { } } -impl Packet for ClientBoundKeepAlive { +impl PacketParser for ClientBoundKeepAlive { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -321,7 +321,7 @@ impl ChunkData { } } -impl Packet for ChunkData { +impl PacketParser for ChunkData { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 4347cd1..cef45e3 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -125,7 +125,7 @@ impl From for DecodeError { } } -trait Packet { +trait PacketParser { type Output; fn encode(&self, writer: &mut W) -> Result<(), EncodeError>; diff --git a/protocol/src/login.rs b/protocol/src/login.rs index 8891408..89dc1e6 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -4,7 +4,7 @@ use mc_varint::{VarIntRead, VarIntWrite}; use uuid::Uuid; use crate::chat::Message; -use crate::{DecodeError, EncodeError, Packet, PacketRead, PacketWrite, STRING_MAX_LENGTH}; +use crate::{DecodeError, EncodeError, PacketParser, PacketRead, PacketWrite, STRING_MAX_LENGTH}; const LOGIN_MAX_LENGTH: u32 = 16; const SERVER_ID_MAX_LENGTH: u32 = 20; @@ -118,7 +118,7 @@ impl LoginStart { } } -impl Packet for LoginStart { +impl PacketParser for LoginStart { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -148,7 +148,7 @@ impl EncryptionResponse { } } -impl Packet for EncryptionResponse { +impl PacketParser for EncryptionResponse { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -187,7 +187,7 @@ impl LoginPluginResponse { } } -impl Packet for LoginPluginResponse { +impl PacketParser for LoginPluginResponse { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -225,7 +225,7 @@ impl LoginDisconnect { } } -impl Packet for LoginDisconnect { +impl PacketParser for LoginDisconnect { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -263,7 +263,7 @@ impl EncryptionRequest { } } -impl Packet for EncryptionRequest { +impl PacketParser for EncryptionRequest { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -300,7 +300,7 @@ impl LoginSuccess { } } -impl Packet for LoginSuccess { +impl PacketParser for LoginSuccess { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -334,7 +334,7 @@ impl SetCompression { } } -impl Packet for SetCompression { +impl PacketParser for SetCompression { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -368,7 +368,7 @@ impl LoginPluginRequest { } } -impl Packet for LoginPluginRequest { +impl PacketParser for LoginPluginRequest { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -397,7 +397,7 @@ impl Packet for LoginPluginRequest { mod tests { use crate::login::LoginPluginResponse; use crate::login::LoginStart; - use crate::Packet; + use crate::PacketParser; use std::io::Cursor; #[test] diff --git a/protocol/src/status.rs b/protocol/src/status.rs index e617580..110b325 100644 --- a/protocol/src/status.rs +++ b/protocol/src/status.rs @@ -4,7 +4,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{DecodeError, EncodeError, Packet, PacketWrite, STRING_MAX_LENGTH}; +use crate::{DecodeError, EncodeError, PacketParser, PacketWrite, STRING_MAX_LENGTH}; pub enum StatusServerBoundPacket { StatusRequest, @@ -58,7 +58,7 @@ impl PingRequest { } } -impl Packet for PingRequest { +impl PacketParser for PingRequest { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -86,7 +86,7 @@ impl PingResponse { } } -impl Packet for PingResponse { +impl PacketParser for PingResponse { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { @@ -140,7 +140,7 @@ impl StatusResponse { } } -impl Packet for StatusResponse { +impl PacketParser for StatusResponse { type Output = Self; fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { From 1fca9c3666fb63364054c26e2636305dc404c1fa Mon Sep 17 00:00:00 2001 From: vagola Date: Mon, 30 Dec 2019 21:54:48 +0300 Subject: [PATCH 05/18] Finished with tests for server bound login packets --- protocol/src/lib.rs | 4 +-- protocol/src/login.rs | 31 ++++++++++++++++++- .../test/packet/login/encryption_response.dat | 3 ++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 protocol/test/packet/login/encryption_response.dat diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index cef45e3..9f740af 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -186,7 +186,7 @@ impl PacketRead for R { } fn read_byte_array(&mut self) -> Result, DecodeError> { - let length = self.read_var_u32()?; + let length = self.read_var_i32()?; let mut buf = vec![0; length as usize]; self.read_exact(&mut buf)?; @@ -238,7 +238,7 @@ impl PacketWrite for W { } fn write_byte_array(&mut self, value: &[u8]) -> Result<(), EncodeError> { - self.write_var_u32(value.len() as u32)?; + self.write_var_i32(value.len() as i32)?; self.write_all(value)?; Ok(()) diff --git a/protocol/src/login.rs b/protocol/src/login.rs index 89dc1e6..8e15b41 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -395,8 +395,8 @@ impl PacketParser for LoginPluginRequest { #[cfg(test)] mod tests { - use crate::login::LoginPluginResponse; use crate::login::LoginStart; + use crate::login::{EncryptionResponse, LoginPluginResponse}; use crate::PacketParser; use std::io::Cursor; @@ -424,6 +424,35 @@ mod tests { 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 { 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 From 133ebf8888f0da9b02664651c2aa07c2f7955a0b Mon Sep 17 00:00:00 2001 From: vagola Date: Mon, 30 Dec 2019 22:38:43 +0300 Subject: [PATCH 06/18] Finished with tests for client bound login packets --- protocol/src/login.rs | 146 +++++++++++++++++- .../test/packet/login/encryption_request.dat | 3 + .../test/packet/login/login_disconnect.dat | 1 + .../packet/login/login_plugin_request.dat | 1 + .../packet/login/login_set_compression.dat | 1 + protocol/test/packet/login/login_success.dat | 1 + 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 protocol/test/packet/login/encryption_request.dat create mode 100644 protocol/test/packet/login/login_disconnect.dat create mode 100644 protocol/test/packet/login/login_plugin_request.dat create mode 100644 protocol/test/packet/login/login_set_compression.dat create mode 100644 protocol/test/packet/login/login_success.dat diff --git a/protocol/src/login.rs b/protocol/src/login.rs index 8e15b41..479aedb 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -395,10 +395,13 @@ impl PacketParser for LoginPluginRequest { #[cfg(test)] mod tests { - use crate::login::LoginStart; + use crate::chat::{MessageBuilder, Payload}; + use crate::login::{EncryptionRequest, LoginDisconnect, LoginPluginRequest, SetCompression}; use crate::login::{EncryptionResponse, LoginPluginResponse}; + use crate::login::{LoginStart, LoginSuccess}; use crate::PacketParser; use std::io::Cursor; + use uuid::Uuid; #[test] fn test_login_start_packet_encode() { @@ -483,4 +486,145 @@ mod tests { vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ); } + + #[test] + fn test_login_disconnect_encode() { + let login_disconnect = LoginDisconnect { + reason: MessageBuilder::builder(Payload::text("Message")).build(), + }; + + 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, + MessageBuilder::builder(Payload::text("Message")).build() + ); + } + + #[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/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/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_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_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 From 1d61f198f2036273c059f69ee63e75261f28835d Mon Sep 17 00:00:00 2001 From: vagola Date: Tue, 31 Dec 2019 00:45:32 +0300 Subject: [PATCH 07/18] Add tests for status packets --- protocol/src/lib.rs | 2 +- protocol/src/login.rs | 6 +- protocol/src/status.rs | 132 +++++++++++++++++- protocol/test/packet/status/ping_request.dat | Bin 0 -> 8 bytes protocol/test/packet/status/ping_response.dat | Bin 0 -> 8 bytes .../test/packet/status/status_response.dat | 1 + 6 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 protocol/test/packet/status/ping_request.dat create mode 100644 protocol/test/packet/status/ping_response.dat create mode 100644 protocol/test/packet/status/status_response.dat diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 9f740af..69bd201 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -23,7 +23,7 @@ pub mod login; pub mod status; /// Current supported protocol version. -pub const PROTOCOL_VERSION: usize = 498; +pub const PROTOCOL_VERSION: u32 = 575; /// String maximum length. const STRING_MAX_LENGTH: u32 = 32_768; diff --git a/protocol/src/login.rs b/protocol/src/login.rs index 479aedb..dce107c 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -395,7 +395,7 @@ impl PacketParser for LoginPluginRequest { #[cfg(test)] mod tests { - use crate::chat::{MessageBuilder, Payload}; + use crate::chat::{Message, Payload}; use crate::login::{EncryptionRequest, LoginDisconnect, LoginPluginRequest, SetCompression}; use crate::login::{EncryptionResponse, LoginPluginResponse}; use crate::login::{LoginStart, LoginSuccess}; @@ -490,7 +490,7 @@ mod tests { #[test] fn test_login_disconnect_encode() { let login_disconnect = LoginDisconnect { - reason: MessageBuilder::builder(Payload::text("Message")).build(), + reason: Message::new(Payload::text("Message")), }; let mut vec = Vec::new(); @@ -510,7 +510,7 @@ mod tests { assert_eq!( login_disconnect.reason, - MessageBuilder::builder(Payload::text("Message")).build() + Message::new(Payload::text("Description")) ); } diff --git a/protocol/src/status.rs b/protocol/src/status.rs index 110b325..2dc1b7a 100644 --- a/protocol/src/status.rs +++ b/protocol/src/status.rs @@ -4,7 +4,8 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{DecodeError, EncodeError, PacketParser, PacketWrite, STRING_MAX_LENGTH}; +use crate::chat::Message; +use crate::{DecodeError, EncodeError, PacketParser, PacketRead, PacketWrite, STRING_MAX_LENGTH}; pub enum StatusServerBoundPacket { StatusRequest, @@ -105,8 +106,8 @@ impl PacketParser for PingResponse { #[derive(Serialize, Deserialize)] pub struct ServerStatus { pub version: ServerVersion, - pub description: String, pub players: OnlinePlayers, + pub description: Message, } #[derive(Serialize, Deserialize)] @@ -117,15 +118,15 @@ pub struct ServerVersion { #[derive(Serialize, Deserialize)] pub struct OnlinePlayers { - pub online: u32, pub max: u32, + pub online: u32, pub sample: Vec, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct OnlinePlayer { - pub id: Uuid, pub name: String, + pub id: Uuid, } pub struct StatusResponse { @@ -151,9 +152,128 @@ impl PacketParser for StatusResponse { } fn decode(reader: &mut R) -> Result { - let server_status = serde_json::from_reader(reader)?; + let json = reader.read_string(STRING_MAX_LENGTH)?; + let server_status = serde_json::from_str(&json)?; let status_response = StatusResponse { server_status }; Ok(status_response) } } + +#[cfg(test)] +mod tests { + use crate::chat::{Message, Payload}; + use crate::status::{ + OnlinePlayer, OnlinePlayers, PingRequest, PingResponse, ServerStatus, ServerVersion, + StatusResponse, + }; + use crate::{PacketParser, PROTOCOL_VERSION}; + 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: PROTOCOL_VERSION, + }; + + 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/status/ping_request.dat b/protocol/test/packet/status/ping_request.dat new file mode 100644 index 0000000000000000000000000000000000000000..36856e898b9bfb36cb10f238657aec85deb223e7 GIT binary patch literal 8 PcmZQzV9bw5DtiS029*Mf literal 0 HcmV?d00001 diff --git a/protocol/test/packet/status/ping_response.dat b/protocol/test/packet/status/ping_response.dat new file mode 100644 index 0000000000000000000000000000000000000000..36856e898b9bfb36cb10f238657aec85deb223e7 GIT binary patch literal 8 PcmZQzV9bw5DtiS029*Mf literal 0 HcmV?d00001 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 From 1d524405aa0ffe44f236d6934b91fbeddf42a10f Mon Sep 17 00:00:00 2001 From: vagola Date: Tue, 31 Dec 2019 03:07:07 +0300 Subject: [PATCH 08/18] Fix test --- protocol/src/login.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/src/login.rs b/protocol/src/login.rs index dce107c..aed614a 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -510,7 +510,7 @@ mod tests { assert_eq!( login_disconnect.reason, - Message::new(Payload::text("Description")) + Message::new(Payload::text("Message")) ); } From 4bb68bb22d285078aa189ed4c049d459ffae3e1e Mon Sep 17 00:00:00 2001 From: vagola Date: Tue, 31 Dec 2019 04:07:45 +0300 Subject: [PATCH 09/18] Add tests for chat message, keep alive and join game --- protocol/src/game.rs | 150 +++++++++++++++++- protocol/src/lib.rs | 2 +- protocol/src/status.rs | 4 +- .../packet/game/client_bound_chat_message.dat | 1 + .../packet/game/client_bound_keep_alive.dat | Bin 0 -> 8 bytes protocol/test/packet/game/join_game.dat | Bin 0 -> 20 bytes .../packet/game/server_bound_chat_message.dat | 1 + .../packet/game/server_bound_keep_alive.dat | Bin 0 -> 8 bytes 8 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 protocol/test/packet/game/client_bound_chat_message.dat create mode 100644 protocol/test/packet/game/client_bound_keep_alive.dat create mode 100644 protocol/test/packet/game/join_game.dat create mode 100644 protocol/test/packet/game/server_bound_chat_message.dat create mode 100644 protocol/test/packet/game/server_bound_keep_alive.dat diff --git a/protocol/src/game.rs b/protocol/src/game.rs index b387153..8133dbc 100644 --- a/protocol/src/game.rs +++ b/protocol/src/game.rs @@ -204,7 +204,7 @@ impl PacketParser for JoinGame { 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_var_i32(self.view_distance as i32)?; writer.write_bool(self.reduced_debug_info)?; Ok(()) @@ -216,7 +216,7 @@ impl PacketParser for JoinGame { 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 view_distance = reader.read_var_i32()? as u8; let reduced_debug_info = reader.read_bool()?; Ok(JoinGame { @@ -367,3 +367,149 @@ impl PacketParser for ChunkData { }) } } + +#[cfg(test)] +mod tests { + use crate::chat::{Message, Payload}; + use crate::game::{ + ClientBoundChatMessage, ClientBoundKeepAlive, GameMode, JoinGame, MessagePosition, + ServerBoundChatMessage, ServerBoundKeepAlive, + }; + use crate::PacketParser; + 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_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); + } +} diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 69bd201..af20277 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -23,7 +23,7 @@ pub mod login; pub mod status; /// Current supported protocol version. -pub const PROTOCOL_VERSION: u32 = 575; +pub const PROTOCOL_VERSION: u32 = 498; /// String maximum length. const STRING_MAX_LENGTH: u32 = 32_768; diff --git a/protocol/src/status.rs b/protocol/src/status.rs index 2dc1b7a..8a5cacc 100644 --- a/protocol/src/status.rs +++ b/protocol/src/status.rs @@ -167,7 +167,7 @@ mod tests { OnlinePlayer, OnlinePlayers, PingRequest, PingResponse, ServerStatus, ServerVersion, StatusResponse, }; - use crate::{PacketParser, PROTOCOL_VERSION}; + use crate::PacketParser; use std::io::Cursor; use uuid::Uuid; @@ -223,7 +223,7 @@ mod tests { fn test_status_response_encode() { let version = ServerVersion { name: String::from("1.15.1"), - protocol: PROTOCOL_VERSION, + protocol: 575, }; let player = OnlinePlayer { 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 0000000000000000000000000000000000000000..51d9e4cb88bdeba56ec28ea7b2da446bf6fa714b GIT binary patch literal 8 NcmZQz00QPUUH}2J0Qvv` literal 0 HcmV?d00001 diff --git a/protocol/test/packet/game/join_game.dat b/protocol/test/packet/game/join_game.dat new file mode 100644 index 0000000000000000000000000000000000000000..e2e43830bbbc50042243e09ddbe8d36d11c7ac27 GIT binary patch literal 20 bcmZQzV31~JU| Date: Tue, 31 Dec 2019 14:22:35 +0300 Subject: [PATCH 10/18] Add chunk data packet test --- protocol/src/game.rs | 52 +++++++++++++++++++---- protocol/test/packet/game/chunk_data.dat | Bin 0 -> 52 bytes 2 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 protocol/test/packet/game/chunk_data.dat diff --git a/protocol/src/game.rs b/protocol/src/game.rs index 8133dbc..fc8efe4 100644 --- a/protocol/src/game.rs +++ b/protocol/src/game.rs @@ -291,7 +291,7 @@ pub struct ChunkData { pub x: i32, pub z: i32, pub full: bool, - pub primary_mask: u32, + pub primary_mask: i32, pub heights: CompoundTag, pub data: Vec, pub tiles: Vec, @@ -302,7 +302,7 @@ impl ChunkData { x: i32, z: i32, full: bool, - primary_mask: u32, + primary_mask: i32, heights: CompoundTag, data: Vec, tiles: Vec, @@ -328,10 +328,10 @@ impl PacketParser for ChunkData { writer.write_i32::(self.x)?; writer.write_i32::(self.z)?; writer.write_bool(self.full)?; - writer.write_var_u32(self.primary_mask)?; + writer.write_var_i32(self.primary_mask)?; writer.write_compound_tag(&self.heights)?; writer.write_byte_array(&self.data)?; - writer.write_var_u32(self.tiles.len() as u32)?; + writer.write_var_i32(self.tiles.len() as i32)?; for tile_compound_tag in self.tiles.iter() { writer.write_compound_tag(&tile_compound_tag)?; @@ -344,11 +344,11 @@ impl PacketParser for ChunkData { let x = reader.read_i32::()?; let z = reader.read_i32::()?; let full = reader.read_bool()?; - let primary_mask = reader.read_var_u32()?; + let primary_mask = reader.read_var_i32()?; let heights = reader.read_compound_tag()?; let data = reader.read_byte_array()?; - let tiles_length = reader.read_var_u32()?; + let tiles_length = reader.read_var_i32()?; let mut tiles = Vec::new(); for _ in 0..tiles_length { @@ -372,10 +372,11 @@ impl PacketParser for ChunkData { mod tests { use crate::chat::{Message, Payload}; use crate::game::{ - ClientBoundChatMessage, ClientBoundKeepAlive, GameMode, JoinGame, MessagePosition, - ServerBoundChatMessage, ServerBoundKeepAlive, + ChunkData, ClientBoundChatMessage, ClientBoundKeepAlive, GameMode, JoinGame, + MessagePosition, ServerBoundChatMessage, ServerBoundKeepAlive, }; use crate::PacketParser; + use nbt::CompoundTag; use std::io::Cursor; #[test] @@ -512,4 +513,39 @@ mod tests { 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/test/packet/game/chunk_data.dat b/protocol/test/packet/game/chunk_data.dat new file mode 100644 index 0000000000000000000000000000000000000000..fb01ab1b9962f49fe3de11bed7d4775cef59ba74 GIT binary patch literal 52 zcmezW|NlP*1_oBf|Noh}7`QxAGt)Cld=m?b8Mqjkm|0la*f}`47=bb&nK`Mhc_o=8 Gl?(vDj1FP| literal 0 HcmV?d00001 From f43592c01fda9d08352583be01cc318c945aebc9 Mon Sep 17 00:00:00 2001 From: vagola Date: Thu, 2 Jan 2020 04:52:46 +0300 Subject: [PATCH 11/18] Save some progress towards derive macro --- protocol-derive/Cargo.toml | 6 +- protocol-derive/src/lib.rs | 85 ++++++++++++--- protocol/src/game.rs | 169 +++-------------------------- protocol/src/lib.rs | 217 ++++++++++++++++++++++++++++++++++++- protocol/src/login.rs | 186 +++---------------------------- protocol/src/status.rs | 71 ++---------- 6 files changed, 327 insertions(+), 407 deletions(-) diff --git a/protocol-derive/Cargo.toml b/protocol-derive/Cargo.toml index 458b140..0d689e2 100644 --- a/protocol-derive/Cargo.toml +++ b/protocol-derive/Cargo.toml @@ -10,5 +10,7 @@ publish = false proc-macro = true [dependencies] -syn = "1.0" -quote = "1.0" +# Versions match serde crate to reduce compile time. +proc-macro2 = "0.4.30" +syn = "0.15.44" +quote = "0.6.13" diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index cba3466..1d416ac 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -1,27 +1,80 @@ extern crate proc_macro; -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput}; +use proc_macro::TokenStream as TokenStream1; +use proc_macro2::Ident; +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, TokenStreamExt}; +use syn::{parse_macro_input, Data, DeriveInput, Field, Fields}; #[proc_macro_derive(Packet)] -pub fn derive_packet(input: TokenStream) -> TokenStream { +pub fn derive_packet(input: proc_macro::TokenStream) -> TokenStream1 { let input = parse_macro_input!(input as DeriveInput); - let name = input.ident; + let name = &input.ident; - let output = quote! { - impl crate::PacketParser for #name { - type Output = Self; + match input.data { + Data::Struct(data) => { + let fields = &data.fields; - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - todo!(); - } + let encoder = impl_encoder_trait(name, fields); + let decoder = impl_decoder_trait(name, fields); - fn decode(reader: &mut R) -> Result { - todo!(); + TokenStream1::from(quote! { + #encoder + + #decoder + }) + } + _ => panic!("Packet derive are available only for structures"), + } +} + +fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { + let encode = quote_field(fields, |field| { + let name = &field.ident; + + quote! { + Encoder::encode(&self.#name, writer); + } + }); + + quote! { + impl crate::Encoder for #name { + fn encode(&self, writer: &mut W) -> Result<(), crate::EncodeError> { + #encode + + Ok(()) } } - }; - - TokenStream::from(output) + } +} + +fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { + let decode = quote_field(fields, |field| { + quote! { + todo!(); + } + }); + + quote! { + impl crate::Decoder for #name { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + #decode + } + } + } +} + +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!("Packet derive are available only for named fields"), + } + + output } diff --git a/protocol/src/game.rs b/protocol/src/game.rs index fc8efe4..298fa18 100644 --- a/protocol/src/game.rs +++ b/protocol/src/game.rs @@ -1,12 +1,12 @@ -use std::io::{Read, Write}; - -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use num_derive::{FromPrimitive, ToPrimitive}; use crate::chat::Message; -use crate::{DecodeError, EncodeError, PacketParser, PacketRead, PacketWrite}; -use mc_varint::{VarIntRead, VarIntWrite}; +use crate::DecodeError; +use crate::Decoder; +use crate::Encoder; +use minecraft_protocol_derive::Packet; use nbt::CompoundTag; +use std::io::Read; const SERVER_BOUND_CHAT_MESSAGE_MAX_LENGTH: u32 = 256; const LEVEL_TYPE_MAX_LENGTH: u32 = 16; @@ -85,6 +85,7 @@ impl GameClientBoundPacket { } } +#[derive(Packet, Debug)] pub struct ServerBoundChatMessage { pub message: String, } @@ -97,20 +98,7 @@ impl ServerBoundChatMessage { } } -impl PacketParser 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 }) - } -} - +#[derive(Packet, Debug)] pub struct ClientBoundChatMessage { pub message: Message, pub position: MessagePosition, @@ -131,27 +119,7 @@ impl ClientBoundChatMessage { } } -impl PacketParser 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)] +#[derive(Packet, Debug)] pub struct JoinGame { pub entity_id: u32, pub game_mode: GameMode, @@ -195,42 +163,7 @@ impl JoinGame { } } -impl PacketParser 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_i32(self.view_distance as i32)?; - 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_i32()? 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, - }) - } -} - +#[derive(Packet)] pub struct ServerBoundKeepAlive { pub id: u64, } @@ -243,22 +176,7 @@ impl ServerBoundKeepAlive { } } -impl PacketParser 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 }) - } -} - +#[derive(Packet)] pub struct ClientBoundKeepAlive { pub id: u64, } @@ -271,22 +189,7 @@ impl ClientBoundKeepAlive { } } -impl PacketParser 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 }) - } -} - +#[derive(Packet, Debug)] pub struct ChunkData { pub x: i32, pub z: i32, @@ -321,53 +224,6 @@ impl ChunkData { } } -impl PacketParser 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_i32(self.primary_mask)?; - writer.write_compound_tag(&self.heights)?; - writer.write_byte_array(&self.data)?; - writer.write_var_i32(self.tiles.len() as i32)?; - - 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_i32()?; - let heights = reader.read_compound_tag()?; - let data = reader.read_byte_array()?; - - let tiles_length = reader.read_var_i32()?; - 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, - }) - } -} - #[cfg(test)] mod tests { use crate::chat::{Message, Payload}; @@ -375,7 +231,8 @@ mod tests { ChunkData, ClientBoundChatMessage, ClientBoundKeepAlive, GameMode, JoinGame, MessagePosition, ServerBoundChatMessage, ServerBoundKeepAlive, }; - use crate::PacketParser; + use crate::Decoder; + use crate::Encoder; use nbt::CompoundTag; use std::io::Cursor; diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index af20277..ec8bc98 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -6,8 +6,8 @@ use std::io; use std::io::{Read, Write}; use std::string::FromUtf8Error; -use byteorder::ReadBytesExt; use byteorder::WriteBytesExt; +use byteorder::{BigEndian, ReadBytesExt}; use mc_varint::{VarIntRead, VarIntWrite}; use serde_json::error::Error as JsonError; use uuid::parser::ParseError as UuidParseError; @@ -16,6 +16,8 @@ use crate::chat::Message; use nbt::decode::TagDecodeError; use nbt::CompoundTag; use num_traits::{FromPrimitive, ToPrimitive}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; pub mod chat; pub mod game; @@ -125,10 +127,12 @@ impl From for DecodeError { } } -trait PacketParser { - type Output; - +trait Encoder { fn encode(&self, writer: &mut W) -> Result<(), EncodeError>; +} + +trait Decoder { + type Output; fn decode(reader: &mut R) -> Result; } @@ -261,3 +265,208 @@ impl PacketWrite for W { Ok(()) } } + +// TODO: Replace primitive impls with macros. + +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 T { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + Ok(writer.write_enum(self)?) + } +} + +impl Decoder for T { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + Ok(reader.read_enum()) + } +} + +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 buf = [0; 16]; + reader.read_exact(&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) + } +} + +impl Encoder for T { + fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { + let json = serde_json::to_string(self)?; + writer.write_string(&json, STRING_MAX_LENGTH)?; + + Ok(()) + } +} + +impl<'de, T: Deserialize<'de>> Decoder for T { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + let json = reader.read_string(STRING_MAX_LENGTH)?; + serde_json::from_str(&json)? + } +} diff --git a/protocol/src/login.rs b/protocol/src/login.rs index aed614a..e78130e 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -1,10 +1,11 @@ -use std::io::{Read, Write}; - -use mc_varint::{VarIntRead, VarIntWrite}; +use crate::chat::Message; +use crate::DecodeError; +use crate::Decoder; +use crate::Encoder; +use std::io::Read; use uuid::Uuid; -use crate::chat::Message; -use crate::{DecodeError, EncodeError, PacketParser, PacketRead, PacketWrite, STRING_MAX_LENGTH}; +use minecraft_protocol_derive::Packet; const LOGIN_MAX_LENGTH: u32 = 16; const SERVER_ID_MAX_LENGTH: u32 = 20; @@ -106,6 +107,7 @@ impl LoginClientBoundPacket { } } +#[derive(Packet, Debug)] pub struct LoginStart { pub name: String, } @@ -118,20 +120,7 @@ impl LoginStart { } } -impl PacketParser 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 }) - } -} - +#[derive(Packet, Debug)] pub struct EncryptionResponse { pub shared_secret: Vec, pub verify_token: Vec, @@ -148,27 +137,7 @@ impl EncryptionResponse { } } -impl PacketParser 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, - }) - } -} - +#[derive(Packet, Debug)] pub struct LoginPluginResponse { pub message_id: i32, pub successful: bool, @@ -187,32 +156,7 @@ impl LoginPluginResponse { } } -impl PacketParser 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, - }) - } -} - +#[derive(Packet, Debug)] pub struct LoginDisconnect { pub reason: Message, } @@ -225,22 +169,7 @@ impl LoginDisconnect { } } -impl PacketParser 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 }) - } -} - +#[derive(Packet, Debug)] pub struct EncryptionRequest { pub server_id: String, pub public_key: Vec, @@ -263,30 +192,7 @@ impl EncryptionRequest { } } -impl PacketParser 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, - }) - } -} - +#[derive(Packet, Debug)] pub struct LoginSuccess { pub uuid: Uuid, pub username: String, @@ -300,28 +206,7 @@ impl LoginSuccess { } } -impl PacketParser 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 }) - } -} - +#[derive(Packet, Debug)] pub struct SetCompression { pub threshold: i32, } @@ -334,22 +219,7 @@ impl SetCompression { } } -impl PacketParser 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 }) - } -} - +#[derive(Packet, Debug)] pub struct LoginPluginRequest { pub message_id: i32, pub channel: String, @@ -368,38 +238,14 @@ impl LoginPluginRequest { } } -impl PacketParser 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, - }) - } -} - #[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::PacketParser; + use crate::Decoder; + use crate::Encoder; use std::io::Cursor; use uuid::Uuid; diff --git a/protocol/src/status.rs b/protocol/src/status.rs index 8a5cacc..eddb865 100644 --- a/protocol/src/status.rs +++ b/protocol/src/status.rs @@ -1,11 +1,11 @@ -use std::io::{Read, Write}; - -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::chat::Message; -use crate::{DecodeError, EncodeError, PacketParser, PacketRead, PacketWrite, STRING_MAX_LENGTH}; +use crate::DecodeError; +use crate::Decoder; +use minecraft_protocol_derive::Packet; +use std::io::Read; pub enum StatusServerBoundPacket { StatusRequest, @@ -47,6 +47,7 @@ impl StatusClientBoundPacket { } } +#[derive(Packet, Debug)] pub struct PingRequest { pub time: u64, } @@ -59,22 +60,7 @@ impl PingRequest { } } -impl PacketParser 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 }) - } -} - +#[derive(Packet, Debug)] pub struct PingResponse { pub time: u64, } @@ -87,36 +73,20 @@ impl PingResponse { } } -impl PacketParser 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)] +#[derive(Serialize, Deserialize, Debug)] pub struct ServerStatus { pub version: ServerVersion, pub players: OnlinePlayers, pub description: Message, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct ServerVersion { pub name: String, pub protocol: u32, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct OnlinePlayers { pub max: u32, pub online: u32, @@ -129,6 +99,7 @@ pub struct OnlinePlayer { pub id: Uuid, } +#[derive(Packet, Debug)] pub struct StatusResponse { pub server_status: ServerStatus, } @@ -141,25 +112,6 @@ impl StatusResponse { } } -impl PacketParser 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 json = reader.read_string(STRING_MAX_LENGTH)?; - let server_status = serde_json::from_str(&json)?; - let status_response = StatusResponse { server_status }; - - Ok(status_response) - } -} - #[cfg(test)] mod tests { use crate::chat::{Message, Payload}; @@ -167,7 +119,8 @@ mod tests { OnlinePlayer, OnlinePlayers, PingRequest, PingResponse, ServerStatus, ServerVersion, StatusResponse, }; - use crate::PacketParser; + use crate::Decoder; + use crate::Encoder; use std::io::Cursor; use uuid::Uuid; From a4b7f100a869e2d3c8b71f564b3fb508c7254a45 Mon Sep 17 00:00:00 2001 From: vagola Date: Thu, 2 Jan 2020 15:25:22 +0300 Subject: [PATCH 12/18] Add macros to impl concentrate enum and json --- protocol-derive/src/lib.rs | 4 +- protocol/src/chat.rs | 3 + protocol/src/game.rs | 6 +- protocol/src/lib.rs | 195 +++++++++++++++++++------------------ protocol/src/login.rs | 1 - protocol/src/status.rs | 3 + 6 files changed, 114 insertions(+), 98 deletions(-) diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index 1d416ac..def0ac0 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -33,7 +33,7 @@ fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { let name = &field.ident; quote! { - Encoder::encode(&self.#name, writer); + crate::Encoder::encode(&self.#name, writer); } }); @@ -49,7 +49,7 @@ fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { } fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { - let decode = quote_field(fields, |field| { + let decode = quote_field(fields, |_field| { quote! { todo!(); } diff --git a/protocol/src/chat.rs b/protocol/src/chat.rs index 415ec0c..2276673 100644 --- a/protocol/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 index 298fa18..ac25f1a 100644 --- a/protocol/src/game.rs +++ b/protocol/src/game.rs @@ -1,9 +1,9 @@ use num_derive::{FromPrimitive, ToPrimitive}; use crate::chat::Message; +use crate::impl_enum_encoder_decoder; use crate::DecodeError; use crate::Decoder; -use crate::Encoder; use minecraft_protocol_derive::Packet; use nbt::CompoundTag; use std::io::Read; @@ -111,6 +111,8 @@ pub enum MessagePosition { HotBar, } +impl_enum_encoder_decoder!(MessagePosition); + impl ClientBoundChatMessage { pub fn new(message: Message, position: MessagePosition) -> GameClientBoundPacket { let chat_message = ClientBoundChatMessage { message, position }; @@ -139,6 +141,8 @@ pub enum GameMode { Hardcore = 8, } +impl_enum_encoder_decoder!(GameMode); + impl JoinGame { pub fn new( entity_id: u32, diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index ec8bc98..9a0c35f 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -6,8 +6,7 @@ use std::io; use std::io::{Read, Write}; use std::string::FromUtf8Error; -use byteorder::WriteBytesExt; -use byteorder::{BigEndian, ReadBytesExt}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use mc_varint::{VarIntRead, VarIntWrite}; use serde_json::error::Error as JsonError; use uuid::parser::ParseError as UuidParseError; @@ -16,7 +15,6 @@ use crate::chat::Message; use nbt::decode::TagDecodeError; use nbt::CompoundTag; use num_traits::{FromPrimitive, ToPrimitive}; -use serde::{Deserialize, Serialize}; use uuid::Uuid; pub mod chat; @@ -137,23 +135,8 @@ trait Decoder { 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 { +trait EncoderWriteExt { fn write_bool(&mut self, value: bool) -> Result<(), EncodeError>; fn write_string(&mut self, value: &str, max_length: u32) -> Result<(), EncodeError>; @@ -167,57 +150,22 @@ trait PacketWrite { 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), - } - } +/// 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: u32) -> Result { - let length = self.read_var_i32()? as u32; + fn read_string(&mut self, max_length: u32) -> Result; - if length > max_length as u32 { - return Err(DecodeError::StringTooLong { length, max_length }); - } + fn read_byte_array(&mut self) -> Result, DecodeError>; - let mut buf = vec![0; length as usize]; - self.read_exact(&mut buf)?; + fn read_chat_message(&mut self) -> Result; - Ok(String::from_utf8(buf)?) - } + fn read_enum(&mut self) -> Result; - 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)?) - } + fn read_compound_tag(&mut self) -> Result; } -impl PacketWrite for W { +impl EncoderWriteExt for W { fn write_bool(&mut self, value: bool) -> Result<(), EncodeError> { if value { self.write_u8(1)?; @@ -266,7 +214,55 @@ impl PacketWrite for W { } } -// TODO: Replace primitive impls with macros. +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: u32) -> Result { + let length = self.read_var_i32()? as 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_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> { @@ -366,20 +362,6 @@ impl Decoder for bool { } } -impl Encoder for T { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - Ok(writer.write_enum(self)?) - } -} - -impl Decoder for T { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - Ok(reader.read_enum()) - } -} - impl Encoder for Vec { fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { Ok(writer.write_byte_array(self)?) @@ -404,8 +386,8 @@ impl Decoder for Uuid { type Output = Self; fn decode(reader: &mut R) -> Result { - let buf = [0; 16]; - reader.read_exact(&buf)?; + let mut buf = [0; 16]; + reader.read_exact(&mut buf)?; Ok(Uuid::from_bytes(buf)) } @@ -453,20 +435,45 @@ impl Decoder for Vec { } } -impl Encoder for T { - fn encode(&self, writer: &mut W) -> Result<(), EncodeError> { - let json = serde_json::to_string(self)?; - writer.write_string(&json, STRING_MAX_LENGTH)?; +#[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)?) + } + } - Ok(()) - } -} + impl crate::Decoder for $ty { + type Output = Self; -impl<'de, T: Deserialize<'de>> Decoder for T { - type Output = Self; + fn decode(reader: &mut R) -> Result { + Ok(crate::DecoderReadExt::read_enum(reader)?) + } + } + ); +); - fn decode(reader: &mut R) -> Result { - let json = reader.read_string(STRING_MAX_LENGTH)?; - serde_json::from_str(&json)? - } -} +#[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)?) + } + } + ); +); diff --git a/protocol/src/login.rs b/protocol/src/login.rs index e78130e..8b20863 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -1,7 +1,6 @@ use crate::chat::Message; use crate::DecodeError; use crate::Decoder; -use crate::Encoder; use std::io::Read; use uuid::Uuid; diff --git a/protocol/src/status.rs b/protocol/src/status.rs index eddb865..1d8f195 100644 --- a/protocol/src/status.rs +++ b/protocol/src/status.rs @@ -2,6 +2,7 @@ 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; @@ -104,6 +105,8 @@ 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 }; From 6d504076707b4dc26757b9d0ca4c4cac80859c32 Mon Sep 17 00:00:00 2001 From: vagola Date: Fri, 3 Jan 2020 15:28:45 +0300 Subject: [PATCH 13/18] Add attributes parsing --- protocol-derive/Cargo.toml | 7 ++--- protocol-derive/src/lib.rs | 63 ++++++++++++++++++++++++++++++++++++-- protocol/src/login.rs | 2 ++ 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/protocol-derive/Cargo.toml b/protocol-derive/Cargo.toml index 0d689e2..e0734a5 100644 --- a/protocol-derive/Cargo.toml +++ b/protocol-derive/Cargo.toml @@ -10,7 +10,6 @@ publish = false proc-macro = true [dependencies] -# Versions match serde crate to reduce compile time. -proc-macro2 = "0.4.30" -syn = "0.15.44" -quote = "0.6.13" +proc-macro2 = "1.0" +syn = "1.0" +quote = "1.0" diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index def0ac0..fe04136 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -4,9 +4,10 @@ use proc_macro::TokenStream as TokenStream1; use proc_macro2::Ident; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, TokenStreamExt}; -use syn::{parse_macro_input, Data, DeriveInput, Field, Fields}; +use std::iter::FromIterator; +use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Lit, Meta, NestedMeta}; -#[proc_macro_derive(Packet)] +#[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; @@ -32,8 +33,11 @@ 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_field_meta(&unparsed_meta); + quote! { - crate::Encoder::encode(&self.#name, writer); + crate::Encoder::encode(&self.#name, writer)?; } }); @@ -66,6 +70,59 @@ fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { } } +#[derive(Debug)] +struct PacketFieldMeta { + module: Option, + max_length: Option, +} + +fn parse_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(m)) => match &m.path { + p if p.is_ident("with") => { + if let Lit::Str(lit_str) = &m.lit { + module = Some(lit_str.value()); + } + } + p if p.is_ident("max_length") => { + if let Lit::Int(lit_int) = &m.lit { + max_length = Some( + lit_int + .base10_parse::() + .expect("Failed to parse max length attribute"), + ); + } + } + p => panic!( + "Packet derive received unrecognized attribute : \"{}\"", + p.get_ident().unwrap() + ), + }, + _ => panic!("Packet derive support 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!("Packet derive support only list attributes"), + }) + .flatten() + .collect() +} + fn quote_field TokenStream2>(fields: &Fields, func: F) -> TokenStream2 { let mut output = quote!(); diff --git a/protocol/src/login.rs b/protocol/src/login.rs index 8b20863..9482ac9 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -170,6 +170,7 @@ impl LoginDisconnect { #[derive(Packet, Debug)] pub struct EncryptionRequest { + #[packet(max_length = 20)] pub server_id: String, pub public_key: Vec, pub verify_token: Vec, @@ -207,6 +208,7 @@ impl LoginSuccess { #[derive(Packet, Debug)] pub struct SetCompression { + #[packet(with = "varint")] pub threshold: i32, } From 0a0500239be29d527af448f7f748e375c4d9954e Mon Sep 17 00:00:00 2001 From: vagola Date: Fri, 3 Jan 2020 18:02:08 +0300 Subject: [PATCH 14/18] Improve error handling --- protocol-derive/src/lib.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index fe04136..0fed335 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -25,7 +25,7 @@ pub fn derive_packet(input: proc_macro::TokenStream) -> TokenStream1 { #decoder }) } - _ => panic!("Packet derive are available only for structures"), + _ => panic!("Expected only structures"), } } @@ -82,27 +82,27 @@ fn parse_field_meta(meta_list: &Vec) -> PacketFieldMeta { for meta in meta_list { match meta { - NestedMeta::Meta(Meta::NameValue(m)) => match &m.path { - p if p.is_ident("with") => { - if let Lit::Str(lit_str) = &m.lit { - module = Some(lit_str.value()); - } - } - p if p.is_ident("max_length") => { - if let Lit::Int(lit_int) = &m.lit { + 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"), - ); + ) } - } - p => panic!( - "Packet derive received unrecognized attribute : \"{}\"", - p.get_ident().unwrap() + _ => panic!("\"max_length\" attribute value must be integer"), + }, + path => panic!( + "Received unrecognized attribute : \"{}\"", + path.get_ident().unwrap() ), }, - _ => panic!("Packet derive support only named meta values"), + _ => panic!("Expected only named meta values"), } } @@ -117,7 +117,7 @@ fn get_packet_field_meta(field: &Field) -> Vec { .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!("Packet derive support only list attributes"), + _ => panic!("Expected only list attributes"), }) .flatten() .collect() @@ -130,7 +130,7 @@ fn quote_field TokenStream2>(fields: &Fields, func: F) -> Token Fields::Named(named_fields) => { output.append_all(named_fields.named.iter().map(|f| func(f))) } - _ => panic!("Packet derive are available only for named fields"), + _ => panic!("Expected only for named fields"), } output From 61fff6fcf700902dfd9d35004c625f652b8be265 Mon Sep 17 00:00:00 2001 From: vagola Date: Fri, 3 Jan 2020 18:31:44 +0300 Subject: [PATCH 15/18] Add field custom encoder decoder support --- protocol-derive/src/lib.rs | 20 ++++++++-- protocol/src/game.rs | 11 +++--- protocol/src/lib.rs | 76 +++++++++++++++++++++++++++++++++++++- protocol/src/login.rs | 12 +++--- 4 files changed, 104 insertions(+), 15 deletions(-) diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index 0fed335..9697908 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -5,6 +5,7 @@ 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))] @@ -34,10 +35,21 @@ fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { let name = &field.ident; let unparsed_meta = get_packet_field_meta(field); - let parsed_meta = parse_field_meta(&unparsed_meta); + let parsed_meta = parse_packet_field_meta(&unparsed_meta); - quote! { - crate::Encoder::encode(&self.#name, writer)?; + match parsed_meta.module { + Some(module) => { + let module_ident = Ident::new(&module, Span::call_site()); + + quote! { + crate::#module_ident::encode(&self.#name, writer)?; + } + } + None => { + quote! { + crate::Encoder::encode(&self.#name, writer)?; + } + } } }); @@ -76,7 +88,7 @@ struct PacketFieldMeta { max_length: Option, } -fn parse_field_meta(meta_list: &Vec) -> PacketFieldMeta { +fn parse_packet_field_meta(meta_list: &Vec) -> PacketFieldMeta { let mut module = None; let mut max_length = None; diff --git a/protocol/src/game.rs b/protocol/src/game.rs index ac25f1a..44afedb 100644 --- a/protocol/src/game.rs +++ b/protocol/src/game.rs @@ -8,9 +8,6 @@ use minecraft_protocol_derive::Packet; use nbt::CompoundTag; use std::io::Read; -const SERVER_BOUND_CHAT_MESSAGE_MAX_LENGTH: u32 = 256; -const LEVEL_TYPE_MAX_LENGTH: u32 = 16; - pub enum GameServerBoundPacket { ServerBoundChatMessage(ServerBoundChatMessage), ServerBoundKeepAlive(ServerBoundKeepAlive), @@ -87,6 +84,7 @@ impl GameClientBoundPacket { #[derive(Packet, Debug)] pub struct ServerBoundChatMessage { + #[packet(max_length = 256)] pub message: String, } @@ -127,8 +125,10 @@ pub struct JoinGame { pub game_mode: GameMode, pub dimension: i32, pub max_players: u8, + #[packet(max_length = 16)] pub level_type: String, - pub view_distance: u8, + #[packet(with = "var_int")] + pub view_distance: i32, pub reduced_debug_info: bool, } @@ -150,7 +150,7 @@ impl JoinGame { dimension: i32, max_players: u8, level_type: String, - view_distance: u8, + view_distance: i32, reduced_debug_info: bool, ) -> GameClientBoundPacket { let join_game = JoinGame { @@ -198,6 +198,7 @@ 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, diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 9a0c35f..11abe58 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -24,8 +24,9 @@ pub mod status; /// Current supported protocol version. pub const PROTOCOL_VERSION: u32 = 498; -/// String maximum length. +/// Protocol limits maximum string length. const STRING_MAX_LENGTH: u32 = 32_768; +const HYPHENATED_UUID_LENGTH: u32 = 36; /// Possible errors while encoding packet. #[derive(Debug)] @@ -477,3 +478,76 @@ macro_rules! impl_json_encoder_decoder ( } ); ); + +mod var_int { + use crate::{DecodeError, EncodeError, Encoder}; + 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, Encoder}; + 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, Decoder, EncodeError, Encoder}; + 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, Decoder, DecoderReadExt, EncodeError, Encoder, 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 index 9482ac9..d18331e 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -6,10 +6,6 @@ use uuid::Uuid; use minecraft_protocol_derive::Packet; -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), @@ -138,8 +134,10 @@ impl EncryptionResponse { #[derive(Packet, Debug)] pub struct LoginPluginResponse { + #[packet(with = "var_int")] pub message_id: i32, pub successful: bool, + #[packet(with = "rest")] pub data: Vec, } @@ -194,7 +192,9 @@ impl EncryptionRequest { #[derive(Packet, Debug)] pub struct LoginSuccess { + #[packet(with = "uuid_hyp_str")] pub uuid: Uuid, + #[packet(max_length = 16)] pub username: String, } @@ -208,7 +208,7 @@ impl LoginSuccess { #[derive(Packet, Debug)] pub struct SetCompression { - #[packet(with = "varint")] + #[packet(with = "var_int")] pub threshold: i32, } @@ -222,8 +222,10 @@ impl SetCompression { #[derive(Packet, Debug)] pub struct LoginPluginRequest { + #[packet(with = "var_int")] pub message_id: i32, pub channel: String, + #[packet(with = "rest")] pub data: Vec, } From 73528233eb8a61a312919f57f3795b0d6d148f29 Mon Sep 17 00:00:00 2001 From: vagola Date: Sat, 4 Jan 2020 11:12:05 +0300 Subject: [PATCH 16/18] Add in proc macro decoder generation --- protocol-derive/src/lib.rs | 51 +++++++++++++++++++++++++++----------- protocol/src/lib.rs | 9 +++---- protocol/src/login.rs | 24 +++++++++++++++++- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index 9697908..ec99738 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -37,23 +37,16 @@ fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { let unparsed_meta = get_packet_field_meta(field); let parsed_meta = parse_packet_field_meta(&unparsed_meta); - match parsed_meta.module { - Some(module) => { - let module_ident = Ident::new(&module, Span::call_site()); + 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)?; - } - } - None => { - quote! { - crate::Encoder::encode(&self.#name, writer)?; - } - } + 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 @@ -65,18 +58,48 @@ fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { } fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { - let decode = quote_field(fields, |_field| { + 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); + + 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! { - todo!(); + #name, } }); quote! { + #[automatically_derived] impl crate::Decoder for #name { type Output = Self; fn decode(reader: &mut R) -> Result { #decode + + Ok(#name { + #create + }) } } } diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 11abe58..30dd730 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -480,7 +480,7 @@ macro_rules! impl_json_encoder_decoder ( ); mod var_int { - use crate::{DecodeError, EncodeError, Encoder}; + use crate::{DecodeError, EncodeError}; use mc_varint::{VarIntRead, VarIntWrite}; use std::io::{Read, Write}; @@ -496,7 +496,7 @@ mod var_int { } mod var_long { - use crate::{DecodeError, EncodeError, Encoder}; + use crate::{DecodeError, EncodeError}; use mc_varint::{VarIntRead, VarIntWrite}; use std::io::{Read, Write}; @@ -512,7 +512,7 @@ mod var_long { } mod rest { - use crate::{DecodeError, Decoder, EncodeError, Encoder}; + use crate::{DecodeError, EncodeError}; use std::io::{Read, Write}; pub fn encode(value: &[u8], writer: &mut W) -> Result<(), EncodeError> { @@ -531,8 +531,7 @@ mod rest { mod uuid_hyp_str { use crate::{ - DecodeError, Decoder, DecoderReadExt, EncodeError, Encoder, EncoderWriteExt, - HYPHENATED_UUID_LENGTH, + DecodeError, DecoderReadExt, EncodeError, EncoderWriteExt, HYPHENATED_UUID_LENGTH, }; use std::io::{Read, Write}; use uuid::Uuid; diff --git a/protocol/src/login.rs b/protocol/src/login.rs index d18331e..0d2679e 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -115,7 +115,7 @@ impl LoginStart { } } -#[derive(Packet, Debug)] +#[derive(Debug)] pub struct EncryptionResponse { pub shared_secret: Vec, pub verify_token: Vec, @@ -132,6 +132,28 @@ impl EncryptionResponse { } } +impl crate::Encoder for EncryptionResponse { + fn encode(&self, writer: &mut W) -> Result<(), crate::EncodeError> { + crate::Encoder::encode(&self.shared_secret, writer)?; + crate::Encoder::encode(&self.verify_token, writer)?; + Ok(()) + } +} + +impl crate::Decoder for EncryptionResponse { + type Output = Self; + + fn decode(reader: &mut R) -> Result { + let shared_secret = as crate::Decoder>::decode(reader)?; + let verify_token = as crate::Decoder>::decode(reader)?; + + Ok(EncryptionResponse { + shared_secret, + verify_token, + }) + } +} + #[derive(Packet, Debug)] pub struct LoginPluginResponse { #[packet(with = "var_int")] From 0287fadb1e137c3ab2eefeb35b6f1bd71b110c5e Mon Sep 17 00:00:00 2001 From: vagola Date: Sat, 4 Jan 2020 11:14:48 +0300 Subject: [PATCH 17/18] Replace with auto generation impl --- protocol/src/login.rs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/protocol/src/login.rs b/protocol/src/login.rs index 0d2679e..d18331e 100644 --- a/protocol/src/login.rs +++ b/protocol/src/login.rs @@ -115,7 +115,7 @@ impl LoginStart { } } -#[derive(Debug)] +#[derive(Packet, Debug)] pub struct EncryptionResponse { pub shared_secret: Vec, pub verify_token: Vec, @@ -132,28 +132,6 @@ impl EncryptionResponse { } } -impl crate::Encoder for EncryptionResponse { - fn encode(&self, writer: &mut W) -> Result<(), crate::EncodeError> { - crate::Encoder::encode(&self.shared_secret, writer)?; - crate::Encoder::encode(&self.verify_token, writer)?; - Ok(()) - } -} - -impl crate::Decoder for EncryptionResponse { - type Output = Self; - - fn decode(reader: &mut R) -> Result { - let shared_secret = as crate::Decoder>::decode(reader)?; - let verify_token = as crate::Decoder>::decode(reader)?; - - Ok(EncryptionResponse { - shared_secret, - verify_token, - }) - } -} - #[derive(Packet, Debug)] pub struct LoginPluginResponse { #[packet(with = "var_int")] From d7c35639c91c462a47f6502a54eada86c6345923 Mon Sep 17 00:00:00 2001 From: vagola Date: Sat, 4 Jan 2020 12:39:31 +0300 Subject: [PATCH 18/18] Validate string max length --- protocol-derive/src/lib.rs | 14 +++++++++++ protocol/src/game.rs | 48 ++++++++++++++++++++++++++++++++++++-- protocol/src/lib.rs | 22 ++++++++--------- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/protocol-derive/src/lib.rs b/protocol-derive/src/lib.rs index ec99738..2953b8f 100644 --- a/protocol-derive/src/lib.rs +++ b/protocol-derive/src/lib.rs @@ -37,6 +37,13 @@ fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { 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()); @@ -65,6 +72,13 @@ fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 { 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()); diff --git a/protocol/src/game.rs b/protocol/src/game.rs index 44afedb..3261e38 100644 --- a/protocol/src/game.rs +++ b/protocol/src/game.rs @@ -236,8 +236,8 @@ mod tests { ChunkData, ClientBoundChatMessage, ClientBoundKeepAlive, GameMode, JoinGame, MessagePosition, ServerBoundChatMessage, ServerBoundKeepAlive, }; - use crate::Decoder; - use crate::Encoder; + use crate::{DecodeError, Encoder, EncoderWriteExt, STRING_MAX_LENGTH}; + use crate::{Decoder, EncodeError}; use nbt::CompoundTag; use std::io::Cursor; @@ -266,6 +266,50 @@ mod tests { 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 { diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 30dd730..5024b31 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -25,8 +25,8 @@ pub mod status; /// Current supported protocol version. pub const PROTOCOL_VERSION: u32 = 498; /// Protocol limits maximum string length. -const STRING_MAX_LENGTH: u32 = 32_768; -const HYPHENATED_UUID_LENGTH: u32 = 36; +const STRING_MAX_LENGTH: u16 = 32_768; +const HYPHENATED_UUID_LENGTH: u16 = 36; /// Possible errors while encoding packet. #[derive(Debug)] @@ -36,7 +36,7 @@ pub enum EncodeError { /// String length. length: usize, /// Max string length. - max_length: u32, + max_length: u16, }, IOError { io_error: IoError, @@ -68,9 +68,9 @@ pub enum DecodeError { /// String length can't be more than provided value. StringTooLong { /// String length. - length: u32, + length: usize, /// Max string length. - max_length: u32, + max_length: u16, }, IOError { io_error: IoError, @@ -140,7 +140,7 @@ trait Decoder { trait EncoderWriteExt { fn write_bool(&mut self, value: bool) -> Result<(), EncodeError>; - fn write_string(&mut self, value: &str, max_length: u32) -> Result<(), EncodeError>; + fn write_string(&mut self, value: &str, max_length: u16) -> Result<(), EncodeError>; fn write_byte_array(&mut self, value: &[u8]) -> Result<(), EncodeError>; @@ -155,7 +155,7 @@ trait EncoderWriteExt { trait DecoderReadExt { fn read_bool(&mut self) -> Result; - fn read_string(&mut self, max_length: u32) -> Result; + fn read_string(&mut self, max_length: u16) -> Result; fn read_byte_array(&mut self) -> Result, DecodeError>; @@ -177,7 +177,7 @@ impl EncoderWriteExt for W { Ok(()) } - fn write_string(&mut self, value: &str, max_length: u32) -> Result<(), EncodeError> { + fn write_string(&mut self, value: &str, max_length: u16) -> Result<(), EncodeError> { let length = value.len(); if length > max_length as usize { @@ -224,10 +224,10 @@ impl DecoderReadExt for R { } } - fn read_string(&mut self, max_length: u32) -> Result { - let length = self.read_var_i32()? as u32; + fn read_string(&mut self, max_length: u16) -> Result { + let length = self.read_var_i32()? as usize; - if length > max_length as u32 { + if length as u16 > max_length { return Err(DecodeError::StringTooLong { length, max_length }); }