diff options
author | Joey Sacchini <joey@sacchini.net> | 2020-12-02 19:28:56 -0500 |
---|---|---|
committer | Joey Sacchini <joey@sacchini.net> | 2020-12-02 19:28:56 -0500 |
commit | a75dccefd966560793e9776bc44d09fa22733a43 (patch) | |
tree | 7b99ebeaac6726f5141babbedd4b41c27ba3e586 | |
download | craftio-rs-a75dccefd966560793e9776bc44d09fa22733a43.tar.gz craftio-rs-a75dccefd966560793e9776bc44d09fa22733a43.tar.bz2 craftio-rs-a75dccefd966560793e9776bc44d09fa22733a43.zip |
init commit
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Cargo.lock | 513 | ||||
-rw-r--r-- | Cargo.toml | 19 | ||||
-rw-r--r-- | src/cfb8.rs | 105 | ||||
-rw-r--r-- | src/connection.rs | 125 | ||||
-rw-r--r-- | src/lib.rs | 14 | ||||
-rw-r--r-- | src/reader.rs | 294 | ||||
-rw-r--r-- | src/tcp.rs | 69 | ||||
-rw-r--r-- | src/util.rs | 55 | ||||
-rw-r--r-- | src/wrapper.rs | 12 | ||||
-rw-r--r-- | src/writer.rs | 420 |
11 files changed, 1629 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d114b83 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/.idea +*.iml
\ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a47a4cf --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,513 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher", +] + +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher", + "opaque-debug", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher", + "opaque-debug", +] + +[[package]] +name = "async-trait" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "cc" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array", +] + +[[package]] +name = "craftio-rs" +version = "0.1.0" +dependencies = [ + "aes", + "async-trait", + "flate2", + "futures", + "mcproto-rs", + "thiserror", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "flate2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "libz-sys", + "miniz_oxide", +] + +[[package]] +name = "futures" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" + +[[package]] +name = "futures-executor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" + +[[package]] +name = "futures-macro" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" + +[[package]] +name = "futures-task" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "libc" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" + +[[package]] +name = "libz-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "mcproto-rs" +version = "0.2.0" +source = "git+https://github.com/Twister915/mcproto-rs?branch=master#1f08aada116c9836f3d0f0e6261c47bbeffd7549" +dependencies = [ + "base64", + "rand", + "serde", + "serde_json", +] + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "miniz_oxide" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pin-project" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "syn" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "vcpkg" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..56b60b9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "craftio-rs" +version = "0.1.0" +authors = ["Joey Sacchini <joey@sacchini.net>"] +edition = "2018" +license = "APACHE 2.0" + +[dependencies] +mcproto-rs = { git = "https://github.com/Twister915/mcproto-rs", branch = "master", default-features = false, features = ["std", "v1_16_3"] } +futures = { version = "0.3.8", optional = true } +async-trait = { version = "0.1.42", optional = true } +aes = "0.6.0" +thiserror = "1.0" +flate2 = { version = "1.0", features = ["zlib"] } + +[features] +default = ["async"] + +async = ["futures", "async-trait"]
\ No newline at end of file diff --git a/src/cfb8.rs b/src/cfb8.rs new file mode 100644 index 0000000..6fc3bb6 --- /dev/null +++ b/src/cfb8.rs @@ -0,0 +1,105 @@ +use aes::{ + cipher::{consts::U16, generic_array::GenericArray, BlockCipherMut, NewBlockCipher}, + Aes128, +}; +use thiserror::Error; + +pub type CraftCipherResult<T> = Result<T, CipherError>; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum CipherComponent { + Key, + Iv, +} + +#[derive(Debug, Error)] +pub enum CipherError { + #[error("encryption is already enabled and cannot be enabled again")] + AlreadyEnabled, + #[error("bad size '{1}' for '{0:?}'")] + BadSize(CipherComponent, usize), +} + +const BYTES_SIZE: usize = 16; + +pub struct CraftCipher { + iv: GenericArray<u8, U16>, + tmp: GenericArray<u8, U16>, + cipher: Aes128, +} + +impl CraftCipher { + pub fn new(key: &[u8], iv: &[u8]) -> CraftCipherResult<Self> { + if iv.len() != BYTES_SIZE { + return Err(CipherError::BadSize(CipherComponent::Iv, iv.len())); + } + + if key.len() != BYTES_SIZE { + return Err(CipherError::BadSize(CipherComponent::Key, iv.len())); + } + + let mut iv_out = [0u8; BYTES_SIZE]; + iv_out.copy_from_slice(iv); + + let mut key_out = [0u8; BYTES_SIZE]; + key_out.copy_from_slice(key); + + let tmp = [0u8; BYTES_SIZE]; + + Ok(Self { + iv: GenericArray::from(iv_out), + tmp: GenericArray::from(tmp), + cipher: Aes128::new(&GenericArray::from(key_out)), + }) + } + + pub fn encrypt(&mut self, data: &mut [u8]) { + unsafe { self.crypt(data, false) } + } + + pub fn decrypt(&mut self, data: &mut [u8]) { + unsafe { self.crypt(data, true) } + } + + unsafe fn crypt(&mut self, data: &mut [u8], decrypt: bool) { + let iv = &mut self.iv; + const IV_SIZE: usize = 16; + const IV_SIZE_MINUS_ONE: usize = IV_SIZE - 1; + let iv_ptr = iv.as_mut_ptr(); + let iv_end_ptr = iv_ptr.offset(IV_SIZE_MINUS_ONE as isize); + let tmp_ptr = self.tmp.as_mut_ptr(); + let tmp_offset_one_ptr = tmp_ptr.offset(1); + let cipher = &mut self.cipher; + let n = data.len(); + let mut data_ptr = data.as_mut_ptr(); + let data_end_ptr = data_ptr.offset(n as isize); + + while data_ptr != data_end_ptr { + std::ptr::copy_nonoverlapping(iv_ptr, tmp_ptr, IV_SIZE); + cipher.encrypt_block(iv); + let orig = *data_ptr; + let updated = orig ^ *iv_ptr; + std::ptr::copy_nonoverlapping(tmp_offset_one_ptr, iv_ptr, IV_SIZE_MINUS_ONE); + if decrypt { + *iv_end_ptr = orig; + } else { + *iv_end_ptr = updated; + } + *data_ptr = updated; + data_ptr = data_ptr.offset(1); + } + } +} + +pub(crate) fn setup_craft_cipher( + target: &mut Option<CraftCipher>, + key: &[u8], + iv: &[u8], +) -> Result<(), CipherError> { + if target.is_some() { + Err(CipherError::AlreadyEnabled) + } else { + *target = Some(CraftCipher::new(key, iv)?); + Ok(()) + } +} diff --git a/src/connection.rs b/src/connection.rs new file mode 100644 index 0000000..af6f4a9 --- /dev/null +++ b/src/connection.rs @@ -0,0 +1,125 @@ +use crate::cfb8::CipherError; +use crate::reader::{CraftAsyncReader, CraftReader, CraftSyncReader, ReadResult}; +use crate::wrapper::{CraftIo, CraftWrapper}; +use crate::writer::{CraftAsyncWriter, CraftSyncWriter, CraftWriter, WriteResult}; +use mcproto_rs::protocol::{Packet, RawPacket, State}; + +#[cfg(feature = "async")] +use async_trait::async_trait; + +pub struct CraftConnection<R, W> { + pub(crate) reader: CraftReader<R>, + pub(crate) writer: CraftWriter<W>, +} + +impl<R, W> CraftWrapper<(CraftReader<R>, CraftWriter<W>)> for CraftConnection<R, W> { + fn into_inner(self) -> (CraftReader<R>, CraftWriter<W>) { + (self.reader, self.writer) + } +} + +impl<R, W> CraftIo for CraftConnection<R, W> { + fn set_state(&mut self, next: State) { + self.reader.set_state(next); + self.writer.set_state(next); + } + + fn set_compression_threshold(&mut self, threshold: Option<i32>) { + self.reader.set_compression_threshold(threshold); + self.writer.set_compression_threshold(threshold); + } + + fn enable_encryption(&mut self, key: &[u8], iv: &[u8]) -> Result<(), CipherError> { + self.reader.enable_encryption(key, iv)?; + self.writer.enable_encryption(key, iv)?; + Ok(()) + } +} + +impl<R, W> CraftSyncReader for CraftConnection<R, W> +where + CraftReader<R>: CraftSyncReader, + CraftWriter<W>: CraftSyncWriter, +{ + fn read_packet<'a, P>(&'a mut self) -> ReadResult<<P as RawPacket<'a>>::Packet> + where + P: RawPacket<'a>, + { + self.reader.read_packet::<P>() + } + + fn read_raw_packet<'a, P>(&'a mut self) -> ReadResult<P> + where + P: RawPacket<'a>, + { + self.reader.read_raw_packet::<P>() + } +} + +impl<R, W> CraftSyncWriter for CraftConnection<R, W> +where + CraftReader<R>: CraftSyncReader, + CraftWriter<W>: CraftSyncWriter, +{ + fn write_packet<P>(&mut self, packet: P) -> WriteResult<()> + where + P: Packet, + { + self.writer.write_packet(packet) + } + + fn write_raw_packet<'a, P>(&mut self, packet: P) -> WriteResult<()> + where + P: RawPacket<'a>, + { + self.writer.write_raw_packet(packet) + } +} + +#[cfg(feature = "async")] +#[async_trait] +impl<R, W> CraftAsyncReader for CraftConnection<R, W> +where + CraftReader<R>: CraftAsyncReader, + R: Send + Sync, + CraftWriter<W>: CraftAsyncWriter, + W: Send + Sync, +{ + async fn read_packet<'a, P>(&'a mut self) -> ReadResult<<P as RawPacket<'a>>::Packet> + where + P: RawPacket<'a>, + { + self.reader.read_packet::<P>().await + } + + async fn read_raw_packet<'a, P>(&'a mut self) -> ReadResult<P> + where + P: RawPacket<'a>, + { + self.reader.read_raw_packet::<P>().await + } +} + +#[cfg(feature = "async")] +#[async_trait] +impl<R, W> CraftAsyncWriter for CraftConnection<R, W> +where + CraftReader<R>: CraftAsyncReader, + R: Send + Sync, + CraftWriter<W>: CraftAsyncWriter, + W: Send + Sync, +{ + async fn write_packet<P>(&mut self, packet: P) -> WriteResult<()> + where + P: Packet + Send + Sync, + { + self.writer.write_packet(packet).await + } + + async fn write_raw_packet<'a, P>(&mut self, packet: P) -> WriteResult<()> + where + P: RawPacket<'a> + Send + Sync, + { + self.writer.write_raw_packet(packet).await + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8fafec0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +mod cfb8; +mod connection; +mod reader; +mod tcp; +mod util; +mod wrapper; +mod writer; + +pub use connection::CraftConnection; +pub use reader::*; +pub use writer::*; +pub use tcp::*; +pub use cfb8::CipherError; +pub use wrapper::*; diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 0000000..68b9a9b --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,294 @@ +use crate::cfb8::{setup_craft_cipher, CipherError, CraftCipher}; +use crate::util::{get_sized_buf, VAR_INT_BUF_SIZE}; +use crate::wrapper::{CraftIo, CraftWrapper}; +use flate2::{DecompressError, FlushDecompress, Status}; +use mcproto_rs::protocol::{Id, PacketDirection, RawPacket, State}; +use mcproto_rs::types::VarInt; +use mcproto_rs::{Deserialize, Deserialized}; +use thiserror::Error; + +#[cfg(feature = "async")] +use {async_trait::async_trait, futures::AsyncReadExt}; + +#[derive(Debug, Error)] +pub enum ReadError { + #[error("i/o failure during read")] + IoFailure(#[from] std::io::Error), + #[error("failed to read header VarInt")] + PacketHeaderErr(#[from] mcproto_rs::DeserializeErr), + #[error("failed to read packet")] + PacketErr(#[from] mcproto_rs::protocol::PacketErr), + #[error("failed to decompress packet")] + DecompressFailed(#[from] DecompressErr), +} + +#[derive(Debug, Error)] +pub enum DecompressErr { + #[error("buf error")] + BufError, + #[error("failure while decompressing")] + Failure(#[from] DecompressError), +} + +pub type ReadResult<P> = Result<Option<P>, ReadError>; + +#[cfg(feature = "async")] +#[async_trait] +pub trait CraftAsyncReader { + async fn read_packet<'a, P>(&'a mut self) -> ReadResult<<P as RawPacket<'a>>::Packet> + where + P: RawPacket<'a>, + { + deserialize_raw_packet(self.read_raw_packet::<P>().await) + } + + async fn read_raw_packet<'a, P>(&'a mut self) -> ReadResult<P> + where + P: RawPacket<'a>; +} + +pub trait CraftSyncReader { + fn read_packet<'a, P>(&'a mut self) -> ReadResult<<P as RawPacket<'a>>::Packet> + where + P: RawPacket<'a>, + { + deserialize_raw_packet(self.read_raw_packet::<'a, P>()) + } + + fn read_raw_packet<'a, P>(&'a mut self) -> ReadResult<P> + where + P: RawPacket<'a>; +} + +pub struct CraftReader<R> { + inner: R, + raw_buf: Option<Vec<u8>>, + decompress_buf: Option<Vec<u8>>, + compression_threshold: Option<i32>, + state: State, + direction: PacketDirection, + encryption: Option<CraftCipher>, +} + +impl<R> CraftWrapper<R> for CraftReader<R> { + fn into_inner(self) -> R { + self.inner + } +} + +impl<R> CraftIo for CraftReader<R> { + fn set_state(&mut self, next: State) { + self.state = next; + } + + fn set_compression_threshold(&mut self, threshold: Option<i32>) { + self.compression_threshold = threshold; + } + + fn enable_encryption(&mut self, key: &[u8], iv: &[u8]) -> Result<(), CipherError> { + setup_craft_cipher(&mut self.encryption, key, iv) + } +} + +macro_rules! rr_unwrap { + ($result: expr) => { + match $result { + Ok(Some(r)) => r, + Ok(None) => return Ok(None), + Err(err) => return Err(err), + } + }; +} + +macro_rules! check_unexpected_eof { + ($result: expr) => { + if let Err(err) = $result { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + return Ok(None); + } + + return Err(ReadError::IoFailure(err)); + } + }; +} + +impl<R> CraftSyncReader for CraftReader<R> +where + R: std::io::Read, +{ + fn read_raw_packet<'a, P>(&'a mut self) -> ReadResult<P> + where + P: RawPacket<'a>, + { + let primary_packet_len = rr_unwrap!(self.read_one_varint_sync()).0 as usize; + rr_unwrap!(self.read_n(primary_packet_len)); + self.read_packet_in_buf::<'a, P>(primary_packet_len) + } +} + +#[cfg(feature = "async")] +#[async_trait] +impl<R> CraftAsyncReader for CraftReader<R> +where + R: futures::AsyncRead + Unpin + Sync + Send, +{ + async fn read_raw_packet<'a, P>(&'a mut self) -> Result<Option<P>, ReadError> + where + P: RawPacket<'a>, + { + let primary_packet_len = rr_unwrap!(self.read_one_varint_async().await).0 as usize; + rr_unwrap!(self.read_n_async(primary_packet_len).await); + self.read_packet_in_buf::<P>(primary_packet_len) + } +} + +impl<R> CraftReader<R> +where + R: std::io::Read, +{ + fn read_one_varint_sync(&mut self) -> ReadResult<VarInt> { + deserialize_varint(rr_unwrap!(self.read_n(VAR_INT_BUF_SIZE))) + } + + fn read_n(&mut self, n: usize) -> ReadResult<&mut [u8]> { + let buf = get_sized_buf(&mut self.raw_buf, 0, n); + check_unexpected_eof!(self.inner.read_exact(buf)); + Ok(Some(buf)) + } +} + +#[cfg(feature = "async")] +impl<R> CraftReader<R> +where + R: futures::io::AsyncRead + Unpin + Sync + Send, +{ + async fn read_one_varint_async(&mut self) -> ReadResult<VarInt> { + deserialize_varint(rr_unwrap!(self.read_n_async(VAR_INT_BUF_SIZE).await)) + } + + async fn read_n_async(&mut self, n: usize) -> ReadResult<&mut [u8]> { + let buf = get_sized_buf(&mut self.raw_buf, 0, n); + check_unexpected_eof!(self.inner.read_exact(buf).await); + Ok(Some(buf)) + } +} + +macro_rules! dsz_unwrap { + ($bnam: expr, $k: ty) => { + match <$k>::mc_deserialize($bnam) { + Ok(Deserialized { + value: val, + data: rest, + }) => (val, rest), + Err(err) => { + return Err(ReadError::PacketHeaderErr(err)); + } + }; + }; +} + +impl<R> CraftReader<R> { + pub fn wrap(inner: R, direction: PacketDirection) -> Self { + Self::wrap_with_state(inner, direction, State::Handshaking) + } + + pub fn wrap_with_state(inner: R, direction: PacketDirection, state: State) -> Self { + Self { + inner, + raw_buf: None, + decompress_buf: None, + compression_threshold: None, + state, + direction, + encryption: None, + } + } + + fn read_packet_in_buf<'a, P>(&'a mut self, size: usize) -> ReadResult<P> + where + P: RawPacket<'a>, + { + // find data in buf + let buf = &mut self.raw_buf.as_mut().expect("should exist right now")[..size]; + // decrypt the packet if encryption is enabled + if let Some(encryption) = self.encryption.as_mut() { + encryption.decrypt(buf); + } + + // try to get the packet body bytes... this boils down to: + // * check if compression enabled, + // * read data len (VarInt) which isn't compressed + // * if data len is 0, then rest of packet is not compressed, remaining data is body + // * otherwise, data len is decompressed length, so prepare a decompression buf and decompress from + // the buffer into the decompression buffer, and return the slice of the decompression buffer + // which contains this packet's data + // * if compression not enabled, then the buf contains only the packet body bytes + + let packet_buf = if let Some(_) = self.compression_threshold { + let (data_len, rest) = dsz_unwrap!(buf, VarInt); + let data_len = data_len.0 as usize; + if data_len == 0 { + rest + } else { + decompress(rest, &mut self.decompress_buf, data_len)? + } + } else { + buf + }; + + let (raw_id, body_buf) = dsz_unwrap!(packet_buf, VarInt); + + let id = Id { + id: raw_id.0, + state: self.state.clone(), + direction: self.direction.clone(), + }; + + match P::create(id, body_buf) { + Ok(raw) => Ok(Some(raw)), + Err(err) => Err(ReadError::PacketErr(err)), + } + } +} + +fn deserialize_raw_packet<'a, P>(raw: ReadResult<P>) -> ReadResult<P::Packet> +where + P: RawPacket<'a>, +{ + match raw { + Ok(Some(raw)) => match raw.deserialize() { + Ok(deserialized) => Ok(Some(deserialized)), + Err(err) => Err(ReadError::PacketErr(err)), + }, + Ok(None) => Ok(None), + Err(err) => Err(err), + } +} + +fn deserialize_varint(buf: &[u8]) -> ReadResult<VarInt> { + match VarInt::mc_deserialize(buf) { + Ok(v) => Ok(Some(v.value)), + Err(err) => Err(ReadError::PacketHeaderErr(err)), + } +} + +fn decompress<'a>( + src: &'a [u8], + target: &'a mut Option<Vec<u8>>, + decompressed_len: usize, +) -> Result<&'a mut [u8], ReadError> { + let mut decompress = flate2::Decompress::new(true); + let decompress_buf = get_sized_buf(target, 0, decompressed_len); + loop { + match decompress.decompress(src, decompress_buf, FlushDecompress::Finish) { + Ok(Status::StreamEnd) => break, + Ok(Status::Ok) => {} + Ok(Status::BufError) => { + return Err(ReadError::DecompressFailed(DecompressErr::BufError)) + } + Err(err) => return Err(ReadError::DecompressFailed(DecompressErr::Failure(err))), + } + } + + Ok(&mut decompress_buf[..(decompress.total_out() as usize)]) +} diff --git a/src/tcp.rs b/src/tcp.rs new file mode 100644 index 0000000..e2ead90 --- /dev/null +++ b/src/tcp.rs @@ -0,0 +1,69 @@ +use crate::connection::CraftConnection; +use crate::reader::CraftReader; +use crate::writer::CraftWriter; +use mcproto_rs::protocol::{PacketDirection, State}; +use std::convert::TryFrom; +use std::io::BufReader as StdBufReader; +use std::net::TcpStream; + +#[cfg(feature = "async")] +use futures::io::{AsyncRead, AsyncWrite, BufReader as AsyncBufReader}; + +pub const BUF_SIZE: usize = 8192; + +pub type CraftTcpConnection = CraftConnection<StdBufReader<TcpStream>, TcpStream>; + +impl CraftConnection<StdBufReader<TcpStream>, TcpStream> { + pub fn connect_server_std(to: String) -> Result<Self, std::io::Error> { + Self::from_std(TcpStream::connect(to)?, PacketDirection::ClientBound) + } + + pub fn wrap_client_stream_std(stream: TcpStream) -> Result<Self, std::io::Error> { + Self::from_std(stream, PacketDirection::ServerBound) + } + + pub fn from_std( + s1: TcpStream, + read_direction: PacketDirection, + ) -> Result<Self, std::io::Error> { + Self::from_std_with_state(s1, read_direction, State::Handshaking) + } + + pub fn from_std_with_state( + s1: TcpStream, + read_direction: PacketDirection, + state: State, + ) -> Result<Self, std::io::Error> { + let write = s1.try_clone()?; + let read = StdBufReader::with_capacity(BUF_SIZE, s1); + + Ok(Self { + reader: CraftReader::wrap_with_state(read, read_direction, state), + writer: CraftWriter::wrap_with_state(write, read_direction.opposite(), state), + }) + } +} + +#[cfg(feature = "async")] +impl<R, W> CraftConnection<AsyncBufReader<R>, W> +where + R: AsyncRead + Send + Sync + Unpin, + W: AsyncWrite + Send + Sync + Unpin, +{ + pub fn from_async(tuple: (R, W), read_direction: PacketDirection) -> Self { + Self::from_async_with_state(tuple, read_direction, State::Handshaking) + } + + pub fn from_async_with_state( + tuple: (R, W), + read_direction: PacketDirection, + state: State, + ) -> Self { + let (reader, writer) = tuple; + let reader = AsyncBufReader::with_capacity(BUF_SIZE, reader); + Self { + reader: CraftReader::wrap_with_state(reader, read_direction, state), + writer: CraftWriter::wrap_with_state(writer, read_direction.opposite(), state), + } + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..a19922f --- /dev/null +++ b/src/util.rs @@ -0,0 +1,55 @@ +pub(crate) const VAR_INT_BUF_SIZE: usize = 5; + +pub(crate) fn get_sized_buf(buf: &mut Option<Vec<u8>>, offset: usize, size: usize) -> &mut [u8] { + let end_at = offset + size; + loop { + match buf { + Some(v) => { + ensure_buf_has_size(v, end_at); + break &mut v[offset..end_at]; + } + None => { + let new_buf = Vec::with_capacity(end_at); + *buf = Some(new_buf); + } + } + } +} +fn ensure_buf_has_size(buf: &mut Vec<u8>, total_size: usize) { + let cur_len = buf.len(); + if cur_len >= total_size { + return; + } + + let additional = total_size - cur_len; + buf.reserve(additional); + unsafe { + let start_at = buf.as_mut_ptr(); + let start_write_at = start_at.offset(cur_len as isize); + std::ptr::write_bytes(start_write_at, 0, additional); + buf.set_len(total_size); + } +} + +pub(crate) fn move_data_rightwards(target: &mut [u8], size: usize, shift_amount: usize) { + let required_len = size + shift_amount; + let actual_len = target.len(); + if actual_len < required_len { + panic!( + "move of data to the right (0..{} -> {}..{}) exceeds size of buffer {}", + size, shift_amount, required_len, actual_len, + ) + } + + unsafe { move_data_rightwards_unchecked(target, size, shift_amount) } +} + +unsafe fn move_data_rightwards_unchecked(target: &mut [u8], size: usize, shift_amount: usize) { + if shift_amount == 0 { + return; + } + + let src_ptr = target.as_mut_ptr(); + let dst_ptr = src_ptr.offset(shift_amount as isize); + std::ptr::copy(src_ptr, dst_ptr, size); +} diff --git a/src/wrapper.rs b/src/wrapper.rs new file mode 100644 index 0000000..06f451b --- /dev/null +++ b/src/wrapper.rs @@ -0,0 +1,12 @@ +use crate::cfb8::CipherError; +use mcproto_rs::protocol::State; + +pub trait CraftWrapper<I> { + fn into_inner(self) -> I; +} + +pub trait CraftIo { + fn set_state(&mut self, next: State); + fn set_compression_threshold(&mut self, threshold: Option<i32>); + fn enable_encryption(&mut self, key: &[u8], iv: &[u8]) -> Result<(), CipherError>; +} diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..6c76b23 --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,420 @@ +use crate::cfb8::{setup_craft_cipher, CipherError, CraftCipher}; +use crate::util::{get_sized_buf, move_data_rightwards, VAR_INT_BUF_SIZE}; +use crate::wrapper::{CraftIo, CraftWrapper}; +use flate2::{CompressError, Compression, FlushCompress, Status}; +use mcproto_rs::protocol::{Id, Packet, PacketDirection, RawPacket, State}; +use mcproto_rs::types::VarInt; +use mcproto_rs::{Serialize, SerializeErr, SerializeResult, Serializer}; +use thiserror::Error; + +#[cfg(feature = "async")] +use {async_trait::async_trait, futures::AsyncWriteExt}; + +#[derive(Debug, Error)] +pub enum WriteError { + #[error("serialization of header data failed")] + HeaderSerializeFail(SerializeErr), + #[error("packet body serialization failed")] + BodySerializeFail(SerializeErr), + #[error("failed to compress packet")] + CompressFail(CompressError), + #[error("compression gave buf error")] + CompressBufError, + #[error("io error while writing data")] + IoFail(#[from] std::io::Error), + #[error("bad direction")] + BadDirection { + attempted: PacketDirection, + expected: PacketDirection, + }, + #[error("bad state")] + BadState { attempted: State, expected: State }, +} + +pub type WriteResult<P> = Result<P, WriteError>; + +#[cfg(feature = "async")] +#[async_trait] +pub trait CraftAsyncWriter { + async fn write_packet<P>(&mut self, packet: P) -> WriteResult<()> + where + P: Packet + Send + Sync; + + async fn write_raw_packet<'a, P>(&mut self, packet: P) -> WriteResult<()> + where + P: RawPacket<'a> + Send + Sync; +} + +pub trait CraftSyncWriter { + fn write_packet<P>(&mut self, packet: P) -> WriteResult<()> + where + P: Packet; + + fn write_raw_packet<'a, P>(&mut self, packet: P) -> WriteResult<()> + where + P: RawPacket<'a>; +} + +pub struct CraftWriter<W> { + inner: W, + + raw_buf: Option<Vec<u8>>, + compress_buf: Option<Vec<u8>>, + compression_threshold: Option<i32>, + state: State, + direction: PacketDirection, + encryption: Option<CraftCipher>, +} + +impl<W> CraftWrapper<W> for CraftWriter<W> { + fn into_inner(self) -> W { + self.inner + } +} + +impl<W> CraftIo for CraftWriter<W> { + fn set_state(&mut self, next: State) { + self.state = next; + } + + fn set_compression_threshold(&mut self, threshold: Option<i32>) { + self.compression_threshold = threshold; + } + + fn enable_encryption(&mut self, key: &[u8], iv: &[u8]) -> Result<(), CipherError> { + setup_craft_cipher(&mut self.encryption, key, iv) + } +} + +impl<W> CraftSyncWriter for CraftWriter<W> +where + W: std::io::Write, +{ + fn write_packet<P>(&mut self, packet: P) -> WriteResult<()> + where + P: Packet, + { + let prepared = self.serialize_packet_to_buf(packet)?; + write_data_to_target_sync(self.prepare_packet_in_buf(prepared)?)?; + Ok(()) + } + + fn write_raw_packet<'a, P>(&mut self, packet: P) -> WriteResult<()> + where + P: RawPacket<'a>, + { + let prepared = self.serialize_raw_packet_to_buf(packet)?; + write_data_to_target_sync(self.prepare_packet_in_buf(prepared)?)?; + Ok(()) + } +} + +fn write_data_to_target_sync<'a, W>(tuple: (&'a [u8], &'a mut W)) -> Result<(), std::io::Error> +where + W: std::io::Write, +{ + let (data, target) = tuple; + target.write_all(data) +} + +#[cfg(feature = "async")] +#[async_trait] +impl<W> CraftAsyncWriter for CraftWriter<W> +where + W: futures::AsyncWrite + Unpin + Send + Sync, +{ + async fn write_packet<P>(&mut self, packet: P) -> WriteResult<()> + where + P: Packet + Send + Sync, + { + let prepared = self.serialize_packet_to_buf(packet)?; + write_data_to_target_async(self.prepare_packet_in_buf(prepared)?).await?; + Ok(()) + } + + async fn write_raw_packet<'a, P>(&mut self, packet: P) -> WriteResult<()> + where + P: RawPacket<'a> + Send + Sync, + { + let prepared = self.serialize_raw_packet_to_buf(packet)?; + write_data_to_target_async(self.prepare_packet_in_buf(prepared)?).await?; + Ok(()) + } +} + +#[cfg(feature = "async")] +async fn write_data_to_target_async<'a, W>( + tuple: (&'a [u8], &'a mut W), +) -> Result<(), std::io::Error> +where + W: futures::AsyncWrite + Unpin + Send + Sync, +{ + let (data, target) = tuple; + target.write_all(data).await +} + +const HEADER_OFFSET: usize = VAR_INT_BUF_SIZE * 2; + +struct PreparedPacketHandle { + id_size: usize, + data_size: usize, +} + +impl<W> CraftWriter<W> { + pub fn wrap(inner: W, direction: PacketDirection) -> Self { + Self::wrap_with_state(inner, direction, State::Handshaking) + } + + pub fn wrap_with_state(inner: W, direction: PacketDirection, state: State) -> Self { + Self { + inner, + raw_buf: None, + compression_threshold: None, + compress_buf: None, + state, + direction, + encryption: None, + } + } + + fn prepare_packet_in_buf( + &mut self, + prepared: PreparedPacketHandle, + ) -> WriteResult<(&[u8], &mut W)> { + // assume id and body are in raw buf from HEADER_OFFSET .. size + HEADER_OFFSET + let body_size = prepared.id_size + prepared.data_size; + let buf = get_sized_buf(&mut self.raw_buf, 0, body_size); + + let packet_data = if let Some(threshold) = self.compression_threshold { + if threshold >= 0 && (threshold as usize) <= body_size { + let compressed_size = compress(buf, &mut self.compress_buf, HEADER_OFFSET)?.len(); + let compress_buf = + get_sized_buf(&mut self.compress_buf, 0, compressed_size + HEADER_OFFSET); + + let data_len_target = &mut compress_buf[VAR_INT_BUF_SIZE..HEADER_OFFSET]; + let mut data_len_serializer = SliceSerializer::create(data_len_target); + VarInt(body_size as i32) + .mc_serialize(&mut data_len_serializer) + .map_err(move |err| WriteError::HeaderSerializeFail(err))?; + let data_len_bytes = data_len_serializer.finish().len(); + + let packet_len_target = &mut compress_buf[..VAR_INT_BUF_SIZE]; + let mut packet_len_serializer = SliceSerializer::create(packet_len_target); + VarInt((compressed_size + data_len_bytes) as i32) + .mc_serialize(&mut packet_len_serializer) + .map_err(move |err| WriteError::HeaderSerializeFail(err))?; + let packet_len_bytes = packet_len_serializer.finish().len(); + + let n_shift_packet_len = VAR_INT_BUF_SIZE - packet_len_bytes; + move_data_rightwards( + &mut compress_buf[..HEADER_OFFSET], + packet_len_bytes, + n_shift_packet_len, + ); + let n_shift_data_len = VAR_INT_BUF_SIZE - data_len_bytes; + move_data_rightwards( + &mut compress_buf[n_shift_packet_len..HEADER_OFFSET], + packet_len_bytes + data_len_bytes, + n_shift_data_len, + ); + let start_offset = n_shift_data_len + n_shift_packet_len; + let end_at = start_offset + data_len_bytes + packet_len_bytes + compressed_size; + &mut compress_buf[start_offset..end_at] + } else { + let packet_len_start_at = VAR_INT_BUF_SIZE - 1; + let packet_len_target = &mut buf[packet_len_start_at..HEADER_OFFSET - 1]; + let mut packet_len_serializer = SliceSerializer::create(packet_len_target); + VarInt((body_size + 1) as i32) + .mc_serialize(&mut packet_len_serializer) + .map_err(move |err| WriteError::HeaderSerializeFail(err))?; + + let packet_len_bytes = packet_len_serializer.finish().len(); + let n_shift_packet_len = VAR_INT_BUF_SIZE - packet_len_bytes; + move_data_rightwards( + &mut buf[packet_len_start_at..HEADER_OFFSET - 1], + packet_len_bytes, + n_shift_packet_len, + ); + + let start_offset = packet_len_start_at + n_shift_packet_len; + let end_at = start_offset + packet_len_bytes + 1 + body_size; + &mut buf[start_offset..end_at] + } + } else { + let packet_len_target = &mut buf[VAR_INT_BUF_SIZE..HEADER_OFFSET]; + let mut packet_len_serializer = SliceSerializer::create(packet_len_target); + VarInt(body_size as i32) + .mc_serialize(&mut packet_len_serializer) + .map_err(move |err| WriteError::HeaderSerializeFail(err))?; + let packet_len_bytes = packet_len_serializer.finish().len(); + let n_shift_packet_len = VAR_INT_BUF_SIZE - packet_len_bytes; + move_data_rightwards( + &mut buf[VAR_INT_BUF_SIZE..HEADER_OFFSET], + packet_len_bytes, + n_shift_packet_len, + ); + let start_offset = VAR_INT_BUF_SIZE + n_shift_packet_len; + let end_at = start_offset + packet_len_bytes + body_size; + &mut buf[start_offset..end_at] + }; + + if let Some(encryption) = &mut self.encryption { + encryption.encrypt(packet_data); + } + + Ok((packet_data, &mut self.inner)) + } + + fn serialize_packet_to_buf<P>(&mut self, packet: P) -> WriteResult<PreparedPacketHandle> + where + P: Packet, + { + let id_size = self.serialize_id_to_buf(packet.id())?; + let data_size = self.serialize_to_buf(HEADER_OFFSET + id_size, move |serializer| { + packet + .mc_serialize_body(serializer) + .map_err(move |err| WriteError::BodySerializeFail(err)) + })?; + + Ok(PreparedPacketHandle { id_size, data_size }) + } + + fn serialize_raw_packet_to_buf<'a, P>(&mut self, packet: P) -> WriteResult<PreparedPacketHandle> + where + P: RawPacket<'a>, + { + let id_size = self.serialize_id_to_buf(packet.id())?; + let packet_data = packet.data(); + let data_size = packet_data.len(); + let buf = get_sized_buf(&mut self.raw_buf, HEADER_OFFSET, id_size + data_size); + + (&mut buf[id_size..]).copy_from_slice(packet_data); + + Ok(PreparedPacketHandle { id_size, data_size }) + } + + fn serialize_id_to_buf(&mut self, id: Id) -> WriteResult<usize> { + if id.direction != self.direction { + return Err(WriteError::BadDirection { + expected: self.direction, + attempted: id.direction, + }); + } + + if id.state != self.state { + return Err(WriteError::BadState { + expected: self.state, + attempted: id.state, + }); + } + + self.serialize_to_buf(HEADER_OFFSET, move |serializer| { + id.mc_serialize(serializer) + .map_err(move |err| WriteError::HeaderSerializeFail(err)) + }) + } + + fn serialize_to_buf<'a, F>(&'a mut self, offset: usize, f: F) -> WriteResult<usize> + where + F: FnOnce(&mut GrowVecSerializer<'a>) -> Result<(), WriteError>, + { + let mut serializer = GrowVecSerializer::create(&mut self.raw_buf, offset); + f(&mut serializer)?; + Ok(serializer.finish().map(move |b| b.len()).unwrap_or(0)) + } +} + +#[derive(Debug)] +struct GrowVecSerializer<'a> { + target: &'a mut Option<Vec<u8>>, + at: usize, + offset: usize, +} + +impl<'a> Serializer for GrowVecSerializer<'a> { + fn serialize_bytes(&mut self, data: &[u8]) -> SerializeResult { + get_sized_buf(self.target, self.at + self.offset, data.len()).copy_from_slice(data); + Ok(()) + } +} + +impl<'a> GrowVecSerializer<'a> { + fn create(target: &'a mut Option<Vec<u8>>, offset: usize) -> Self { + Self { + target, + at: 0, + offset, + } + } + + fn finish(self) -> Option<&'a mut [u8]> { + if let Some(buf) = self.target { + Some(&mut buf[self.offset..self.offset + self.at]) + } else { + None + } + } +} + +struct SliceSerializer<'a> { + target: &'a mut [u8], + at: usize, +} + +impl<'a> Serializer for SliceSerializer<'a> { + fn serialize_bytes(&mut self, data: &[u8]) -> SerializeResult { + let end_at = self.at + data.len(); + if end_at >= self.target.len() { + panic!( + "cannot fit data in slice ({} exceeds length {} at {})", + data.len(), + self.target.len(), + self.at + ); + } + + (&mut self.target[self.at..end_at]).copy_from_slice(data); + self.at = end_at; + Ok(()) + } +} + +impl<'a> SliceSerializer<'a> { + fn create(target: &'a mut [u8]) -> Self { + Self { target, at: 0 } + } + + fn finish(self) -> &'a [u8] { + &self.target[..self.at] + } +} + +fn compress<'a, 'b>( + src: &'b [u8], + output: &'a mut Option<Vec<u8>>, + offset: usize, +) -> Result<&'a mut [u8], WriteError> { + let target = get_sized_buf(output, offset, src.len()); + let mut compressor = flate2::Compress::new_with_window_bits(Compression::fast(), true, 15); + loop { + let input = &src[(compressor.total_in() as usize)..]; + let eof = input.is_empty(); + let output = &mut target[(compressor.total_out() as usize)..]; + let flush = if eof { + FlushCompress::Finish + } else { + FlushCompress::None + }; + + match compressor + .compress(input, output, flush) + .map_err(move |err| WriteError::CompressFail(err))? + { + Status::Ok => {} + Status::BufError => return Err(WriteError::CompressBufError), + Status::StreamEnd => break, + } + } + + Ok(&mut target[..(compressor.total_out() as usize)]) +} |