From c0f077a10e124e5d63e8680a311e26fc8105778b Mon Sep 17 00:00:00 2001 From: MeexReay Date: Mon, 28 Apr 2025 14:07:46 +0300 Subject: [PATCH] init commit --- .gitignore | 1 + Cargo.lock | 289 +++++++++++++++++++++++++++++++++++ Cargo.toml | 16 ++ LICENSE | 14 +- README.md | 48 +++++- examples/recv_motd.rs | 43 ++++++ examples/recv_motd_dirty.rs | 17 +++ examples/status_server.rs | 122 +++++++++++++++ src/data/mod.rs | 8 + src/data/reader.rs | 222 +++++++++++++++++++++++++++ src/data/varint.rs | 70 +++++++++ src/data/writer.rs | 110 ++++++++++++++ src/lib.rs | 292 ++++++++++++++++++++++++++++++++++++ src/packet.rs | 123 +++++++++++++++ src/tests.rs | 162 ++++++++++++++++++++ src/zigzag.rs | 65 ++++++++ 16 files changed, 1595 insertions(+), 7 deletions(-) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 examples/recv_motd.rs create mode 100644 examples/recv_motd_dirty.rs create mode 100644 examples/status_server.rs create mode 100644 src/data/mod.rs create mode 100644 src/data/reader.rs create mode 100644 src/data/varint.rs create mode 100644 src/data/writer.rs create mode 100644 src/lib.rs create mode 100644 src/packet.rs create mode 100644 src/tests.rs create mode 100644 src/zigzag.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3b0e4bf --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,289 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rust_mc_proto_tokio" +version = "0.1.18" +dependencies = [ + "flate2", + "tokio", + "uuid", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..decf3ed --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rust_mc_proto_tokio" +description = "lightweight minecraft protocol support in pure rust" + +repository = "https://git.meex.lol/MeexReay/rust_mc_proto" +license-file = "LICENSE" +readme = "README.md" +keywords = ["minecraft", "protocol", "packets", "lightweight", "tokio"] + +version = "0.1.18" +edition = "2024" + +[dependencies] +flate2 = "1.1.1" +uuid = "1.16.0" +tokio = { version = "1", features = ["io-util", "rt", "macros", "net", "rt-multi-thread"] } \ No newline at end of file diff --git a/LICENSE b/LICENSE index 7a3094a..07b7a81 100644 --- a/LICENSE +++ b/LICENSE @@ -1,11 +1,13 @@ -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -Version 2, December 2004 + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 Copyright (C) 2004 Sam Hocevar -Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - 0. You just DO WHAT THE FUCK YOU WANT TO. + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/README.md b/README.md index c242970..98a9571 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,49 @@ # rust_mc_proto_tokio -Async minecraft packets protocol in pure rust \ No newline at end of file +Lightweight minecraft packets protocol support in pure rust \ +Has compression (`MinecraftConnection::set_compression`) \ +This crate can be used for a server on rust idk -_- + +## Setup + +```toml +rust_mc_proto_tokio = "0.1.18" # stable version +rust_mc_proto_tokio = { git = "https://git.meex.lol/MeexReay/rust_mc_proto_tokio" } # unstable version +``` + +Features: +- `atomic_clone` - Atomic clone of MinecraftConnection + +## How to use + +Example of receiving motd: + +```rust +use rust_mc_proto_tokio::{prelude::*, MCConnTcp, Packet, ProtocolError}; + +#[tokio::main] +async fn main() -> Result<(), ProtocolError> { + let mut conn = MCConnTcp::connect("localhost:25565").await?; // connecting + + let mut packet = Packet::empty(0x00); + packet.write_u16_varint(765).await?; // protocol_version + packet.write_string("localhost").await?; // server_address + packet.write_unsigned_short(25565).await?; // server_port + packet.write_u8_varint(1).await?; // next_state + conn.write_packet(&packet).await?; + + conn.write_packet(&Packet::empty(0x00)).await?; // status request packet + + Ok(println!("motd: {}", conn.read_packet().await?.read_string().await?)) // status response packet +} +``` + +[More examples](https://git.meex.lol/MeexReay/rust_mc_proto/src/branch/main/examples) \ +[Documentation](https://docs.rs/rust_mc_proto/) + +### Contributing + +If you would like to contribute to the project, feel free to fork the repository and submit a pull request. + +### License +This project is licensed under the WTFPL License \ No newline at end of file diff --git a/examples/recv_motd.rs b/examples/recv_motd.rs new file mode 100644 index 0000000..424ff4f --- /dev/null +++ b/examples/recv_motd.rs @@ -0,0 +1,43 @@ +use rust_mc_proto_tokio::{prelude::*, MCConnTcp, Packet, ProtocolError}; + +/* + + Example of receiving motd from the server + Sends handshake, status request and receiving one + +*/ + +async fn send_handshake( + conn: &mut MCConnTcp, + protocol_version: u16, + server_address: &str, + server_port: u16, + next_state: u8 +) -> Result<(), ProtocolError> { + let mut packet = Packet::empty(0x00); + packet.write_u16_varint(protocol_version).await?; + packet.write_string(server_address).await?; + packet.write_unsigned_short(server_port).await?; + packet.write_u8_varint(next_state).await?; + conn.write_packet(&packet).await +} + +async fn send_status_request(conn: &mut MCConnTcp) -> Result<(), ProtocolError> { + conn.write_packet(&Packet::empty(0x00)).await +} + +async fn read_status_response(conn: &mut MCConnTcp) -> Result { + conn.read_packet().await?.read_string().await +} + +#[tokio::main] +async fn main() { + let mut conn = MCConnTcp::connect("mc.hypixel.net:25565").await.unwrap(); + + send_handshake(&mut conn, 765, "mc.hypixel.net", 25565, 1).await.unwrap(); + send_status_request(&mut conn).await.unwrap(); + + let motd = read_status_response(&mut conn).await.unwrap(); + + dbg!(motd); +} diff --git a/examples/recv_motd_dirty.rs b/examples/recv_motd_dirty.rs new file mode 100644 index 0000000..91a84f6 --- /dev/null +++ b/examples/recv_motd_dirty.rs @@ -0,0 +1,17 @@ +use rust_mc_proto_tokio::{DataReader, DataWriter, MCConnTcp, Packet, ProtocolError}; + +#[tokio::main] +async fn main() -> Result<(), ProtocolError> { + let mut conn = MCConnTcp::connect("localhost:25565").await?; // connecting + + let mut packet = Packet::empty(0x00); + packet.write_u16_varint(765).await?; // protocol_version + packet.write_string("localhost").await?; // server_address + packet.write_unsigned_short(25565).await?; // server_port + packet.write_u8_varint(1).await?; // next_state + conn.write_packet(&packet).await?; + + conn.write_packet(&Packet::empty(0x00)).await?; // status request packet + + Ok(println!("motd: {}", conn.read_packet().await?.read_string().await?)) // status response packet +} diff --git a/examples/status_server.rs b/examples/status_server.rs new file mode 100644 index 0000000..6a14234 --- /dev/null +++ b/examples/status_server.rs @@ -0,0 +1,122 @@ +use tokio::net::TcpListener; +use std::sync::Arc; +use rust_mc_proto_tokio::{prelude::*, MCConnTcp, MinecraftConnection, Packet, ProtocolError}; + +/* + + Example of simple server that sends motd + to client like a vanilla minecraft server + +*/ + +struct MinecraftServer { + server_ip: String, + server_port: u16, + motd: String +} + +impl MinecraftServer { + fn new(server_ip: &str, + server_port: u16, + motd: &str) -> Self { + MinecraftServer { + server_ip: server_ip.to_string(), + server_port, + motd: motd.to_string() + } + } + + async fn start(self) { + let addr = self.server_ip.clone() + ":" + &self.server_port.to_string(); + let listener = TcpListener::bind(addr).await.unwrap(); + + let this = Arc::new(self); + + while let Ok((stream, _)) = listener.accept().await { + tokio::spawn({ + let this = this.clone(); + + async move { + Self::accept_client(this, MinecraftConnection::new(stream)).await.unwrap(); + } + }); + } + } + + async fn accept_client(self: Arc, mut conn: MCConnTcp) -> Result<(), ProtocolError> { + let mut handshake = false; + + loop { + let Ok(mut packet) = conn.read_packet().await else { break; }; + + if handshake { + if packet.id() == 0x00 { + let motd = self.motd.clone(); + + let mut status = Packet::empty(0x00); + status.write_string(&motd).await?; + conn.write_packet(&status).await?; + } else if packet.id() == 0x01 { + let mut status = Packet::empty(0x01); + status.write_long(packet.read_long().await?).await?; + conn.write_packet(&status).await?; + } + } else if packet.id() == 0x00 { + let protocol_version = packet.read_i32_varint().await?; + let server_address = packet.read_string().await?; + let server_port = packet.read_unsigned_short().await?; + let next_state = packet.read_u8_varint().await?; + + if next_state != 1 { break; } + + println!( + "{} > protocol: {} server: {}:{}", + conn.get_ref().peer_addr().unwrap(), + protocol_version, + server_address, + server_port + ); + + handshake = true; + } else { + break; + } + } + + conn.close().await; + + Ok(()) + } +} + +#[tokio::main] +async fn main() { + let server = MinecraftServer::new( + "127.0.0.1", + 25565, + "{ + \"version\":{ + \"protocol\":765, + \"name\":\"Version name\" + }, + \"players\":{ + \"online\":0, + \"max\":1, + \"sample\":[ + { + \"uuid\": \"\", + \"name\": \"Notch\" + } + ] + }, + \"description\": { + \"text\": \"Hello World!\", + \"color\": \"red\", + \"bold\": true + }, + \"favicon\": \"data:image/png;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=\" + }" + ); + + server.start().await; +} diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..fada9ef --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,8 @@ +//! `DataReader` and `DataWriter` traits for reading and writing primitive types in the Minecraft protocol + +pub mod reader; +pub mod varint; +pub mod writer; + +pub use reader::*; +pub use writer::*; diff --git a/src/data/reader.rs b/src/data/reader.rs new file mode 100644 index 0000000..a4aea96 --- /dev/null +++ b/src/data/reader.rs @@ -0,0 +1,222 @@ +use crate::{ + data::varint::{read_varint, size_varint}, + zigzag::Zigzag, + ProtocolError, +}; +use tokio::io::AsyncReadExt; +use uuid::Uuid; + +/// Packet data reader trait +pub trait DataReader { + /// Read bytes + fn read_bytes(&mut self, size: usize) -> impl Future, ProtocolError>>; + + /// Read byte + fn read_byte(&mut self) -> impl Future> { + async {Ok(self.read_bytes(1).await?[0])} + } + /// Read String + fn read_string(&mut self) -> impl Future> { + async { + let size = self.read_usize_varint().await?; + match String::from_utf8(self.read_bytes(size).await?) { + Ok(i) => Ok(i), + Err(_) => Err(ProtocolError::StringParseError), + } + } + } + /// Read Unsigned Short as u16 + fn read_unsigned_short(&mut self) -> impl Future> { + async {match self.read_bytes(2).await?.try_into() { + Ok(i) => Ok(u16::from_be_bytes(i)), + Err(_) => Err(ProtocolError::ReadError), + }} + } + /// Read Boolean + fn read_boolean(&mut self) -> impl Future> { + async {Ok(self.read_byte().await? == 0x01)} + } + /// Read Short as i16 + fn read_short(&mut self) -> impl Future> { + async {match self.read_bytes(2).await?.try_into() { + Ok(i) => Ok(i16::from_be_bytes(i)), + Err(_) => Err(ProtocolError::ReadError), + }} + } + /// Read Long as i64 + fn read_long(&mut self) -> impl Future> { + async {match self.read_bytes(8).await?.try_into() { + Ok(i) => Ok(i64::from_be_bytes(i)), + Err(_) => Err(ProtocolError::ReadError), + }} + } + /// Read Float as f32 + fn read_float(&mut self) -> impl Future> { + async {match self.read_bytes(4).await?.try_into() { + Ok(i) => Ok(f32::from_be_bytes(i)), + Err(_) => Err(ProtocolError::ReadError), + }} + } + /// Read Double as f64 + fn read_double(&mut self) -> impl Future> { + async {match self.read_bytes(8).await?.try_into() { + Ok(i) => Ok(f64::from_be_bytes(i)), + Err(_) => Err(ProtocolError::ReadError), + }} + } + /// Read Int as i32 + fn read_int(&mut self) -> impl Future> { + async {match self.read_bytes(4).await?.try_into() { + Ok(i) => Ok(i32::from_be_bytes(i)), + Err(_) => Err(ProtocolError::ReadError), + }} + } + /// Read UUID + fn read_uuid(&mut self) -> impl Future> { + async {match self.read_bytes(16).await?.try_into() { + Ok(i) => Ok(Uuid::from_bytes(i)), + Err(_) => Err(ProtocolError::ReadError), + }} + } + + /// Read VarInt as usize with size in bytes (varint, size) + fn read_usize_varint_size(&mut self) -> impl Future> { + async {size_varint!(usize, self)} + } + /// Read VarInt as u8 with size in bytes (varint, size) + fn read_u8_varint_size(&mut self) -> impl Future> { + async {size_varint!(u8, self)} + } + /// Read VarInt as u16 with size in bytes (varint, size) + fn read_u16_varint_size(&mut self) -> impl Future> { + async {size_varint!(u16, self)} + } + /// Read VarInt as u32 with size in bytes (varint, size) + fn read_u32_varint_size(&mut self) -> impl Future> { + async {size_varint!(u32, self)} + } + /// Read VarInt as u64 with size in bytes (varint, size) + fn read_u64_varint_size(&mut self) -> impl Future> { + async {size_varint!(u64, self)} + } + /// Read VarInt as u128 with size in bytes (varint, size) + fn read_u128_varint_size(&mut self) -> impl Future> { + async {size_varint!(u128, self)} + } + + /// Read VarInt as isize with size in bytes (varint, size) + fn read_isize_varint_size(&mut self) -> impl Future> { + async {Ok({ + let i = self.read_usize_varint_size().await?; + (i.0.zigzag(), i.1) + })} + } + /// Read VarInt as i8 with size in bytes (varint, size) + fn read_i8_varint_size(&mut self) -> impl Future> { + async {Ok({ + let i = self.read_u8_varint_size().await?; + (i.0.zigzag(), i.1) + })} + } + /// Read VarInt as i16 with size in bytes (varint, size) + fn read_i16_varint_size(&mut self) -> impl Future> { + async {Ok({ + let i = self.read_u16_varint_size().await?; + (i.0.zigzag(), i.1) + })} + } + /// Read VarInt as i32 with size in bytes (varint, size) + fn read_i32_varint_size(&mut self) -> impl Future> { + async {Ok({ + let i = self.read_u32_varint_size().await?; + (i.0.zigzag(), i.1) + })} + } + /// Read VarInt as i64 with size in bytes (varint, size) + fn read_i64_varint_size(&mut self) -> impl Future> { + async {Ok({ + let i = self.read_u64_varint_size().await?; + (i.0.zigzag(), i.1) + })} + } + /// Read VarInt as i128 with size in bytes (varint, size) + fn read_i128_varint_size(&mut self) -> impl Future> { + async {Ok({ + let i = self.read_u128_varint_size().await?; + (i.0.zigzag(), i.1) + })} + } + + /// Read VarInt as usize + fn read_usize_varint(&mut self) -> impl Future> { + async {read_varint!(usize, self)} + } + /// Read VarInt as u8 + fn read_u8_varint(&mut self) -> impl Future> { + async {read_varint!(u8, self)} + } + /// Read VarInt as u16 + fn read_u16_varint(&mut self) -> impl Future> { + async {read_varint!(u16, self)} + } + /// Read VarInt as u32 + fn read_u32_varint(&mut self) -> impl Future> { + async {read_varint!(u32, self)} + } + /// Read VarInt as u64 + fn read_u64_varint(&mut self) -> impl Future> { + async {read_varint!(u64, self)} + } + /// Read VarInt as u128 + fn read_u128_varint(&mut self) -> impl Future> { + async {read_varint!(u128, self)} + } + + /// Read VarInt as isize + fn read_isize_varint(&mut self) -> impl Future> { + async {Ok(self.read_usize_varint().await?.zigzag())} + } + /// Read VarInt as i8 + fn read_i8_varint(&mut self) -> impl Future> { + async {Ok(self.read_u8_varint().await?.zigzag())} + } + /// Read VarInt as i16 + fn read_i16_varint(&mut self) -> impl Future> { + async {Ok(self.read_u16_varint().await?.zigzag())} + } + /// Read VarInt as i32 + fn read_i32_varint(&mut self) -> impl Future> { + async {Ok(self.read_u32_varint().await?.zigzag())} + } + /// Read VarInt as i64 + fn read_i64_varint(&mut self) -> impl Future> { + async {Ok(self.read_u64_varint().await?.zigzag())} + } + /// Read VarInt as i128 + fn read_i128_varint(&mut self) -> impl Future> { + async {Ok(self.read_u128_varint().await?.zigzag())} + } +} + +impl DataReader for R { + async fn read_bytes(&mut self, size: usize) -> Result, ProtocolError> { + let mut data = Vec::new(); + + while data.len() < size { + let mut buf = vec![0; size - data.len()]; + + let n = self.read(&mut buf).await + .map_err(|_| ProtocolError::ReadError)?; + + if n == 0 { + return Err(ProtocolError::ConnectionClosedError); + } + + buf.truncate(n); + + data.append(&mut buf); + } + + Ok(data) + } +} diff --git a/src/data/varint.rs b/src/data/varint.rs new file mode 100644 index 0000000..7a91e5e --- /dev/null +++ b/src/data/varint.rs @@ -0,0 +1,70 @@ +macro_rules! size_varint { + ($type:ty, $self:expr) => {{ + let mut shift: $type = 0; + let mut decoded: $type = 0; + let mut size: usize = 0; + + loop { + let next = DataReader::read_byte($self).await?; + size += 1; + + if shift >= (std::mem::size_of::<$type>() * 8) as $type { + return Err(ProtocolError::VarIntError); + } + + decoded |= ((next & 0b01111111) as $type) << shift; + + if next & 0b10000000 == 0b10000000 { + shift += 7; + } else { + return Ok((decoded, size)); + } + } + }}; +} + +macro_rules! read_varint { + ($type:ty, $self:expr) => {{ + let mut shift: $type = 0; + let mut decoded: $type = 0; + + loop { + let next = DataReader::read_byte($self).await?; + + if shift >= (std::mem::size_of::<$type>() * 8) as $type { + return Err(ProtocolError::VarIntError); + } + + decoded |= ((next & 0b01111111) as $type) << shift; + + if next & 0b10000000 == 0b10000000 { + shift += 7; + } else { + return Ok(decoded); + } + } + }}; +} + +macro_rules! write_varint { + ($type:ty, $self:expr, $value:expr) => {{ + let mut value: $type = $value; + + if value == 0 { + DataWriter::write_byte($self, 0).await + } else { + while value >= 0b10000000 { + let next: u8 = ((value & 0b01111111) as u8) | 0b10000000; + value >>= 7; + + DataWriter::write_byte($self, next).await?; + } + + DataWriter::write_byte($self, (value & 0b01111111) as u8).await + } + }}; +} + +pub(crate) use read_varint; +pub(crate) use size_varint; +pub(crate) use write_varint; diff --git a/src/data/writer.rs b/src/data/writer.rs new file mode 100644 index 0000000..60d15f7 --- /dev/null +++ b/src/data/writer.rs @@ -0,0 +1,110 @@ +use crate::{data::varint::write_varint, zigzag::Zigzag, ProtocolError}; +use tokio::io::AsyncWriteExt; +use uuid::Uuid; + +/// Packet data writer trait +pub trait DataWriter { + /// Write bytes + fn write_bytes(&mut self, bytes: &[u8]) -> impl Future>; + + /// Write byte + fn write_byte(&mut self, byte: u8) -> impl Future> { + async move { self.write_bytes(&[byte]).await } + } + /// Write String + fn write_string(&mut self, val: &str) -> impl Future> { + async move { + let bytes = val.as_bytes(); + self.write_usize_varint(bytes.len()).await?; + self.write_bytes(bytes).await + } + } + /// Write UUID + fn write_uuid(&mut self, val: &Uuid) -> impl Future> { + async move { self.write_bytes(val.as_bytes()).await } + } + /// Write Unsigned Short as u16 + fn write_unsigned_short(&mut self, val: u16) -> impl Future> { + async move { self.write_bytes(&val.to_be_bytes()).await.map_err(|_| ProtocolError::UnsignedShortError) } + } + /// Write Boolean + fn write_boolean(&mut self, val: bool) -> impl Future> { + async move { self.write_byte(if val { 0x01 } else { 0x00 }).await.map_err(|_| ProtocolError::UnsignedShortError) } + } + /// Write Short as i16 + fn write_short(&mut self, val: i16) -> impl Future> { + async move { self.write_bytes(&val.to_be_bytes()).await.map_err(|_| ProtocolError::UnsignedShortError) } + } + /// Write Long as i64 + fn write_long(&mut self, val: i64) -> impl Future> { + async move { self.write_bytes(&val.to_be_bytes()).await.map_err(|_| ProtocolError::UnsignedShortError) } + } + /// Write Float as f32 + fn write_float(&mut self, val: f32) -> impl Future> { + async move { self.write_bytes(&val.to_be_bytes()).await.map_err(|_| ProtocolError::UnsignedShortError) } + } + /// Write Double as f64 + fn write_double(&mut self, val: f64) -> impl Future> { + async move { self.write_bytes(&val.to_be_bytes()).await.map_err(|_| ProtocolError::UnsignedShortError) } + } + /// Write Int as i32 + fn write_int(&mut self, val: i32) -> impl Future> { + async move { self.write_bytes(&val.to_be_bytes()).await.map_err(|_| ProtocolError::UnsignedShortError) } + } + + /// Write VarInt as usize + fn write_usize_varint(&mut self, val: usize) -> impl Future> { + async move { write_varint!(usize, self, val) } + } + /// Write VarInt as u8 + fn write_u8_varint(&mut self, val: u8) -> impl Future> { + async move { write_varint!(u8, self, val) } + } + /// Write VarInt as u16 + fn write_u16_varint(&mut self, val: u16) -> impl Future> { + async move { write_varint!(u16, self, val) } + } + /// Write VarInt as u32 + fn write_u32_varint(&mut self, val: u32) -> impl Future> { + async move { write_varint!(u32, self, val) } + } + /// Write VarInt as u64 + fn write_u64_varint(&mut self, val: u64) -> impl Future> { + async move { write_varint!(u64, self, val) } + } + /// Write VarInt as u128 + fn write_u128_varint(&mut self, val: u128) -> impl Future> { + async move { write_varint!(u128, self, val) } + } + + /// Write VarInt as isize + fn write_isize_varint(&mut self, val: isize) -> impl Future> { + async move { self.write_usize_varint(val.zigzag()).await } + } + /// Write VarInt as i8 + fn write_i8_varint(&mut self, val: i8) -> impl Future> { + async move { self.write_u8_varint(val.zigzag()).await } + } + /// Write VarInt as i16 + fn write_i16_varint(&mut self, val: i16) -> impl Future> { + async move { self.write_u16_varint(val.zigzag()).await } + } + /// Write VarInt as i32 + fn write_i32_varint(&mut self, val: i32) -> impl Future> { + async move { self.write_u32_varint(val.zigzag()).await } + } + /// Write VarInt as i64 + fn write_i64_varint(&mut self, val: i64) -> impl Future> { + async move { self.write_u64_varint(val.zigzag()).await } + } + /// Write VarInt as i128 + fn write_i128_varint(&mut self, val: i128) -> impl Future> { + async move { self.write_u128_varint(val.zigzag()).await } + } +} + +impl DataWriter for W { + async fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), ProtocolError> { + self.write_all(bytes).await.map_err(|_| ProtocolError::WriteError) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bf13c9d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,292 @@ +#[cfg(test)] +mod tests; + +pub mod data; +pub mod packet; +pub mod zigzag; + +pub mod prelude { + pub use crate::{DataReader, DataWriter}; +} + +pub use crate::{ + data::{DataReader, DataWriter}, + packet::Packet, +}; + +use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression}; +use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream}; +use std::{ + error::Error, fmt, io::{Read, Write}, net::ToSocketAddrs, usize +}; + +/// Minecraft protocol error +#[derive(Debug)] +pub enum ProtocolError { + AddressParseError, + DataRanOutError, + StringParseError, + StreamConnectError, + VarIntError, + ReadError, + WriteError, + ZlibError, + UnsignedShortError, + CloneError, + ConnectionClosedError +} + +impl fmt::Display for ProtocolError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "An protocol error occured") + } +} + +impl Error for ProtocolError {} + +/// Minecraft connection, wrapper for stream with compression +pub struct MinecraftConnection { + stream: T, + compression: Option, + compression_type: u32, + is_alive: bool, +} + +impl MinecraftConnection { + /// Connect to Minecraft Server with TcpStream + pub async fn connect(addr: &str) -> Result, ProtocolError> { + let addr = match addr.to_socket_addrs() { + Ok(mut i) => match i.next() { + Some(i) => i, + None => return Err(ProtocolError::AddressParseError), + }, + Err(_) => return Err(ProtocolError::AddressParseError), + }; + + let stream = TcpStream::connect(&addr).await + .map_err(|_| ProtocolError::StreamConnectError)?; + + Ok(MinecraftConnection { + stream, + compression: None, + is_alive: true, + compression_type: 1, + }) + } + + /// Close TcpStream + pub async fn close(&mut self) { + let _ = self.stream.shutdown().await; + self.is_alive = false; + } +} + +impl DataReader for MinecraftConnection { + async fn read_bytes(&mut self, size: usize) -> Result, ProtocolError> { + let mut buf = vec![0; size]; + match self.stream.read_exact(&mut buf).await { + Ok(_) => Ok(buf), + Err(_) => Err(ProtocolError::ReadError), + } + } +} + +impl DataWriter for MinecraftConnection { + async fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), ProtocolError> { + self.stream.write_all(bytes).await.map_err(|_| ProtocolError::WriteError) + } +} + +impl MinecraftConnection { + /// Create new MinecraftConnection from stream + pub fn new(stream: T) -> MinecraftConnection { + MinecraftConnection { + stream, + compression: None, + is_alive: true, + compression_type: 1, + } + } + + /// Set alive state + fn set_alive(&mut self, state: bool) { + self.is_alive = state; + } + + /// Is connection alive + pub fn is_alive(&self) -> bool { + self.is_alive + } + + /// Set compression threshold + pub fn set_compression(&mut self, threshold: Option) { + self.compression = threshold; + } + + /// Get compression threshold + pub fn compression(&self) -> Option { + self.compression + } + + /// Set compression type + /// + /// `compression_type` is integer from 0 (none) to 9 (longest) + /// 1 is fast compression + /// 6 is normal compression + pub fn set_compression_type(&mut self, compression_type: u32) { + self.compression_type = compression_type; + } + + /// Get compression type + /// + /// `compression_type` is integer from 0 (none) to 9 (longest) + /// 1 is fast compression + /// 6 is normal compression + pub fn compression_type(&self) -> u32 { + self.compression_type + } + + /// Get mutable reference of stream + pub fn get_mut(&mut self) -> &mut T { + &mut self.stream + } + + /// Get immutable reference of stream + pub fn get_ref(&self) -> &T { + &self.stream + } + + /// Read [`Packet`](Packet) from connection + pub async fn read_packet(&mut self) -> Result { + if !self.is_alive() { + return Err(ProtocolError::ConnectionClosedError); + } + + match read_packet(&mut self.stream, self.compression).await { + Err(ProtocolError::ConnectionClosedError) => { + self.set_alive(false); + Err(ProtocolError::ConnectionClosedError) + }, + i => i + } + } + + /// Write [`Packet`](Packet) to connection + pub async fn write_packet(&mut self, packet: &Packet) -> Result<(), ProtocolError> { + if !self.is_alive() { + return Err(ProtocolError::ConnectionClosedError); + } + + write_packet(&mut self.stream, self.compression, self.compression_type, packet).await + } +} + +impl MinecraftConnection { + /// Clone MinecraftConnection with compression and stream + pub fn clone(&mut self) -> MinecraftConnection { + MinecraftConnection { + stream: self.stream.clone(), + compression: self.compression.clone(), + is_alive: self.is_alive.clone(), + compression_type: self.compression_type, + } + } +} + +fn compress_zlib(bytes: &[u8], compression: u32) -> Result, ProtocolError> { + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::new(compression)); + encoder.write_all(bytes).or(Err(ProtocolError::ZlibError))?; + encoder.finish().or(Err(ProtocolError::ZlibError)) +} + +fn decompress_zlib(bytes: &[u8]) -> Result, ProtocolError> { + let mut decoder = ZlibDecoder::new(bytes); + let mut output = Vec::new(); + decoder + .read_to_end(&mut output) + .or(Err(ProtocolError::ZlibError))?; + Ok(output) +} + +/// MinecraftConnection shorter alias +pub type MCConn = MinecraftConnection; + +/// MinecraftConnection\ shorter alias +pub type MCConnTcp = MinecraftConnection; + + +/// Read [`Packet`](Packet) from stream +/// +/// `compression` here is atomic usize +/// usize::MAX means that compression is disabled +/// +/// `ordering` is order how to load atomic +pub async fn read_packet( + stream: &mut T, + compression: Option +) -> Result { + let mut data: Vec; + + let packet_length = stream.read_usize_varint_size().await?; + + if compression.is_some() { + let data_length = stream.read_usize_varint_size().await?; + + data = stream.read_bytes(packet_length.0 - data_length.1).await?; + + if data_length.0 != 0 { + data = decompress_zlib(&data)?; + } + } else { + data = stream.read_bytes(packet_length.0).await?; + } + + Ok(Packet::from_data(&data).await?) +} + +/// Write [`Packet`](Packet) to stream +/// +/// `compression` here is usize +/// usize::MAX means that compression is disabled +/// +/// `ordering` is order how to load atomic +/// +/// `compression_type` is integer from 0 (none) to 9 (longest) +/// 1 is fast compression +/// 6 is normal compression +pub async fn write_packet( + stream: &mut T, + compression: Option, + compression_type: u32, + packet: &Packet, +) -> Result<(), ProtocolError> { + let mut buf = Vec::new(); + + let mut data_buf = Vec::new(); + data_buf.write_u8_varint(packet.id()).await?; + data_buf.write_bytes(packet.get_bytes()).await?; + + if let Some(compression) = compression { + let mut packet_buf = Vec::new(); + + if data_buf.len() >= compression { + let compressed_data = compress_zlib(&data_buf, compression_type)?; + packet_buf.write_usize_varint(data_buf.len()).await?; + Write::write_all(&mut packet_buf, &compressed_data) + .or(Err(ProtocolError::WriteError))?; + } else { + packet_buf.write_usize_varint(0).await?; + packet_buf.write_bytes(&data_buf).await?; + } + + buf.write_usize_varint(packet_buf.len()).await?; + buf.write_bytes(&packet_buf).await?; + } else { + buf.write_usize_varint(data_buf.len()).await?; + buf.write_bytes(&data_buf).await?; + } + + stream.write_bytes(&buf).await?; + + Ok(()) +} \ No newline at end of file diff --git a/src/packet.rs b/src/packet.rs new file mode 100644 index 0000000..511c66b --- /dev/null +++ b/src/packet.rs @@ -0,0 +1,123 @@ +//! Minecraft packet struct + +use crate::data::{DataReader, DataWriter}; +use crate::ProtocolError; +use std::io::Cursor; + +/// Minecraft packet +#[derive(Debug, Clone)] +pub struct Packet { + id: u8, + cursor: Cursor>, +} + +impl Packet { + /// Create new packet from id and buffer + pub fn new(id: u8, cursor: Cursor>) -> Packet { + Packet { id, cursor } + } + + /// Create new packet from raw packet (id + data) + pub async fn from_data(data: &[u8]) -> Result { + let mut cursor = Cursor::new(data); + + let (packet_id, packet_id_size) = cursor.read_u8_varint_size().await?; + let packet_data = + DataReader::read_bytes(&mut cursor, data.len() - packet_id_size as usize).await?; + + Ok(Packet { + id: packet_id, + cursor: Cursor::new(packet_data), + }) + } + + /// Create new packet from id and bytes in buffer + pub fn from_bytes(id: u8, data: &[u8]) -> Packet { + Packet { + id, + cursor: Cursor::new(data.to_vec()), + } + } + + /// Create new packet with id and empty buffer + pub fn empty(id: u8) -> Packet { + Packet { + id, + cursor: Cursor::new(Vec::new()), + } + } + + /// Build packet with lambda + pub async fn build(id: u8, builder: F) -> Result + where + F: FnOnce(&mut Packet) -> Fut, + Fut: Future> + Send + { + let mut packet = Self::empty(id); + builder(&mut packet).await?; + Ok(packet) + } + + /// Get packet id + pub fn id(&self) -> u8 { + self.id + } + + /// Set packet id + pub fn set_id(&mut self, id: u8) { + self.id = id; + } + + /// Set packet cursor + pub fn set_cursor(&mut self, cursor: Cursor>) { + self.cursor = cursor; + } + + /// Get cursor length + pub fn len(&self) -> usize { + self.cursor.get_ref().len() - self.cursor.position() as usize + } + + /// Is cursor empty + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get cursor remaining bytes + pub fn get_bytes(&self) -> &[u8] { + &self.cursor.get_ref() + } + + /// Get mutable reference to cursor + pub fn get_mut(&mut self) -> &mut Cursor> { + &mut self.cursor + } + + /// Get immutable reference to cursor + pub fn get_ref(&self) -> &Cursor> { + &self.cursor + } + + /// Get inner cursor + pub fn into_inner(self) -> Cursor> { + self.cursor + } +} + +impl Into>> for Packet { + fn into(self) -> Cursor> { + self.cursor + } +} + +impl DataReader for Packet { + async fn read_bytes(&mut self, size: usize) -> Result, ProtocolError> { + self.cursor.read_bytes(size).await + } +} + +impl DataWriter for Packet { + async fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), ProtocolError> { + self.cursor.write_bytes(bytes).await + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..52e33f2 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,162 @@ +use uuid::Uuid; +use tokio::net::TcpListener; +use std::io::Cursor; + +use crate::*; + +#[tokio::test] +async fn test_data_transfer() -> Result<(), ProtocolError> { + + async fn server_thread() -> Result<(), ProtocolError> { + let listener = + TcpListener::bind("localhost:44447").await.or(Err(ProtocolError::StreamConnectError))?; + + while let Ok((stream, _)) = listener.accept().await { + let mut stream = MCConnTcp::new(stream); + + stream.set_compression(Some(5)); + + let mut packet = stream.read_packet().await?; + + let mut pack = Packet::empty(packet.id()); + + pack.write_boolean(packet.read_boolean().await?).await?; + pack.write_byte(packet.read_byte().await?).await?; + pack.write_bytes(&packet.read_bytes(10).await?.clone()).await?; + pack.write_double(packet.read_double().await?).await?; + pack.write_float(packet.read_float().await?).await?; + pack.write_i128_varint(packet.read_i128_varint().await?).await?; + pack.write_u128_varint(packet.read_u128_varint().await?).await?; + pack.write_int(packet.read_int().await?).await?; + pack.write_long(packet.read_long().await?).await?; + pack.write_short(packet.read_short().await?).await?; + pack.write_uuid(&packet.read_uuid().await?).await?; + pack.write_string(&packet.read_string().await?.clone()).await?; + + stream.write_packet(&pack).await?; + + stream.set_compression(None); + + let mut packet = stream.read_packet().await?; + + let mut pack = Packet::empty(packet.id()); + + pack.write_boolean(packet.read_boolean().await?).await?; + pack.write_byte(packet.read_byte().await?).await?; + pack.write_bytes(&packet.read_bytes(10).await?.clone()).await?; + pack.write_double(packet.read_double().await?).await?; + pack.write_float(packet.read_float().await?).await?; + pack.write_i128_varint(packet.read_i128_varint().await?).await?; + pack.write_u128_varint(packet.read_u128_varint().await?).await?; + pack.write_int(packet.read_int().await?).await?; + pack.write_long(packet.read_long().await?).await?; + pack.write_short(packet.read_short().await?).await?; + pack.write_uuid(&packet.read_uuid().await?).await?; + pack.write_string(&packet.read_string().await?.clone()).await?; + + stream.write_packet(&pack).await?; + } + + Ok(()) + } + + tokio::spawn(async { let _ = server_thread().await; }); + + let conn = MCConnTcp::connect("localhost:44447").await; + + while let Err(_) = conn {} + + let mut conn = conn?; + + conn.set_compression(Some(5)); + + let mut pack = Packet::empty(0xfe); + + pack.write_boolean(true).await?; + pack.write_byte(0x12).await?; + pack.write_bytes(&vec![0x01, 0x56, 0x47, 0x48, 0xf5, 0xc2, 0x45, 0x98, 0xde, 0x99]).await?; + pack.write_double(123456789.123456789f64).await?; + pack.write_float(789456.44422f32).await?; + pack.write_i128_varint(468927513325566).await?; + pack.write_u128_varint(99859652365236523).await?; + pack.write_int(77861346i32).await?; + pack.write_long(789465123545678946i64).await?; + pack.write_short(1233i16).await?; + pack.write_uuid(&Uuid::try_parse("550e8400-e29b-41d4-a716-446655440000").map_err(|_| ProtocolError::CloneError)?).await?; + pack.write_string("&packet.read_string()?").await?; + + conn.write_packet(&pack).await?; + + let mut packet = conn.read_packet().await?; + + assert_eq!(packet.read_boolean().await?, true); + assert_eq!(packet.read_byte().await?, 0x12); + assert_eq!(packet.read_bytes(10).await?, vec![0x01, 0x56, 0x47, 0x48, 0xf5, 0xc2, 0x45, 0x98, 0xde, 0x99]); + assert_eq!(packet.read_double().await?, 123456789.123456789f64); + assert_eq!(packet.read_float().await?, 789456.44422f32); + assert_eq!(packet.read_i128_varint().await?, 468927513325566); + assert_eq!(packet.read_u128_varint().await?, 99859652365236523); + assert_eq!(packet.read_int().await?, 77861346i32); + assert_eq!(packet.read_long().await?, 789465123545678946i64); + assert_eq!(packet.read_short().await?, 1233i16); + assert_eq!(packet.read_uuid().await?, Uuid::try_parse("550e8400-e29b-41d4-a716-446655440000").map_err(|_| ProtocolError::CloneError)?); + assert_eq!(packet.read_string().await?, "&packet.read_string()?"); + + conn.set_compression(None); + + let mut pack = Packet::empty(0xfe); + + pack.write_boolean(true).await?; + pack.write_byte(0x12).await?; + pack.write_bytes(&vec![0x01, 0x56, 0x47, 0x48, 0xf5, 0xc2, 0x45, 0x98, 0xde, 0x99]).await?; + pack.write_double(123456789.123456789f64).await?; + pack.write_float(789456.44422f32).await?; + pack.write_i128_varint(468927513325566).await?; + pack.write_u128_varint(99859652365236523).await?; + pack.write_int(77861346i32).await?; + pack.write_long(789465123545678946i64).await?; + pack.write_short(1233i16).await?; + pack.write_uuid(&Uuid::try_parse("550e8400-e29b-41d4-a716-446655440000").map_err(|_| ProtocolError::CloneError)?).await?; + pack.write_string("&packet.read_string()?").await?; + + conn.write_packet(&pack).await?; + + let mut packet = conn.read_packet().await?; + + assert_eq!(packet.read_boolean().await?, true); + assert_eq!(packet.read_byte().await?, 0x12); + assert_eq!(packet.read_bytes(10).await?, vec![0x01, 0x56, 0x47, 0x48, 0xf5, 0xc2, 0x45, 0x98, 0xde, 0x99]); + assert_eq!(packet.read_double().await?, 123456789.123456789f64); + assert_eq!(packet.read_float().await?, 789456.44422f32); + assert_eq!(packet.read_i128_varint().await?, 468927513325566); + assert_eq!(packet.read_u128_varint().await?, 99859652365236523); + assert_eq!(packet.read_int().await?, 77861346i32); + assert_eq!(packet.read_long().await?, 789465123545678946i64); + assert_eq!(packet.read_short().await?, 1233i16); + assert_eq!(packet.read_uuid().await?, Uuid::try_parse("550e8400-e29b-41d4-a716-446655440000").map_err(|_| ProtocolError::CloneError)?); + assert_eq!(packet.read_string().await?, "&packet.read_string()?"); + + Ok(()) +} + +#[tokio::test] +async fn test_compression() -> Result<(), ProtocolError> { + let mut conn = MCConn::new(Cursor::new(Vec::new())); + conn.set_compression(Some(5)); + + let mut packet_1 = Packet::empty(0x12); + packet_1.write_bytes(b"1234567890qwertyuiopasdfghjklzxcvbnm").await?; + dbg!(&packet_1); + conn.write_packet(&packet_1).await?; + + conn.get_mut().set_position(0); + + let mut packet_2 = conn.read_packet().await?; + + assert_eq!( + packet_2.read_bytes(36).await?, + b"1234567890qwertyuiopasdfghjklzxcvbnm" + ); + + Ok(()) +} diff --git a/src/zigzag.rs b/src/zigzag.rs new file mode 100644 index 0000000..b41018d --- /dev/null +++ b/src/zigzag.rs @@ -0,0 +1,65 @@ +//! VarInt reading helper + +pub trait Zigzag { + fn zigzag(&self) -> T; +} +impl Zigzag for i8 { + fn zigzag(&self) -> u8 { + ((self << 1) ^ (self >> 7)) as u8 + } +} +impl Zigzag for u8 { + fn zigzag(&self) -> i8 { + ((self >> 1) as i8) ^ (-((self & 1) as i8)) + } +} +impl Zigzag for i16 { + fn zigzag(&self) -> u16 { + ((self << 1) ^ (self >> 15)) as u16 + } +} +impl Zigzag for u16 { + fn zigzag(&self) -> i16 { + ((self >> 1) as i16) ^ (-((self & 1) as i16)) + } +} +impl Zigzag for i32 { + fn zigzag(&self) -> u32 { + ((self << 1) ^ (self >> 31)) as u32 + } +} +impl Zigzag for u32 { + fn zigzag(&self) -> i32 { + ((self >> 1) as i32) ^ (-((self & 1) as i32)) + } +} +impl Zigzag for i64 { + fn zigzag(&self) -> u64 { + ((self << 1) ^ (self >> 63)) as u64 + } +} +impl Zigzag for u64 { + fn zigzag(&self) -> i64 { + ((self >> 1) as i64) ^ (-((self & 1) as i64)) + } +} +impl Zigzag for i128 { + fn zigzag(&self) -> u128 { + ((self << 1) ^ (self >> 127)) as u128 + } +} +impl Zigzag for u128 { + fn zigzag(&self) -> i128 { + ((self >> 1) as i128) ^ (-((self & 1) as i128)) + } +} +impl Zigzag for isize { + fn zigzag(&self) -> usize { + ((self << 1) ^ (self >> std::mem::size_of::() - 1)) as usize + } +} +impl Zigzag for usize { + fn zigzag(&self) -> isize { + ((self >> 1) as isize) ^ (-((self & 1) as isize)) + } +}