From 81610da67deb6b400663f70fca6f96e1522d88cd Mon Sep 17 00:00:00 2001 From: Skye Date: Sun, 2 Apr 2023 22:22:03 +0900 Subject: [PATCH] first commit --- .envrc | 3 + .gitignore | 1 + Cargo.lock | 1266 +++++++++++++++++ Cargo.toml | 6 + client/Cargo.toml | 21 + client/src/main.rs | 186 +++ client/src/netty.rs | 233 +++ flake.lock | 77 + flake.nix | 36 + server/Cargo.toml | 20 + server/src/disconnect_response.json | 3 + server/src/legacy_serverlistping_response.bin | Bin 0 -> 101 bytes server/src/main.rs | 392 +++++ server/src/netty.rs | 234 +++ server/src/serverlistping_response.json | 13 + 15 files changed, 2491 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 client/Cargo.toml create mode 100644 client/src/main.rs create mode 100644 client/src/netty.rs create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 server/Cargo.toml create mode 100644 server/src/disconnect_response.json create mode 100644 server/src/legacy_serverlistping_response.bin create mode 100644 server/src/main.rs create mode 100644 server/src/netty.rs create mode 100644 server/src/serverlistping_response.json diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..9bec6e9 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +export BASE_DOMAIN=lc.bs2k.me +export WS_BIND_ADDR=127.0.0.1:8080 +export RUST_LOG=info \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a9db432 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1266 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "async-tungstenite" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0388bb7a400072bbb41ceb75d65c3baefb2ea99672fa22e85278452cd9b58b" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tungstenite", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "e4mc-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "async-tungstenite", + "clap", + "env_logger", + "futures", + "futures-util", + "lazy_static", + "log", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] +name = "e4mc-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "async-tungstenite", + "env_logger", + "futures", + "lazy_static", + "log", + "nanoid", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0dd4be24fcdcfeaa12a432d588dc59bbad6cad3510c67e74a2b6b2fc950564" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustix" +version = "0.37.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d097081ed288dfe45699b72f5b5d648e5f15d64d900c7080273baa20c16a6849" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.45.0", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha1", + "thiserror", + "url", + "utf-8", + "webpki", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..830d036 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +members = [ + "server", + "client", +] diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100644 index 0000000..b13803a --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "e4mc-client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.70" +async-trait = "0.1.68" +async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-rustls-native-certs"] } +clap = { version = "4.2.1", features = ["derive"] } +env_logger = "0.10.0" +futures = "0.3.28" +futures-util = { version = "0.3.28", features = ["io"] } +lazy_static = "1.4.0" +log = "0.4.17" +serde = { version = "1.0.159", features = ["derive"] } +serde_json = "1.0.95" +thiserror = "1.0.40" +tokio = { version = "1.27.0", features = ["rt-multi-thread", "sync", "macros", "net", "io-util"] } diff --git a/client/src/main.rs b/client/src/main.rs new file mode 100644 index 0000000..7eadbbb --- /dev/null +++ b/client/src/main.rs @@ -0,0 +1,186 @@ +use std::{collections::HashMap, net::SocketAddr}; + +use anyhow::anyhow; +use async_tungstenite::tokio::connect_async; +use async_tungstenite::tungstenite::Message; +use futures_util::{SinkExt, StreamExt}; +use lazy_static::lazy_static; +use log::{error, info, trace}; +use serde::{Deserialize, Serialize}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{ + tcp::{OwnedReadHalf, OwnedWriteHalf}, + TcpStream, + }, + sync::mpsc::{UnboundedReceiver, UnboundedSender}, + sync::RwLock, + task, +}; + +enum ChannelHandlerMessage { + Data(Vec), + Shutdown, +} + +lazy_static! { + static ref CHANNEL_MAP: RwLock>> = + RwLock::new(HashMap::new()); +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let _ = env_logger::try_init(); + let args = Args::parse(); + let server = args.server.unwrap_or("wss://e4mc.skyevg.systems".to_string()); + let (ws_conn, _) = connect_async(server).await?; + let (mut send, mut recv) = ws_conn.split(); + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel(); + let sender = &*Box::leak(Box::new(sender)); // the task::spawn below requires 'static and this is main + tokio::try_join!( + async move { + while let Some(Ok(message)) = recv.next().await { + match message { + Message::Text(message) => { + info!("{}", message); + let message: ClientboundControlMessage = serde_json::from_str(&message)?; + match message { + ClientboundControlMessage::DomainAssigned(domain) => { + println!("Domain assigned: {}", domain); + } + ClientboundControlMessage::ChannelOpen(id, _) => { + match TcpStream::connect(("127.0.0.1", args.port)).await { + Ok(conn) => { + let (send, recv) = tokio::sync::mpsc::unbounded_channel(); + CHANNEL_MAP.write().await.insert(id, send); + task::spawn(async move { + if let Err(e) = + handle_channel(id, sender.clone(), conn, recv).await + { + error!("Error handling channel: {}", e); + } + CHANNEL_MAP.write().await.remove(&id); + info!("Sending close request for channel: {}", id); + sender + .send(Message::Text( + serde_json::to_string( + &ServerboundControlMessage::ChannelClosed( + id, + ), + ) + .unwrap(), + )) + .unwrap(); + }); + } + Err(e) => { + error!("Error creating channel: {}", e); + sender + .send(Message::Text( + serde_json::to_string( + &ServerboundControlMessage::ChannelClosed( + id, + ), + ) + .unwrap(), + )) + .unwrap(); + } + }; + } + ClientboundControlMessage::ChannelClosed(id) => { + let mut channel_map = CHANNEL_MAP.write().await; + if let Some(send) = channel_map.remove(&id) { + if let Err(e) = send.send(ChannelHandlerMessage::Shutdown) { + error!("Error closing channel: {}", e); + } + } + } + } + } + Message::Binary(buf) => { + trace!("recv: {:?}", buf); + if let Some(send) = CHANNEL_MAP.write().await.get_mut(&buf[0]) { + if let Err(e) = send.send(ChannelHandlerMessage::Data(buf[1..].to_vec())) { + error!("Error transferring data: {}", e); + } + } + } + _ => {} + } + } + Ok(()) as anyhow::Result<()> + }, + async move { + while let Some(message) = receiver.recv().await { + send.send(message.clone()).await?; + } + Ok(()) as anyhow::Result<()> + } + )?; + Ok(()) +} + +async fn handle_channel( + id: u8, + sender: UnboundedSender, + mut conn: TcpStream, + mut recv: UnboundedReceiver, +) -> anyhow::Result<()> { + let (mut read, mut write) = conn.split(); + tokio::select!(_ = async move { + let mut buf = [0u8; 1024]; + loop { + let ready = read.ready(tokio::io::Interest::READABLE).await?; + if ready.is_read_closed() { + return Ok(()) + } + let len = read.read(&mut buf).await?; + if len == 0 { + continue; + } + let mut packet = Vec::from(&buf[..len]); + packet.insert(0, id); + trace!("send: {:?}", packet); + sender.send(Message::Binary(packet))?; + } + Ok(()) as anyhow::Result<()> + } => {}, + _ = async move { + while let Some(message) = recv.recv().await { + match message { + ChannelHandlerMessage::Data(buf) => write.write_all(&buf).await?, + ChannelHandlerMessage::Shutdown => return Ok(()), + } + } + Ok(()) as anyhow::Result<()> + } => {}); + conn.shutdown().await?; + Ok(()) +} + +use clap::Parser; + +/// Simple program to greet a person +#[derive(Parser, Debug)] +struct Args { + /// Port of Minecraft server + #[arg(short, long, default_value_t = 25565)] + port: u16, + + /// Address of e4mc-server instance + #[arg(short, long)] + server: Option, +} + +#[derive(Deserialize)] +enum ClientboundControlMessage { + DomainAssigned(String), + ChannelOpen(u8, SocketAddr), + ChannelClosed(u8), +} + +#[derive(Serialize)] +enum ServerboundControlMessage { + ChannelClosed(u8), +} diff --git a/client/src/netty.rs b/client/src/netty.rs new file mode 100644 index 0000000..e6a0206 --- /dev/null +++ b/client/src/netty.rs @@ -0,0 +1,233 @@ +use async_std::io::{ReadExt, WriteExt}; + +use async_trait::async_trait; +use log::{info, error}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum NettyReadError { + #[error("{0}")] + IoError(std::io::Error), + #[error("Was not a netty packet, but a Legacy ServerListPing")] + LegacyServerListPing, +} + +impl From for NettyReadError { + fn from(value: std::io::Error) -> Self { + Self::IoError(value) + } +} + +impl From for NettyReadError { + fn from(value: std::io::ErrorKind) -> Self { + Self::IoError(value.into()) + } +} + +#[async_trait] +pub trait ReadExtNetty: ReadExt + Unpin { + async fn read_bool(&mut self) -> Result { + let mut buf = [0u8]; + self.read_exact(&mut buf).await?; + match buf[0] { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(std::io::ErrorKind::InvalidData.into()), + } + } + + async fn read_byte(&mut self) -> Result { + let mut buf = [0u8]; + self.read_exact(&mut buf).await?; + Ok(i8::from_be_bytes(buf)) + } + + async fn read_unsigned_byte(&mut self) -> Result { + let mut buf = [0u8]; + self.read_exact(&mut buf).await?; + Ok(buf[0]) + } + + async fn read_short(&mut self) -> Result { + let mut buf = [0u8; 2]; + self.read_exact(&mut buf).await?; + Ok(i16::from_be_bytes(buf)) + } + + async fn read_unsigned_short(&mut self) -> Result { + let mut buf = [0u8; 2]; + self.read_exact(&mut buf).await?; + Ok(u16::from_be_bytes(buf)) + } + + async fn read_int(&mut self) -> Result { + let mut buf = [0u8; 4]; + self.read_exact(&mut buf).await?; + Ok(i32::from_be_bytes(buf)) + } + + async fn read_long(&mut self) -> Result { + let mut buf = [0u8; 8]; + self.read_exact(&mut buf).await?; + Ok(i64::from_be_bytes(buf)) + } + + async fn read_float(&mut self) -> Result { + let mut buf = [0u8; 4]; + self.read_exact(&mut buf).await?; + Ok(f32::from_be_bytes(buf)) + } + + async fn read_double(&mut self) -> Result { + let mut buf = [0u8; 8]; + self.read_exact(&mut buf).await?; + Ok(f64::from_be_bytes(buf)) + } + + async fn read_string(&mut self) -> Result { + let len = self.read_varint().await?; + let mut buf = vec![0u8; len as usize]; + self.read_exact(&mut buf).await?; + String::from_utf8(buf).map_err(|_| std::io::ErrorKind::InvalidData.into()) + } + + async fn read_varint(&mut self) -> Result { + let mut res = 0i32; + for i in 0..5 { + let part = self.read_unsigned_byte().await?; + res |= (part as i32 & 0x7F) << (7 * i); + if part & 0x80 == 0 { + return Ok(res); + } + } + error!("Varint is invalid"); + Err(std::io::ErrorKind::InvalidData.into()) + } + + // async fn read_varint(&mut self) -> Result { + // let mut value = 0i32; + // let mut buf = [0u8; 1]; + // let mut pos = 0u8; + // loop { + // self.read_exact(&mut buf).await?; + // println!("{}", buf[0]); + // value |= ((buf[0] & 0b01111111) << pos) as i32; + // if (buf[0] & 0b10000000) == 0 { + // break; + // }; + // pos += 7; + // if pos >= 32 { + // return Err(std::io::ErrorKind::InvalidData.into()); + // }; + // } + // Ok(value) + // } + + async fn read_varlong(&mut self) -> Result { + let mut value = 0i64; + let mut buf = [0u8; 1]; + let mut position = 0u8; + loop { + self.read_exact(&mut buf).await?; + value |= ((buf[0] & 0b01111111) << position) as i64; + if (buf[0] & 0b10000000) == 0 { + break; + }; + position += 7; + if position >= 64 { + return Err(std::io::ErrorKind::InvalidData.into()); + }; + } + Ok(value) + } + + async fn read_vec3(&mut self) -> Result<(i32, i16, i32), NettyReadError> { + let mut buf = [0u8; 8]; + self.read_exact(&mut buf).await?; + let packed = u64::from_be_bytes(buf); + let x: i32 = ((packed & 0xffffffc000000000) >> 38) as _; + let y: i16 = (packed & 0xfff) as _; + let z: i32 = ((packed & 0x3ffffff000) >> 12) as _; + Ok(( + match x { + i32::MIN..=0x1ffffff => x, + 0x2000000.. => x - 0x2000000, + }, + match y { + i16::MIN..=0x7ff => y, + 0x800.. => y - 0x800, + }, + match z { + i32::MIN..=0x1ffffff => z, + 0x2000000.. => z - 0x2000000, + }, + )) + } + + async fn read_uuid(&mut self) -> Result { + let mut buf = [0u8; 16]; + self.read_exact(&mut buf).await?; + Ok(u128::from_be_bytes(buf)) + } + + async fn read_packet(&mut self) -> Result, NettyReadError> { + let len = self.read_varint().await?; + let mut buf = vec![0u8; len as usize]; + if len == 254 { + let mut temp = [0u8]; + self.read_exact(&mut temp).await?; + if temp[0] == 0xFA { + // FE 01 FA: Legacy ServerListPing + return Err(NettyReadError::LegacyServerListPing); + } + buf[0] = temp[0]; + self.read_exact(&mut buf[1..]).await?; + } else { + self.read_exact(&mut buf).await?; + } + Ok(buf) + } + + // fn read_packet_compressed(&mut self) -> Result, NettyReadError> { + // let len = self.read_varint()?; + // let len_decompressed = self.read_varint()?; + // let mut buf = vec![0u8; len as usize]; + // self.read_exact(&mut buf)?; + // if len_decompressed == 0 { + // return Ok(buf); + // } + // let mut buf_decompressed = vec![0u8; len_decompressed as usize]; + // if flate2::Decompress::new(true) + // .decompress(&buf, &mut buf_decompressed, flate2::FlushDecompress::Finish) + // .is_err() + // { + // return Err(std::io::ErrorKind::InvalidData.into()); + // }; + // Ok(buf_decompressed) + // } +} + +impl ReadExtNetty for T {} + +#[async_trait] +pub trait WriteExtNetty: WriteExt + Unpin { + async fn write_varint(&mut self, mut val: i32) -> std::io::Result<()> { + for _ in 0..5 { + if val & !0x7F == 0 { + self.write_all(&[val as u8]).await?; + return Ok(()); + } + self.write_all(&[(val & 0x7F | 0x80) as u8]).await?; + val >>= 7; + } + Err(std::io::ErrorKind::InvalidData.into()) + } + + async fn write_string(&mut self, s: &str) -> std::io::Result<()> { + self.write_varint(s.len() as i32).await?; + self.write_all(s.as_bytes()).await?; + Ok(()) + } +} + +impl WriteExtNetty for T {} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..451a52b --- /dev/null +++ b/flake.lock @@ -0,0 +1,77 @@ +{ + "nodes": { + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1679567394, + "narHash": "sha256-ZvLuzPeARDLiQUt6zSZFGOs+HZmE+3g4QURc8mkBsfM=", + "owner": "nix-community", + "repo": "naersk", + "rev": "88cd22380154a2c36799fe8098888f0f59861a15", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "master", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1680273054, + "narHash": "sha256-Bs6/5LpvYp379qVqGt9mXxxx9GSE789k3oFc+OAL07M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1680273054, + "narHash": "sha256-Bs6/5LpvYp379qVqGt9mXxxx9GSE789k3oFc+OAL07M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "naersk": "naersk", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + } + }, + "utils": { + "locked": { + "lastModified": 1678901627, + "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..6bf7b1e --- /dev/null +++ b/flake.nix @@ -0,0 +1,36 @@ +{ + inputs = { + naersk.url = "github:nix-community/naersk/master"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, utils, naersk }: + utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + naersk-lib = pkgs.callPackage naersk { }; + in + rec { + packages.e4mc-server = naersk-lib.buildPackage { + pname = "e4mc-server"; + src = ./.; + }; + packages.e4mc-client = naersk-lib.buildPackage { + pname = "e4mc-client"; + src = ./.; + }; + dockerImage = pkgs.dockerTools.buildImage { + name = "e4mc-server"; + tag = "latest"; + + config = { + Cmd = [ "${packages.e4mc-server}/bin/e4mc-server" ]; + }; + }; + devShell = with pkgs; mkShell { + buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy ]; + RUST_SRC_PATH = rustPlatform.rustLibSrc; + }; + }); +} \ No newline at end of file diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..25c166b --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "e4mc-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.70" +async-trait = "0.1.68" +async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-rustls-native-certs"] } +env_logger = "0.10.0" +futures = "0.3.28" +lazy_static = "1.4.0" +log = "0.4.17" +nanoid = "0.4.0" +serde = { version = "1.0.159", features = ["derive"] } +serde_json = "1.0.95" +thiserror = "1.0.40" +tokio = { version = "1.27.0", features = ["rt-multi-thread", "sync", "macros", "net", "io-util"] } diff --git a/server/src/disconnect_response.json b/server/src/disconnect_response.json new file mode 100644 index 0000000..ddea148 --- /dev/null +++ b/server/src/disconnect_response.json @@ -0,0 +1,3 @@ +{ + "text": "Unknown server. Check address and try again." +} \ No newline at end of file diff --git a/server/src/legacy_serverlistping_response.bin b/server/src/legacy_serverlistping_response.bin new file mode 100644 index 0000000000000000000000000000000000000000..9411c5a4b24e61e30d4bbe8a0ee0901ff404df85 GIT binary patch literal 101 zcmYj}%L#xm5JcZORcr;KAOy?tcgsg&JftGa)WMSs%bQtt=V9Sy0451`Mk4Fa8JR?& dVVqnXQl?OqOrITIoOQE_L9VK$Xb=52?gea)59t5^ literal 0 HcmV?d00001 diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..26ad940 --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,392 @@ +use std::collections::{HashMap}; +use std::env; +use std::net::SocketAddr; + +use anyhow::{anyhow, Result}; +use futures::{SinkExt, StreamExt}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::RwLock; +use tokio::task; +use async_tungstenite::tungstenite::Message; +use lazy_static::lazy_static; +use log::{error, info}; +use nanoid::nanoid; +use netty::ReadExtNetty; +use serde::{Deserialize, Serialize}; + +use crate::netty::{NettyReadError, WriteExtNetty}; + +mod netty; + +#[derive(Debug, Clone)] +struct Handshake { + protocol_version: i32, + server_address: String, + server_port: u16, + next_state: HandshakeType, +} + +#[derive(Debug, Clone)] +#[repr(i32)] +enum HandshakeType { + Status = 1, + Login = 2, +} + +impl Handshake { + async fn new(mut packet: &[u8]) -> Result { + let packet_type = packet.read_varint().await?; + if packet_type != 0 { + Err(anyhow!("Not a Handshake packet")) + } else { + let protocol_version = packet.read_varint().await?; + let server_address = packet.read_string().await?; + let server_port = packet.read_unsigned_short().await?; + let next_state = match packet.read_varint().await? { + 1 => HandshakeType::Status, + 2 => HandshakeType::Login, + _ => return Err(anyhow!("Invalid next state")), + }; + Ok(Self { + protocol_version, + server_address, + server_port, + next_state, + }) + } + } +} + +lazy_static! { + static ref EXPOSER_MAP: RwLock>> = + RwLock::new(HashMap::new()); + static ref BASE_DOMAIN: String = env::var("BASE_DOMAIN").expect("BASE_DOMAIN missing"); +} + +#[tokio::main] +async fn main() -> Result<()> { + let _ = env_logger::try_init(); + let ws_bind_addr = env::var("WS_BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:80".to_string()); + let mc_bind_addr = env::var("MC_BIND_ADDR").unwrap_or_else(|_| "0.0.0.0:25565".to_string()); + + futures::try_join!( + async { + let listener = TcpListener::bind(&ws_bind_addr).await?; + info!("WebSocket Listening on: {}", ws_bind_addr); + while let Ok((stream, _)) = listener.accept().await { + task::spawn(async { + if let Err(e) = accept_ws_connection(stream).await { + error!("Error handling WebSocket connection: {}", e); + } + }); + } + Ok(()) as Result<()> + }, + async { + let listener = TcpListener::bind(&mc_bind_addr).await?; + info!("Minecraft Listening on: {}", mc_bind_addr); + while let Ok((stream, _)) = listener.accept().await { + task::spawn(async { + if let Err(e) = accept_mc_connection(stream).await { + error!("Error handling Minecraft connection: {}", e); + } + }); + } + Ok(()) as Result<()> + } + )?; + + Ok(()) +} + +#[derive(Serialize)] +enum ClientboundControlMessage { + DomainAssigned(String), + ChannelOpen(u8, SocketAddr), + ChannelClosed(u8), +} + +#[derive(Deserialize)] +enum ServerboundControlMessage { + ChannelClosed(u8), +} + +struct ExposerMapHandle(String, UnboundedReceiver<(Handshake, TcpStream, SocketAddr)>); + +impl ExposerMapHandle { + async fn new(domain: String) -> Self { + let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + EXPOSER_MAP.write().await.insert(domain.clone(), sender); + Self(domain, receiver) + } + + async fn recv(&mut self) -> Option<(Handshake, TcpStream, SocketAddr)> { + self.1.recv().await + } +} + +impl Drop for ExposerMapHandle { + fn drop(&mut self) { + let domain = self.0.clone(); + task::spawn(async move { + EXPOSER_MAP.write().await.remove(&domain); + }); + } +} + +struct ChannelHandle( + u8, + UnboundedReceiver, + UnboundedSender, +); + +impl ChannelHandle { + async fn new( + id: u8, + addr: SocketAddr, + send: UnboundedSender, + ) -> Result { + let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + send.send(WebsocketConnectionMessage::Open(id, addr, sender)) + .map_err(|e| anyhow::anyhow!("{}", e))?; + Ok(Self(id, receiver, send)) + } + + async fn recv(&mut self) -> Option { + self.1.recv().await + } +} + +impl Drop for ChannelHandle { + fn drop(&mut self) { + self.2.send(WebsocketConnectionMessage::Close(self.0)).map_err(|e| anyhow::anyhow!("{}", e)).unwrap(); + } +} + +enum MinecraftConnectionMessage { + Data(Vec), + Close, +} + +enum WebsocketConnectionMessage { + Data(Vec), + Close(u8), + Open(u8, SocketAddr, UnboundedSender), +} + +async fn accept_ws_connection(stream: TcpStream) -> Result<()> { + let addr = stream.peer_addr()?; + info!("WebSocker Peer address: {}", addr); + + let mut ws_stream = async_tungstenite::tokio::accept_async(stream).await?; + + info!("New WebSocket connection: {}", addr); + + let mut domain = get_random_domain(); + + let exposer_map = EXPOSER_MAP.read().await; + while exposer_map.contains_key(&domain) { + domain = get_random_domain(); + } + let domain = domain; + info!("Connection {} was assigned domain {}", addr, domain); + + ws_stream + .send(Message::Text(serde_json::to_string( + &ClientboundControlMessage::DomainAssigned(domain.clone()), + )?)) + .await?; + + drop(exposer_map); + + let mut exposer_handle = ExposerMapHandle::new(domain.clone()).await; + + let channel_map = &RwLock::new(HashMap::new()); + + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel(); + let (mut send, mut recv) = ws_stream.split(); + + futures::try_join!( + async move { + while let Some(message) = receiver.recv().await { + match message { + WebsocketConnectionMessage::Data(buf) => { + send.send(Message::Binary(buf)).await? + } + WebsocketConnectionMessage::Close(id) => { + send.send(Message::Text(serde_json::to_string( + &ClientboundControlMessage::ChannelClosed(id), + )?)) + .await?; + channel_map.write().await.remove(&id); + } + WebsocketConnectionMessage::Open(id, addr, channel) => { + send.send(Message::Text(serde_json::to_string( + &ClientboundControlMessage::ChannelOpen(id, addr), + )?)) + .await?; + channel_map.write().await.insert(id, channel); + } + } + } + Ok(()) as Result<()> + }, + async move { + while let Some(Ok(message)) = recv.next().await { + match message { + Message::Text(message) => { + let message: ServerboundControlMessage = serde_json::from_str(&message)?; + match message { + ServerboundControlMessage::ChannelClosed(id) => { + if let Some(channel) = channel_map.write().await.remove(&id) { + info!("Closing channel id: {}", id); + channel.send(MinecraftConnectionMessage::Close).map_err(|e| anyhow::anyhow!("{}", e))?; + } + } + } + } + Message::Binary(buf) => { + if let Some(channel) = channel_map.read().await.get(&buf[0]) { + channel + .send(MinecraftConnectionMessage::Data(buf[1..].to_vec())).map_err(|e| anyhow::anyhow!("{}", e))?; + } + } + _ => {} + } + } + Ok(()) as Result<()> + }, + async move { + while let Some((handshake, mc_stream, addr)) = exposer_handle.recv().await { + if let Some(channel_id) = + get_available_channel(channel_map.read().await.keys().copied().collect()) + { + task::spawn(handle_sent_connection( + handshake, + mc_stream, + addr, + sender.clone(), + channel_id, + )); + } + } + Ok(()) as Result<()> + } + )?; + Ok(()) +} + +async fn handle_sent_connection( + handshake: Handshake, + mut mc_stream: TcpStream, + addr: SocketAddr, + send: UnboundedSender, + channel_id: u8, +) -> Result<()> { + let mut handle = ChannelHandle::new(channel_id, addr, send.clone()).await?; + let mut buf = vec![]; + buf.write_varint(0).await?; + buf.write_varint(handshake.protocol_version).await?; + buf.write_string(&handshake.server_address).await?; + buf.write_all(&handshake.server_port.to_be_bytes()).await?; + buf.write_varint(handshake.next_state as i32).await?; + let mut len_buf = vec![channel_id]; + len_buf.write_varint(buf.len() as i32).await?; + len_buf.append(&mut buf); + send.send(WebsocketConnectionMessage::Data(len_buf)).map_err(|e| anyhow::anyhow!("{}", e))?; + let (mut read, mut write) = mc_stream.split(); + tokio::select!(_ = + #[allow(unreachable_code)] + async move { + let mut buf = [0u8; 1024]; + loop { + read.readable().await?; + let len = read.read(&mut buf).await?; + if len == 0 { + continue; + } + let mut packet = Vec::from(&buf[..len]); + packet.insert(0, channel_id); + send.send(WebsocketConnectionMessage::Data(packet)).map_err(|e| anyhow::anyhow!("{}", e))?; + } + Ok(()) as anyhow::Result<()> + } => {}, + _ = async move { + while let Some(message) = handle.recv().await { + match message { + MinecraftConnectionMessage::Data(buf) => { + write.write_all(&buf).await?; + } + MinecraftConnectionMessage::Close => { + return Ok(()) + } + } + } + Ok(()) as anyhow::Result<()> + } => {} + ); + mc_stream.shutdown().await?; + Ok(()) +} + +async fn accept_mc_connection(mut stream: TcpStream) -> Result<()> { + let addr = stream.peer_addr()?; + info!("New Minecraft connection: {}", addr); + + let packet = stream.read_packet().await; + + if let Err(NettyReadError::LegacyServerListPing) = packet { + stream + .write_all(include_bytes!("./legacy_serverlistping_response.bin")) + .await?; + return Ok(()); + } + + let packet = packet?; + + let handshake = Handshake::new(&packet).await?; + if let Some(sender) = EXPOSER_MAP.read().await.get(&handshake.server_address) { + sender.send((handshake, stream, addr)).map_err(|e| anyhow::anyhow!("{}", e))?; + } else { + match handshake.next_state { + HandshakeType::Status => { + let mut buf = vec![]; + buf.write_varint(0).await?; + buf.write_string(include_str!("./serverlistping_response.json")) + .await?; + stream.write_varint(buf.len() as i32).await?; + stream.write_all(&buf).await?; + } + HandshakeType::Login => { + let _ = stream.read_packet().await?; + let mut buf = vec![]; + buf.write_varint(0).await?; + buf.write_string(include_str!("./disconnect_response.json")) + .await?; + stream.write_varint(buf.len() as i32).await?; + stream.write_all(&buf).await?; + } + } + } + Ok(()) +} + +const ID_ALPHABET: [char; 36] = [ + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', +]; +const ID_LENGTH: usize = 8; + +fn get_random_domain() -> String { + format!( + "{}.{}", + nanoid!(ID_LENGTH, &ID_ALPHABET), + BASE_DOMAIN.as_str() + ) +} + +fn get_available_channel(used: Vec) -> Option { + (0u8..=255).find(|&i| !used.contains(&i)) +} diff --git a/server/src/netty.rs b/server/src/netty.rs new file mode 100644 index 0000000..334e526 --- /dev/null +++ b/server/src/netty.rs @@ -0,0 +1,234 @@ +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use async_trait::async_trait; +use log::{error}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum NettyReadError { + #[error("{0}")] + IoError(std::io::Error), + #[error("Was not a netty packet, but a Legacy ServerListPing")] + LegacyServerListPing, +} + +impl From for NettyReadError { + fn from(value: std::io::Error) -> Self { + Self::IoError(value) + } +} + +impl From for NettyReadError { + fn from(value: std::io::ErrorKind) -> Self { + Self::IoError(value.into()) + } +} + +#[async_trait] +pub trait ReadExtNetty: AsyncReadExt + Unpin { + async fn read_bool(&mut self) -> Result { + let mut buf = [0u8]; + self.read_exact(&mut buf).await?; + match buf[0] { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(std::io::ErrorKind::InvalidData.into()), + } + } + + async fn read_byte(&mut self) -> Result { + let mut buf = [0u8]; + self.read_exact(&mut buf).await?; + Ok(i8::from_be_bytes(buf)) + } + + async fn read_unsigned_byte(&mut self) -> Result { + let mut buf = [0u8]; + self.read_exact(&mut buf).await?; + Ok(buf[0]) + } + + async fn read_short(&mut self) -> Result { + let mut buf = [0u8; 2]; + self.read_exact(&mut buf).await?; + Ok(i16::from_be_bytes(buf)) + } + + async fn read_unsigned_short(&mut self) -> Result { + let mut buf = [0u8; 2]; + self.read_exact(&mut buf).await?; + Ok(u16::from_be_bytes(buf)) + } + + async fn read_int(&mut self) -> Result { + let mut buf = [0u8; 4]; + self.read_exact(&mut buf).await?; + Ok(i32::from_be_bytes(buf)) + } + + async fn read_long(&mut self) -> Result { + let mut buf = [0u8; 8]; + self.read_exact(&mut buf).await?; + Ok(i64::from_be_bytes(buf)) + } + + async fn read_float(&mut self) -> Result { + let mut buf = [0u8; 4]; + self.read_exact(&mut buf).await?; + Ok(f32::from_be_bytes(buf)) + } + + async fn read_double(&mut self) -> Result { + let mut buf = [0u8; 8]; + self.read_exact(&mut buf).await?; + Ok(f64::from_be_bytes(buf)) + } + + async fn read_string(&mut self) -> Result { + let len = self.read_varint().await?; + let mut buf = vec![0u8; len as usize]; + self.read_exact(&mut buf).await?; + String::from_utf8(buf).map_err(|_| std::io::ErrorKind::InvalidData.into()) + } + + + async fn read_varint(&mut self) -> Result { + let mut res = 0i32; + for i in 0..5 { + let part = self.read_unsigned_byte().await?; + res |= (part as i32 & 0x7F) << (7 * i); + if part & 0x80 == 0 { + return Ok(res); + } + } + error!("Varint is invalid"); + Err(std::io::ErrorKind::InvalidData.into()) + } + + // async fn read_varint(&mut self) -> Result { + // let mut value = 0i32; + // let mut buf = [0u8; 1]; + // let mut pos = 0u8; + // loop { + // self.read_exact(&mut buf).await?; + // println!("{}", buf[0]); + // value |= ((buf[0] & 0b01111111) << pos) as i32; + // if (buf[0] & 0b10000000) == 0 { + // break; + // }; + // pos += 7; + // if pos >= 32 { + // return Err(std::io::ErrorKind::InvalidData.into()); + // }; + // } + // Ok(value) + // } + + async fn read_varlong(&mut self) -> Result { + let mut value = 0i64; + let mut buf = [0u8; 1]; + let mut position = 0u8; + loop { + self.read_exact(&mut buf).await?; + value |= ((buf[0] & 0b01111111) << position) as i64; + if (buf[0] & 0b10000000) == 0 { + break; + }; + position += 7; + if position >= 64 { + return Err(std::io::ErrorKind::InvalidData.into()); + }; + } + Ok(value) + } + + async fn read_vec3(&mut self) -> Result<(i32, i16, i32), NettyReadError> { + let mut buf = [0u8; 8]; + self.read_exact(&mut buf).await?; + let packed = u64::from_be_bytes(buf); + let x: i32 = ((packed & 0xffffffc000000000) >> 38) as _; + let y: i16 = (packed & 0xfff) as _; + let z: i32 = ((packed & 0x3ffffff000) >> 12) as _; + Ok(( + match x { + i32::MIN..=0x1ffffff => x, + 0x2000000.. => x - 0x2000000, + }, + match y { + i16::MIN..=0x7ff => y, + 0x800.. => y - 0x800, + }, + match z { + i32::MIN..=0x1ffffff => z, + 0x2000000.. => z - 0x2000000, + }, + )) + } + + async fn read_uuid(&mut self) -> Result { + let mut buf = [0u8; 16]; + self.read_exact(&mut buf).await?; + Ok(u128::from_be_bytes(buf)) + } + + async fn read_packet(&mut self) -> Result, NettyReadError> { + let len = self.read_varint().await?; + let mut buf = vec![0u8; len as usize]; + if len == 254 { + let mut temp = [0u8]; + self.read_exact(&mut temp).await?; + if temp[0] == 0xFA { + // FE 01 FA: Legacy ServerListPing + return Err(NettyReadError::LegacyServerListPing); + } + buf[0] = temp[0]; + self.read_exact(&mut buf[1..]).await?; + } else { + self.read_exact(&mut buf).await?; + } + Ok(buf) + } + + // fn read_packet_compressed(&mut self) -> Result, NettyReadError> { + // let len = self.read_varint()?; + // let len_decompressed = self.read_varint()?; + // let mut buf = vec![0u8; len as usize]; + // self.read_exact(&mut buf)?; + // if len_decompressed == 0 { + // return Ok(buf); + // } + // let mut buf_decompressed = vec![0u8; len_decompressed as usize]; + // if flate2::Decompress::new(true) + // .decompress(&buf, &mut buf_decompressed, flate2::FlushDecompress::Finish) + // .is_err() + // { + // return Err(std::io::ErrorKind::InvalidData.into()); + // }; + // Ok(buf_decompressed) + // } +} + +impl ReadExtNetty for T {} + +#[async_trait] +pub trait WriteExtNetty: AsyncWriteExt + Unpin { + async fn write_varint(&mut self, mut val: i32) -> std::io::Result<()> { + for _ in 0..5 { + if val & !0x7F == 0 { + self.write_all(&[val as u8]).await?; + return Ok(()); + } + self.write_all(&[(val & 0x7F | 0x80) as u8]).await?; + val >>= 7; + } + Err(std::io::ErrorKind::InvalidData.into()) + } + + async fn write_string(&mut self, s: &str) -> std::io::Result<()> { + self.write_varint(s.len() as i32).await?; + self.write_all(s.as_bytes()).await?; + Ok(()) + } +} + +impl WriteExtNetty for T {} diff --git a/server/src/serverlistping_response.json b/server/src/serverlistping_response.json new file mode 100644 index 0000000..77585a6 --- /dev/null +++ b/server/src/serverlistping_response.json @@ -0,0 +1,13 @@ +{ + "version": { + "name": "e4mc", + "protocol": -1 + }, + "players": { + "max": 0, + "online": 0 + }, + "description": { + "text": "Unknown server. Check address and try again." + } +}