aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoey Sacchini <joey@sacchini.net>2020-12-02 19:28:56 -0500
committerJoey Sacchini <joey@sacchini.net>2020-12-02 19:28:56 -0500
commita75dccefd966560793e9776bc44d09fa22733a43 (patch)
tree7b99ebeaac6726f5141babbedd4b41c27ba3e586
downloadcraftio-rs-a75dccefd966560793e9776bc44d09fa22733a43.tar.gz
craftio-rs-a75dccefd966560793e9776bc44d09fa22733a43.tar.bz2
craftio-rs-a75dccefd966560793e9776bc44d09fa22733a43.zip
init commit
-rw-r--r--.gitignore3
-rw-r--r--Cargo.lock513
-rw-r--r--Cargo.toml19
-rw-r--r--src/cfb8.rs105
-rw-r--r--src/connection.rs125
-rw-r--r--src/lib.rs14
-rw-r--r--src/reader.rs294
-rw-r--r--src/tcp.rs69
-rw-r--r--src/util.rs55
-rw-r--r--src/wrapper.rs12
-rw-r--r--src/writer.rs420
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)])
+}