diff --git a/Cargo.lock b/Cargo.lock index 604b96d..1eaeb89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,12 +18,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "arc-swap" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" - [[package]] name = "atty" version = "0.2.14" @@ -35,6 +29,22 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bincode" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" +dependencies = [ + "byteorder", + "serde", +] + [[package]] name = "bindgen" version = "0.55.1" @@ -46,7 +56,7 @@ dependencies = [ "cfg-if", "clang-sys", "clap", - "env_logger", + "env_logger 0.7.1", "lazy_static", "lazycell", "log", @@ -65,12 +75,24 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + [[package]] name = "bytes" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" +[[package]] +name = "cc" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" + [[package]] name = "cexpr" version = "0.4.0" @@ -115,6 +137,32 @@ dependencies = [ [[package]] name = "client" version = "0.1.0" +dependencies = [ + "env_logger 0.8.1", + "input", + "log", + "net", + "serde", + "structopt", + "tokio", + "toml", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "env_logger" @@ -123,7 +171,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", - "humantime", + "humantime 1.3.0", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54532e3223c5af90a6a757c90b5c5521564b07e5e7a958681bcd2afad421cdcd" +dependencies = [ + "atty", + "humantime 2.0.1", "log", "regex", "termcolor", @@ -135,6 +196,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -152,10 +228,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] -name = "futures-core" -version = "0.3.5" +name = "getrandom" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] [[package]] name = "glob" @@ -163,6 +244,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.15" @@ -181,13 +271,21 @@ dependencies = [ "quick-error", ] +[[package]] +name = "humantime" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" + [[package]] name = "input" version = "0.1.0" dependencies = [ "bindgen", + "cc", "libc", "mio", + "serde", "tokio", ] @@ -266,35 +364,12 @@ dependencies = [ "kernel32-sys", "libc", "log", - "miow 0.2.1", + "miow", "net2", "slab", "winapi 0.2.8", ] -[[package]] -name = "mio-named-pipes" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" -dependencies = [ - "log", - "mio", - "miow 0.3.5", - "winapi 0.3.9", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio", -] - [[package]] name = "miow" version = "0.2.1" @@ -308,13 +383,31 @@ dependencies = [ ] [[package]] -name = "miow" -version = "0.3.5" +name = "native-tls" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" +checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" dependencies = [ - "socket2", - "winapi 0.3.9", + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "net" +version = "0.1.0" +dependencies = [ + "bincode", + "input", + "serde", + "tokio", ] [[package]] @@ -339,13 +432,36 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.13.0" +name = "openssl" +version = "0.10.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" dependencies = [ - "hermit-abi", + "bitflags", + "cfg-if", + "foreign-types", + "lazy_static", "libc", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-sys" +version = "0.9.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] @@ -361,17 +477,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" [[package]] -name = "proc-macro2" -version = "1.0.21" +name = "pkg-config" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "ppv-lite86" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "unicode-xid", + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", ] [[package]] -name = "proto" -version = "0.1.0" +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[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 = "quick-error" @@ -388,6 +536,47 @@ 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 = "redox_syscall" version = "0.1.57" @@ -412,15 +601,89 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + +[[package]] +name = "security-framework" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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 = "server" version = "0.1.0" +dependencies = [ + "env_logger 0.8.1", + "input", + "log", + "native-tls", + "net", + "serde", + "structopt", + "tokio", + "tokio-native-tls", + "toml", +] [[package]] name = "shlex" @@ -428,34 +691,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" -[[package]] -name = "signal-hook-registry" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035" -dependencies = [ - "arc-swap", - "libc", -] - [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -[[package]] -name = "socket2" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "winapi 0.3.9", -] - [[package]] name = "strsim" version = "0.8.0" @@ -463,16 +704,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] -name = "syn" -version = "1.0.41" +name = "structopt" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b" +checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad5de3220ea04da322618ded2c42233d02baca219d6f160a3e9c87cda16c942" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + [[package]] name = "termcolor" version = "1.1.0" @@ -508,20 +787,13 @@ checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd" dependencies = [ "bytes", "fnv", - "futures-core", "iovec", "lazy_static", - "libc", "memchr", "mio", - "mio-named-pipes", - "mio-uds", - "num_cpus", "pin-project-lite", - "signal-hook-registry", "slab", "tokio-macros", - "winapi 0.3.9", ] [[package]] @@ -535,6 +807,31 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd608593a919a8e05a7d1fc6df885e40f6a88d3a70a3a7eff23ff27964eda069" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + [[package]] name = "unicode-width" version = "0.1.8" @@ -547,6 +844,12 @@ 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 = "vec_map" version = "0.8.2" @@ -559,6 +862,12 @@ 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" + [[package]] name = "which" version = "3.1.1" diff --git a/Cargo.toml b/Cargo.toml index e394b97..cdcb7cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["client", "server", "input", "proto"] \ No newline at end of file +members = ["client", "server", "input", "net"] \ No newline at end of file diff --git a/client/src/.gitignore b/client/.gitignore similarity index 100% rename from client/src/.gitignore rename to client/.gitignore diff --git a/client/Cargo.toml b/client/Cargo.toml index 1a747a9..494d593 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -7,3 +7,11 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +tokio = { version = "0.2.22", features = ["macros", "time", "fs", "tcp", "dns"] } +input = { path = "../input" } +net = { path = "../net" } +serde = { version = "1.0.117", features = ["derive"] } +toml = "0.5.7" +structopt = "0.3.20" +log = "0.4.11" +env_logger = "0.8.1" diff --git a/client/src/config.rs b/client/src/config.rs new file mode 100644 index 0000000..3b227cf --- /dev/null +++ b/client/src/config.rs @@ -0,0 +1,56 @@ +use serde::de::{self, Visitor}; +use serde::{Deserialize, Deserializer}; +use std::fmt::{self, Formatter}; + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct Config { + pub server: Server, +} + +pub struct Server { + pub hostname: String, + pub port: u16, +} + +impl<'de> Deserialize<'de> for Server { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(ServerVisitor) + } +} + +struct ServerVisitor; + +impl<'de> Visitor<'de> for ServerVisitor { + type Value = Server; + + fn expecting(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "a server description (hostname:port)") + } + + fn visit_str(self, data: &str) -> Result + where + E: de::Error, + { + let err = || E::custom("Invalid server description"); + + let mut split = data.split(':'); + let hostname = split.next().ok_or_else(err)?; + let port = split + .next() + .and_then(|data| data.parse().ok()) + .ok_or_else(err)?; + + if split.next().is_some() { + return Err(E::custom("Extraneous data")); + } + + Ok(Server { + hostname: hostname.to_owned(), + port, + }) + } +} diff --git a/client/src/main.rs b/client/src/main.rs index e7a11a9..8f25de2 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,3 +1,77 @@ -fn main() { - println!("Hello, world!"); +mod config; + +use config::Config; +use input::EventWriter; +use net::{self, Message, PROTOCOL_VERSION}; +use std::convert::Infallible; +use std::io::{Error, ErrorKind}; +use std::path::PathBuf; +use std::process; +use structopt::StructOpt; +use tokio::fs; +use tokio::net::TcpStream; + +async fn run(server: &str, port: u16) -> Result { + let mut stream = TcpStream::connect((server, port)).await?; + + log::info!("Connected to {}:{}", server, port); + + net::write_version(&mut stream, PROTOCOL_VERSION).await?; + + let version = net::read_version(&mut stream).await?; + if version != PROTOCOL_VERSION { + return Err(Error::new( + ErrorKind::InvalidData, + format!( + "Incompatible protocol version (got {}, expecting {})", + version, PROTOCOL_VERSION + ), + )); + } + + let mut writer = EventWriter::new().await?; + loop { + let message: Message = net::read_message(&mut stream).await?; + match message { + Message::Event(event) => writer.write(event).await?, + Message::KeepAlive => {} + } + } +} + +#[derive(StructOpt)] +#[structopt(name = "rkvm-client", about = "The rkvm client application")] +struct Args { + #[structopt( + help = "Path to configuration file", + default_value = "/etc/rkvm/client.toml" + )] + config_path: PathBuf, +} + +#[tokio::main] +async fn main() { + env_logger::builder().format_timestamp(None).init(); + + let args = Args::from_args(); + let config = match fs::read_to_string(&args.config_path).await { + Ok(config) => config, + Err(err) => { + log::error!("Error loading config: {}", err); + process::exit(1); + } + }; + + let config: Config = match toml::from_str(&config) { + Ok(config) => config, + Err(err) => { + log::error!("Error parsing config: {}", err); + process::exit(1); + } + }; + + if let Err(err) = run(&config.server.hostname, config.server.port).await { + log::error!("Error: {}", err); + process::exit(1); + } } diff --git a/example/client.toml b/example/client.toml new file mode 100644 index 0000000..c74ac72 --- /dev/null +++ b/example/client.toml @@ -0,0 +1 @@ +server = "123.45.67.89:5258" diff --git a/example/server.toml b/example/server.toml new file mode 100644 index 0000000..a7ab6d9 --- /dev/null +++ b/example/server.toml @@ -0,0 +1,2 @@ +listen-address = "0.0.0.0:5258" +switch-keys = [29, 56, 111] \ No newline at end of file diff --git a/input/Cargo.toml b/input/Cargo.toml index 22609ca..f94ace1 100644 --- a/input/Cargo.toml +++ b/input/Cargo.toml @@ -7,9 +7,11 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "0.2.22", features = ["full"] } +tokio = { version = "0.2.22", features = ["fs", "io-util", "io-driver", "sync", "blocking"] } mio = { version = "0.6.22" } libc = "0.2.77" +serde = { version = "1.0.117", features = ["derive"] } [build-dependencies] bindgen = "0.55.1" +cc = "1.0.60" diff --git a/input/build.rs b/input/build.rs index cbd90a3..b3daebd 100644 --- a/input/build.rs +++ b/input/build.rs @@ -1,18 +1,20 @@ use bindgen::{Builder, CargoCallbacks}; +use cc::Build; use std::env; use std::path::PathBuf; fn main() { - println!("cargo:rerun-if-changed=wrapper.h"); + println!("cargo:rerun-if-changed=setup/setup.h"); + println!("cargo:rerun-if-changed=setup/setup.c"); + + Build::new().file("setup/setup.c").compile("setup"); let bindings = Builder::default() - .header("wrapper.h") + .header("setup/setup.h") .parse_callbacks(Box::new(CargoCallbacks)) .generate() .unwrap(); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - bindings - .write_to_file(out_path.join("bindings.rs")) - .unwrap(); + bindings.write_to_file(out_path.join("setup.rs")).unwrap(); } diff --git a/input/setup/setup.c b/input/setup/setup.c new file mode 100644 index 0000000..dc170f9 --- /dev/null +++ b/input/setup/setup.c @@ -0,0 +1,39 @@ +#include +#include + +#include "setup.h" + +#define IOCTL(fd, ...) if (ioctl(fd, __VA_ARGS__) == -1) return 0; + +int setup_write_fd(int fd) { + IOCTL(fd, UI_SET_EVBIT, EV_KEY); + IOCTL(fd, UI_SET_EVBIT, EV_SYN); + IOCTL(fd, UI_SET_EVBIT, EV_REL); + + for (int i = 0; i < KEY_MAX; i++) + IOCTL(fd, UI_SET_KEYBIT, i); + + IOCTL(fd, UI_SET_KEYBIT, BTN_LEFT); + IOCTL(fd, UI_SET_KEYBIT, BTN_RIGHT); + + IOCTL(fd, UI_SET_RELBIT, REL_X); + IOCTL(fd, UI_SET_RELBIT, REL_Y); + IOCTL(fd, UI_SET_RELBIT, REL_WHEEL); + + struct uinput_setup setup; + setup.id.bustype = BUS_USB; + setup.id.vendor = 1; + setup.id.product = 1; + setup.ff_effects_max = 0; + strcpy(setup.name, "kvm"); + + IOCTL(fd, UI_DEV_SETUP, &setup); + IOCTL(fd, UI_DEV_CREATE); + + return 1; +} + +int setup_read_fd(int fd) { + IOCTL(fd, EVIOCGRAB, 1); + return 1; +} \ No newline at end of file diff --git a/input/setup/setup.h b/input/setup/setup.h new file mode 100644 index 0000000..e97e79e --- /dev/null +++ b/input/setup/setup.h @@ -0,0 +1,5 @@ +#include + +int setup_write_fd(int fd); + +int setup_read_fd(int fd); \ No newline at end of file diff --git a/input/src/async_file.rs b/input/src/async_file.rs index 38ef3b9..919cb6d 100644 --- a/input/src/async_file.rs +++ b/input/src/async_file.rs @@ -5,7 +5,6 @@ use mio::{Poll, PollOpt, Ready, Token}; use std::convert::AsRef; use std::convert::TryInto; use std::ffi::CString; -use std::fs::{File, OpenOptions}; use std::io::ErrorKind; use std::io::{Error, Read, Write}; use std::os::unix::ffi::OsStringExt; @@ -32,7 +31,6 @@ impl AsyncFile { let flags = match mode { OpenMode::Read => libc::O_RDONLY, OpenMode::Write => libc::O_WRONLY, - OpenMode::ReadWrite => libc::O_RDWR, }; let fd = unsafe { libc::open(path.as_ptr(), flags | libc::O_NONBLOCK) }; @@ -90,7 +88,6 @@ impl AsyncWrite for AsyncFile { pub enum OpenMode { Read, Write, - ReadWrite, } struct Inner { diff --git a/input/src/bindings.rs b/input/src/bindings.rs deleted file mode 100644 index 792f9ab..0000000 --- a/input/src/bindings.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![allow(warnings)] - -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/input/src/event.rs b/input/src/event.rs index ea6ef23..43a9c4d 100644 --- a/input/src/event.rs +++ b/input/src/event.rs @@ -1,6 +1,83 @@ -#[derive(Clone, Copy, Debug)] +use crate::setup::{self, input_event, timeval}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum Event { - MouseScroll { delta: i8 }, - MouseMove { x_delta: i8, y_delta: i8 }, - Key { up: bool, code: u16 }, + MouseScroll { delta: i32 }, + MouseMove { axis: Axis, delta: i32 }, + Key { direction: Direction, code: u16 }, + Sync, +} + +impl Event { + pub(crate) fn to_raw(&self) -> input_event { + let (type_, code, value) = match *self { + Event::MouseScroll { delta } => (setup::EV_REL as _, setup::REL_WHEEL as _, delta), + Event::MouseMove { + axis: Axis::X, + delta, + } => (setup::EV_REL as _, setup::REL_X as _, delta), + Event::MouseMove { + axis: Axis::Y, + delta, + } => (setup::EV_REL as _, setup::REL_Y as _, delta), + Event::Key { + direction: Direction::Up, + code, + } => (setup::EV_KEY as _, code, 0), + Event::Key { + direction: Direction::Down, + code, + } => (setup::EV_KEY as _, code, 1), + Event::Sync => (setup::EV_SYN as _, 0, 0), + }; + + input_event { + type_, + code, + value, + time: timeval { + tv_sec: 0, + tv_usec: 0, + }, + } + } + + pub(crate) fn from_raw(raw: input_event) -> Option { + let event = match (raw.type_ as _, raw.code as _, raw.value) { + (setup::EV_REL, setup::REL_WHEEL, value) => Event::MouseScroll { delta: value }, + (setup::EV_REL, setup::REL_X, value) => Event::MouseMove { + axis: Axis::X, + delta: value, + }, + (setup::EV_REL, setup::REL_Y, value) => Event::MouseMove { + axis: Axis::Y, + delta: value, + }, + (setup::EV_KEY, code, 0) => Event::Key { + direction: Direction::Up, + code: code as _, + }, + (setup::EV_KEY, code, 1) => Event::Key { + direction: Direction::Down, + code: code as _, + }, + (setup::EV_SYN, _, _) => Event::Sync, + _ => return None, + }; + + Some(event) + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum Axis { + X, + Y, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub enum Direction { + Up, // The key is released. + Down, // The key is pressed. } diff --git a/input/src/event_manager.rs b/input/src/event_manager.rs new file mode 100644 index 0000000..af2cc4c --- /dev/null +++ b/input/src/event_manager.rs @@ -0,0 +1,76 @@ +use crate::event::Event; +use crate::event_reader::{EventReader, OpenError}; +use crate::event_writer::EventWriter; +use crate::setup::input_event; +use std::io::{Error, ErrorKind}; +use tokio::fs; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; + +pub struct EventManager { + writer: EventWriter, + receiver: UnboundedReceiver>, +} + +impl EventManager { + pub async fn new() -> Result { + let (sender, receiver) = mpsc::unbounded_channel(); + let mut read_dir = fs::read_dir("/dev/input").await?; + while let Some(entry) = read_dir.next_entry().await? { + let path = entry.path(); + if path.is_dir() { + continue; + } + + let reader = match EventReader::new(&path).await { + Ok(reader) => reader, + Err(OpenError::NotSupported) => continue, + Err(OpenError::Io(err)) => return Err(err), + }; + let sender = sender.clone(); + + tokio::spawn(handle_events(reader, sender)); + } + + let writer = EventWriter::new().await?; + Ok(EventManager { writer, receiver }) + } + + pub async fn read(&mut self) -> Result { + loop { + let event = self + .receiver + .recv() + .await + .ok_or_else(|| Error::new(ErrorKind::Other, "All devices closed"))??; + if let Some(event) = Event::from_raw(event) { + return Ok(event); + } + + // Not understood. Write it back. + self.writer.write_raw(event).await?; + } + } + + pub async fn write(&mut self, event: Event) -> Result<(), Error> { + self.writer.write(event).await + } +} + +async fn handle_events( + mut reader: EventReader, + sender: UnboundedSender>, +) { + loop { + let result = match reader.read().await { + Ok(event) => sender.send(Ok(event)).is_ok(), + Err(err) => { + let _ = sender.send(Err(err)); + false + } + }; + + if !result { + break; + } + } +} diff --git a/input/src/event_reader.rs b/input/src/event_reader.rs index c909891..af65a65 100644 --- a/input/src/event_reader.rs +++ b/input/src/event_reader.rs @@ -1 +1,43 @@ -pub struct EventReader {} +use crate::async_file::{AsyncFile, OpenMode}; +use crate::setup::{self, input_event}; +use std::io::Error; +use std::mem; +use std::os::unix::io::AsRawFd; +use std::path::Path; +use tokio::io::AsyncReadExt; + +pub(crate) struct EventReader { + file: AsyncFile, +} + +impl EventReader { + pub async fn new(path: &Path) -> Result { + let file = AsyncFile::open(path, OpenMode::Read) + .await + .map_err(OpenError::Io)?; + if unsafe { setup::setup_read_fd(file.as_raw_fd()) == 0 } { + let err = Error::last_os_error(); + if err.raw_os_error() == Some(libc::ENOTTY) { + return Err(OpenError::NotSupported); + } + + return Err(OpenError::Io(err)); + } + + Ok(Self { file }) + } + + pub async fn read(&mut self) -> Result { + let mut buffer = [0u8; mem::size_of::()]; + self.file + .read_exact(&mut buffer) + .await + .map(|_| unsafe { mem::transmute(buffer) }) + } +} + +#[derive(Debug)] +pub enum OpenError { + NotSupported, + Io(Error), +} diff --git a/input/src/event_writer.rs b/input/src/event_writer.rs index 9c46978..4b0373d 100644 --- a/input/src/event_writer.rs +++ b/input/src/event_writer.rs @@ -1,8 +1,10 @@ use crate::async_file::{AsyncFile, OpenMode}; -use crate::bindings; -use libc::c_int; +use crate::event::Event; +use crate::setup::{self, input_event}; use std::io::Error; +use std::mem; use std::os::unix::io::AsRawFd; +use tokio::io::AsyncWriteExt; pub struct EventWriter { file: AsyncFile, @@ -11,22 +13,19 @@ pub struct EventWriter { impl EventWriter { pub async fn new() -> Result { let file = AsyncFile::open("/dev/uinput", OpenMode::Write).await?; - let fd = file.as_raw_fd(); - - for evbit in &[bindings::EV_KEY, bindings::EV_REL] { - // Doesn't work, UI_SET_KEYBIT not found. - // Probably too complicated for bindgen to be able to do something with it. - check_ioctl(unsafe { libc::ioctl(fd, bindings::UI_SET_KEYBIT, evbit) })?; + if unsafe { setup::setup_write_fd(file.as_raw_fd()) == 0 } { + return Err(Error::last_os_error()); } - Ok(EventWriter { file }) - } -} - -fn check_ioctl(ret: c_int) -> Result<(), Error> { - if ret == -1 { - return Err(Error::last_os_error()); + Ok(Self { file }) } - Ok(()) + pub async fn write(&mut self, event: Event) -> Result<(), Error> { + self.write_raw(event.to_raw()).await + } + + pub(crate) async fn write_raw(&mut self, event: input_event) -> Result<(), Error> { + let data: [u8; mem::size_of::()] = unsafe { mem::transmute(event) }; + self.file.write_all(&data).await + } } diff --git a/input/src/lib.rs b/input/src/lib.rs index 81d3232..0afdfae 100644 --- a/input/src/lib.rs +++ b/input/src/lib.rs @@ -1,9 +1,12 @@ +#![deny(warnings)] + mod async_file; -mod bindings; mod event; +mod event_manager; mod event_reader; mod event_writer; +mod setup; -pub use event::Event; -pub use event_reader::EventReader; +pub use event::{Axis, Direction, Event}; +pub use event_manager::EventManager; pub use event_writer::EventWriter; diff --git a/input/src/setup.rs b/input/src/setup.rs new file mode 100644 index 0000000..1c0897c --- /dev/null +++ b/input/src/setup.rs @@ -0,0 +1,3 @@ +#![allow(warnings)] + +include!(concat!(env!("OUT_DIR"), "/setup.rs")); diff --git a/input/wrapper.h b/input/wrapper.h deleted file mode 100644 index d4d1bf4..0000000 --- a/input/wrapper.h +++ /dev/null @@ -1 +0,0 @@ -#include \ No newline at end of file diff --git a/proto/.gitignore b/net/.gitignore similarity index 100% rename from proto/.gitignore rename to net/.gitignore diff --git a/proto/Cargo.toml b/net/Cargo.toml similarity index 57% rename from proto/Cargo.toml rename to net/Cargo.toml index f95808e..8a6528a 100644 --- a/proto/Cargo.toml +++ b/net/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "proto" +name = "net" version = "0.1.0" authors = ["Jan Trefil <8711792+htrefil@users.noreply.github.com>"] edition = "2018" @@ -7,3 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +input = { path = "../input" } +serde = { version = "1.0.117", features = ["derive"] } +bincode = "1.3.1" +tokio = { version = "0.2.22", features = ["io-util"] } diff --git a/net/src/lib.rs b/net/src/lib.rs new file mode 100644 index 0000000..e0b911e --- /dev/null +++ b/net/src/lib.rs @@ -0,0 +1,65 @@ +use input::Event; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use std::io::{Error, ErrorKind}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; + +// Is it bold to assume there won't be more than 65536 protocol versions? +pub const PROTOCOL_VERSION: u16 = 0; + +pub async fn read_version(mut reader: R) -> Result +where + R: AsyncRead + Unpin, +{ + let mut bytes = [0; 2]; + reader.read_exact(&mut bytes).await?; + + Ok(u16::from_le_bytes(bytes)) +} + +pub async fn write_version(mut writer: W, version: u16) -> Result<(), Error> +where + W: AsyncWrite + Unpin, +{ + writer.write_all(&version.to_le_bytes()).await +} + +pub async fn read_message(mut reader: R) -> Result +where + R: AsyncRead + Unpin, +{ + let length = { + let mut bytes = [0; 2]; + reader.read_exact(&mut bytes).await?; + + u16::from_le_bytes(bytes) + }; + + let mut data = vec![0; length as usize]; + reader.read_exact(&mut data).await?; + + bincode::deserialize(&data).map_err(|err| Error::new(ErrorKind::InvalidData, err)) +} + +pub async fn write_message(mut writer: W, message: &Message) -> Result<(), Error> +where + W: AsyncWrite + Unpin, +{ + let data = + bincode::serialize(&message).map_err(|err| Error::new(ErrorKind::InvalidInput, err))?; + let length: u16 = data + .len() + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "Serialized data is too large"))?; + writer.write_all(&length.to_le_bytes()).await?; + writer.write_all(&data).await?; + + Ok(()) +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum Message { + Event(Event), + // Sent only to keep the connection alive. + KeepAlive, +} diff --git a/proto/src/lib.rs b/proto/src/lib.rs deleted file mode 100644 index 8b13789..0000000 --- a/proto/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/server/Cargo.toml b/server/Cargo.toml index be11ec7..b6045f2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -7,3 +7,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +tokio = { version = "0.2.22", features = ["macros", "time", "fs", "tcp"] } +input = { path = "../input" } +net = { path = "../net" } +serde = { version = "1.0.117", features = ["derive"] } +toml = "0.5.7" +structopt = "0.3.20" +log = "0.4.11" +env_logger = "0.8.1" +tokio-native-tls = "0.1.0" +native-tls = "0.2.4" \ No newline at end of file diff --git a/server/src/config.rs b/server/src/config.rs new file mode 100644 index 0000000..8275102 --- /dev/null +++ b/server/src/config.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::net::SocketAddr; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct Config { + pub listen_address: SocketAddr, + pub switch_keys: HashSet, +} diff --git a/server/src/main.rs b/server/src/main.rs index e7a11a9..348f273 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,3 +1,168 @@ -fn main() { - println!("Hello, world!"); +mod config; + +use config::Config; +use input::{Direction, Event, EventManager}; +use net::{self, Message, PROTOCOL_VERSION}; +use std::collections::{HashMap, HashSet}; +use std::convert::Infallible; +use std::io::{Error, ErrorKind}; +use std::net::SocketAddr; +use std::path::PathBuf; +use std::process; +use std::time::Duration; +use structopt::StructOpt; +use tokio::fs; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::net::TcpListener; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use tokio::time; + +async fn handle_connection( + mut stream: T, + mut receiver: UnboundedReceiver, +) -> Result<(), Error> +where + T: AsyncRead + AsyncWrite + Unpin, +{ + net::write_version(&mut stream, PROTOCOL_VERSION).await?; + + let version = net::read_version(&mut stream).await?; + if version != PROTOCOL_VERSION { + return Err(Error::new( + ErrorKind::InvalidData, + format!( + "Incompatible protocol version (got {}, expecting {})", + version, PROTOCOL_VERSION + ), + )); + } + + loop { + let message = match time::timeout(Duration::from_secs(10), receiver.recv()).await { + Ok(Some(message)) => Message::Event(message), + Ok(None) => return Ok(()), + Err(_) => Message::KeepAlive, + }; + + net::write_message(&mut stream, &message).await?; + } +} + +async fn run(listen_address: SocketAddr, switch_keys: &HashSet) -> Result { + let mut listener = TcpListener::bind(listen_address).await?; + + log::info!("Listening on {}", listen_address); + + let (client_sender, mut client_receiver) = mpsc::unbounded_channel(); + tokio::spawn(async move { + loop { + let (stream, address) = match listener.accept().await { + Ok(sa) => sa, + Err(err) => { + let _ = client_sender.send(Err(err)); + return; + } + }; + + let (sender, receiver) = mpsc::unbounded_channel(); + if client_sender.send(Ok(sender)).is_err() { + return; + } + + tokio::spawn(async move { + log::info!("{}: connected", address); + let message = handle_connection(stream, receiver) + .await + .err() + .map(|err| format!(" ({})", err)) + .unwrap_or(String::new()); + log::info!("{}: disconnected{}", address, message); + }); + } + }); + + let mut clients: Vec> = Vec::new(); + let mut current = 0; + let mut manager = EventManager::new().await?; + let mut key_states: HashMap<_, _> = switch_keys + .iter() + .copied() + .map(|key| (key, false)) + .collect(); + loop { + tokio::select! { + event = manager.read() => { + let event = event?; + if let Event::Key { direction, code } = event { + if let Some(state) = key_states.get_mut(&code) { + *state = if direction == Direction::Down { + true + } else { + false + }; + } + } + + if key_states.iter().filter(|(_, state)| **state).count() == key_states.len() { + for (_, state) in &mut key_states { + *state = false; + } + + current = (current + 1) % (clients.len() + 1); + log::info!("Switching to client {}", current); + } + + if current != 0 { + if clients[current - 1].send(event).is_ok() { + continue; + } + + clients.remove(current); + current = 0; + } + + manager.write(event).await?; + } + sender = client_receiver.recv() => { + clients.push(sender.unwrap()?); + } + } + } +} + +#[derive(StructOpt)] +#[structopt(name = "rkvm-server", about = "The rkvm server application")] +struct Args { + #[structopt( + help = "Path to configuration file", + default_value = "/etc/rkvm/server.toml" + )] + config_path: PathBuf, +} + +#[tokio::main] +async fn main() { + env_logger::builder().format_timestamp(None).init(); + + let args = Args::from_args(); + let config = match fs::read_to_string(&args.config_path).await { + Ok(config) => config, + Err(err) => { + log::error!("Error loading config: {}", err); + process::exit(1); + } + }; + + let config: Config = match toml::from_str(&config) { + Ok(config) => config, + Err(err) => { + log::error!("Error parsing config: {}", err); + process::exit(1); + } + }; + + if let Err(err) = run(config.listen_address, &config.switch_keys).await { + log::error!("Error: {}", err); + process::exit(1); + } }