Implement client and server

This commit is contained in:
htrefil 2020-10-23 19:47:57 +02:00
parent 8c55c00a5f
commit 32dc80ddeb
28 changed files with 1070 additions and 126 deletions

473
Cargo.lock generated
View file

@ -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"

View file

@ -1,2 +1,2 @@
[workspace]
members = ["client", "server", "input", "proto"]
members = ["client", "server", "input", "net"]

View file

@ -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"

56
client/src/config.rs Normal file
View file

@ -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<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, data: &str) -> Result<Self::Value, E>
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,
})
}
}

View file

@ -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<Infallible, Error> {
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);
}
}

1
example/client.toml Normal file
View file

@ -0,0 +1 @@
server = "123.45.67.89:5258"

2
example/server.toml Normal file
View file

@ -0,0 +1,2 @@
listen-address = "0.0.0.0:5258"
switch-keys = [29, 56, 111]

View file

@ -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"

View file

@ -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();
}

39
input/setup/setup.c Normal file
View file

@ -0,0 +1,39 @@
#include <sys/ioctl.h>
#include <string.h>
#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;
}

5
input/setup/setup.h Normal file
View file

@ -0,0 +1,5 @@
#include <linux/uinput.h>
int setup_write_fd(int fd);
int setup_read_fd(int fd);

View file

@ -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 {

View file

@ -1,3 +0,0 @@
#![allow(warnings)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

View file

@ -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<Self> {
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.
}

View file

@ -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<Result<input_event, Error>>,
}
impl EventManager {
pub async fn new() -> Result<Self, Error> {
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<Event, Error> {
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<Result<input_event, Error>>,
) {
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;
}
}
}

View file

@ -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<Self, OpenError> {
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<input_event, Error> {
let mut buffer = [0u8; mem::size_of::<input_event>()];
self.file
.read_exact(&mut buffer)
.await
.map(|_| unsafe { mem::transmute(buffer) })
}
}
#[derive(Debug)]
pub enum OpenError {
NotSupported,
Io(Error),
}

View file

@ -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<Self, Error> {
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::<input_event>()] = unsafe { mem::transmute(event) };
self.file.write_all(&data).await
}
}

View file

@ -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;

3
input/src/setup.rs Normal file
View file

@ -0,0 +1,3 @@
#![allow(warnings)]
include!(concat!(env!("OUT_DIR"), "/setup.rs"));

View file

@ -1 +0,0 @@
#include <linux/uinput.h>

View file

View file

@ -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"] }

65
net/src/lib.rs Normal file
View file

@ -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<R>(mut reader: R) -> Result<u16, Error>
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<W>(mut writer: W, version: u16) -> Result<(), Error>
where
W: AsyncWrite + Unpin,
{
writer.write_all(&version.to_le_bytes()).await
}
pub async fn read_message<R>(mut reader: R) -> Result<Message, Error>
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<W>(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,
}

View file

@ -1 +0,0 @@

View file

@ -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"

10
server/src/config.rs Normal file
View file

@ -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<u16>,
}

View file

@ -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<T>(
mut stream: T,
mut receiver: UnboundedReceiver<Event>,
) -> 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<u16>) -> Result<Infallible, Error> {
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<UnboundedSender<Event>> = 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);
}
}