commit 0d69c204bb721e6abac49a6b60215c67158ab952 Author: Paul Zinselmeyer Date: Thu Jun 20 16:57:42 2024 +0200 init 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..c4553b2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,981 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce34de545ad29bcc00cb1b87a94c132256dcf83aa7eeb9674482568405a6ff0a" +dependencies = [ + "alsa-sys", + "bitflags 2.4.2", + "libc", + "nix", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "audiopus_sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62314a1546a2064e033665d658e88c620a62904be945f8147e6b16c3db9f8651" +dependencies = [ + "cmake", + "log", + "pkg-config", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags 2.4.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6100bc57b6209840798d95cb2775684849d332f7bd788db2a8c8caf7ef82a41a" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "libspa" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0434617020ddca18b86067912970c55410ca654cdafd775480322f50b857a8c4" +dependencies = [ + "bitflags 2.4.2", + "cc", + "convert_case", + "cookie-factory", + "libc", + "libspa-sys", + "nix", + "nom", + "system-deps", +] + +[[package]] +name = "libspa-sys" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e70ca3f3e70f858ef363046d06178c427b4e0b63d210c95fd87d752679d345" +dependencies = [ + "bindgen", + "cc", + "system-deps", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "mumble-foo" +version = "0.1.0" +dependencies = [ + "alsa", + "anyhow", + "opus", + "pipewire", + "prost", + "prost-build", + "rustls", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opus" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6526409b274a7e98e55ff59d96aafd38e6cd34d46b7dbbc32ce126dffcd75e8e" +dependencies = [ + "audiopus_sys", + "libc", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pipewire" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d009c8dd65e890b515a71950f7e4c801523b8894ff33863a40830bf762e9e9" +dependencies = [ + "anyhow", + "bitflags 2.4.2", + "libc", + "libspa", + "libspa-sys", + "nix", + "once_cell", + "pipewire-sys", + "thiserror", +] + +[[package]] +name = "pipewire-sys" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890c084e7b737246cb4799c86b71a0e4da536031ff7473dd639eba9f95039f64" +dependencies = [ + "bindgen", + "libspa-sys", + "system-deps", +] + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" + +[[package]] +name = "rustls-webpki" +version = "0.102.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winnow" +version = "0.5.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +dependencies = [ + "memchr", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4f45e78 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mumble-foo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +alsa = "0.8.1" +pipewire = "0.7.2" +opus = "0.3.0" +anyhow = "1.0.79" +rustls = "0.22.2" +prost = "0.12.3" + +[build-dependencies] +prost-build = "0.12.3" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..b6729e5 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +use std::io::Result; +fn main() -> Result<()> { + prost_build::compile_protos(&["proto/Mumble.proto"], &["proto/"])?; + Ok(()) +} diff --git a/proto/Mumble.proto b/proto/Mumble.proto new file mode 100644 index 0000000..c3a23f2 --- /dev/null +++ b/proto/Mumble.proto @@ -0,0 +1,621 @@ +// Copyright 2009-2023 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +syntax = "proto2"; + +package MumbleProto; + +option optimize_for = SPEED; + +message Version { + // Legacy version number format. + optional uint32 version_v1 = 1; + // New version number format. + // Necessary since patch level may exceed 255. (See https://github.com/mumble-voip/mumble/issues/5827) + optional uint64 version_v2 = 5; + + // Client release name. + optional string release = 2; + // Client OS name. + optional string os = 3; + // Client OS version. + optional string os_version = 4; +} + +// Not used. Not even for tunneling UDP through TCP. +message UDPTunnel { + // Not used. + required bytes packet = 1; +} + +// Used by the client to send the authentication credentials to the server. +message Authenticate { + // UTF-8 encoded username. + optional string username = 1; + // Server or user password. + optional string password = 2; + // Additional access tokens for server ACL groups. + repeated string tokens = 3; + // A list of CELT bitstream version constants supported by the client. + repeated int32 celt_versions = 4; + optional bool opus = 5 [default = false]; + // 0 = REGULAR, 1 = BOT + optional int32 client_type = 6 [default = 0]; +} + +// Sent by the client to notify the server that the client is still alive. +// Server must reply to the packet with the same timestamp and its own +// good/late/lost/resync numbers. None of the fields is strictly required. +message Ping { + // Client timestamp. Server should not attempt to decode. + optional uint64 timestamp = 1; + // The amount of good packets received. + optional uint32 good = 2; + // The amount of late packets received. + optional uint32 late = 3; + // The amount of packets never received. + optional uint32 lost = 4; + // The amount of nonce resyncs. + optional uint32 resync = 5; + // The total amount of UDP packets received. + optional uint32 udp_packets = 6; + // The total amount of TCP packets received. + optional uint32 tcp_packets = 7; + // UDP ping average. + optional float udp_ping_avg = 8; + // UDP ping variance. + optional float udp_ping_var = 9; + // TCP ping average. + optional float tcp_ping_avg = 10; + // TCP ping variance. + optional float tcp_ping_var = 11; +} + +// Sent by the server when it rejects the user connection. +message Reject { + enum RejectType { + // The rejection reason is unknown (details should be available + // in Reject.reason). + None = 0; + // The client attempted to connect with an incompatible version. + WrongVersion = 1; + // The user name supplied by the client was invalid. + InvalidUsername = 2; + // The client attempted to authenticate as a user with a password but it + // was wrong. + WrongUserPW = 3; + // The client attempted to connect to a passworded server but the password + // was wrong. + WrongServerPW = 4; + // Supplied username is already in use. + UsernameInUse = 5; + // Server is currently full and cannot accept more users. + ServerFull = 6; + // The user did not provide a certificate but one is required. + NoCertificate = 7; + AuthenticatorFail = 8; + } + // Rejection type. + optional RejectType type = 1; + // Human readable rejection reason. + optional string reason = 2; +} + +// ServerSync message is sent by the server when it has authenticated the user +// and finished synchronizing the server state. +message ServerSync { + // The session of the current user. + optional uint32 session = 1; + // Maximum bandwidth that the user should use. + optional uint32 max_bandwidth = 2; + // Server welcome text. + optional string welcome_text = 3; + // Current user permissions in the root channel. + // Note: The permissions data type usually is uin32 (e.g. in PermissionQuery and PermissionDenied messages). Here + // it is uint64 because of an oversight in the past. Nonetheless it should never exceed the uin32 range. + // See also: https://github.com/mumble-voip/mumble/issues/5139 + optional uint64 permissions = 4; +} + +// Sent by the client when it wants a channel removed. Sent by the server when +// a channel has been removed and clients should be notified. +message ChannelRemove { + required uint32 channel_id = 1; +} + +// Used to communicate channel properties between the client and the server. +// Sent by the server during the login process or when channel properties are +// updated. Client may use this message to update said channel properties. +message ChannelState { + // Unique ID for the channel within the server. + optional uint32 channel_id = 1; + // channel_id of the parent channel. + optional uint32 parent = 2; + // UTF-8 encoded channel name. + optional string name = 3; + // A collection of channel id values of the linked channels. Absent during + // the first channel listing. + repeated uint32 links = 4; + // UTF-8 encoded channel description. Only if the description is less than + // 128 bytes + optional string description = 5; + // A collection of channel_id values that should be added to links. + repeated uint32 links_add = 6; + // A collection of channel_id values that should be removed from links. + repeated uint32 links_remove = 7; + // True if the channel is temporary. + optional bool temporary = 8 [default = false]; + // Position weight to tweak the channel position in the channel list. + optional int32 position = 9 [default = 0]; + // SHA1 hash of the description if the description is 128 bytes or more. + optional bytes description_hash = 10; + // Maximum number of users allowed in the channel. If this value is zero, + // the maximum number of users allowed in the channel is given by the + // server's "usersperchannel" setting. + optional uint32 max_users = 11; + // Whether this channel has enter restrictions (ACL denying ENTER) set + optional bool is_enter_restricted = 12; + // Whether the receiver of this msg is considered to be able to enter this channel + optional bool can_enter = 13; +} + +// Used to communicate user leaving or being kicked. May be sent by the client +// when it attempts to kick a user. Sent by the server when it informs the +// clients that a user is not present anymore. +message UserRemove { + // The user who is being kicked, identified by their session, not present + // when no one is being kicked. + required uint32 session = 1; + // The user who initiated the removal. Either the user who performs the kick + // or the user who is currently leaving. + optional uint32 actor = 2; + // Reason for the kick, stored as the ban reason if the user is banned. + optional string reason = 3; + // True if the kick should result in a ban. + optional bool ban = 4; +} + +// Sent by the server when it communicates new and changed users to client. +// First seen during login procedure. May be sent by the client when it wishes +// to alter its state. +message UserState { + message VolumeAdjustment { + optional uint32 listening_channel = 1; + optional float volume_adjustment = 2; + } + + // Unique user session ID of the user whose state this is, may change on + // reconnect. + optional uint32 session = 1; + // The session of the user who is updating this user. + optional uint32 actor = 2; + // User name, UTF-8 encoded. + optional string name = 3; + // Registered user ID if the user is registered. + optional uint32 user_id = 4; + // Channel on which the user is. + optional uint32 channel_id = 5; + // True if the user is muted by admin. + optional bool mute = 6; + // True if the user is deafened by admin. + optional bool deaf = 7; + // True if the user has been suppressed from talking by a reason other than + // being muted. + optional bool suppress = 8; + // True if the user has muted self. + optional bool self_mute = 9; + // True if the user has deafened self. + optional bool self_deaf = 10; + // User image if it is less than 128 bytes. + optional bytes texture = 11; + // The positional audio plugin identifier. + // Positional audio information is only sent to users who share + // identical plugin contexts. + // + // This value is not transmitted to clients. + optional bytes plugin_context = 12; + // The user's plugin-specific identity. + // This value is not transmitted to clients. + optional string plugin_identity = 13; + // User comment if it is less than 128 bytes. + optional string comment = 14; + // The hash of the user certificate. + optional string hash = 15; + // SHA1 hash of the user comment if it 128 bytes or more. + optional bytes comment_hash = 16; + // SHA1 hash of the user picture if it 128 bytes or more. + optional bytes texture_hash = 17; + // True if the user is a priority speaker. + optional bool priority_speaker = 18; + // True if the user is currently recording. + optional bool recording = 19; + // A list of temporary access tokens to be respected when processing this request. + repeated string temporary_access_tokens = 20; + // A list of channels the user wants to start listening to. + repeated uint32 listening_channel_add = 21; + // a list of channels the user does no longer want to listen to. + repeated uint32 listening_channel_remove = 22; + // A list of volume adjustments the user has applied to listeners + repeated VolumeAdjustment listening_volume_adjustment = 23; +} + +// Relays information on the bans. The client may send the BanList message to +// either modify the list of bans or query them from the server. The server +// sends this list only after a client queries for it. +message BanList { + message BanEntry { + // Banned IP address. + required bytes address = 1; + // The length of the subnet mask for the ban. + required uint32 mask = 2; + // User name for identification purposes (does not affect the ban). + optional string name = 3; + // The certificate hash of the banned user. + optional string hash = 4; + // Reason for the ban (does not affect the ban). + optional string reason = 5; + // Ban start time. + optional string start = 6; + // Ban duration in seconds. + optional uint32 duration = 7; + } + // List of ban entries currently in place. + repeated BanEntry bans = 1; + // True if the server should return the list, false if it should replace old + // ban list with the one provided. + optional bool query = 2 [default = false]; +} + +// Used to send and broadcast text messages. +message TextMessage { + // The message sender, identified by its session. + optional uint32 actor = 1; + // Target users for the message, identified by their session. + repeated uint32 session = 2; + // The channels to which the message is sent, identified by their + // channel_ids. + repeated uint32 channel_id = 3; + // The root channels when sending message recursively to several channels, + // identified by their channel_ids. + repeated uint32 tree_id = 4; + // The UTF-8 encoded message. May be HTML if the server allows. + required string message = 5; +} + +message PermissionDenied { + enum DenyType { + // Operation denied for other reason, see reason field. + Text = 0; + // Permissions were denied. + Permission = 1; + // Cannot modify SuperUser. + SuperUser = 2; + // Invalid channel name. + ChannelName = 3; + // Text message too long. + TextTooLong = 4; + // The flux capacitor was spelled wrong. + H9K = 5; + // Operation not permitted in temporary channel. + TemporaryChannel = 6; + // Operation requires certificate. + MissingCertificate = 7; + // Invalid username. + UserName = 8; + // Channel is full. + ChannelFull = 9; + // Channels are nested too deeply. + NestingLimit = 10; + // Maximum channel count reached. + ChannelCountLimit = 11; + // Amount of listener objects for this channel has been reached + ChannelListenerLimit = 12; + // Amount of listener proxies for the user has been reached + UserListenerLimit = 13; + } + // The denied permission when type is Permission. + optional uint32 permission = 1; + // channel_id for the channel where the permission was denied when type is + // Permission. + optional uint32 channel_id = 2; + // The user who was denied permissions, identified by session. + optional uint32 session = 3; + // Textual reason for the denial. + optional string reason = 4; + // Type of the denial. + optional DenyType type = 5; + // The name that is invalid when type is UserName. + optional string name = 6; +} + +message ACL { + message ChanGroup { + // Name of the channel group, UTF-8 encoded. + required string name = 1; + // True if the group has been inherited from the parent (Read only). + optional bool inherited = 2 [default = true]; + // True if the group members are inherited. + optional bool inherit = 3 [default = true]; + // True if the group can be inherited by sub channels. + optional bool inheritable = 4 [default = true]; + // Users explicitly included in this group, identified by user_id. + repeated uint32 add = 5; + // Users explicitly removed from this group in this channel if the group + // has been inherited, identified by user_id. + repeated uint32 remove = 6; + // Users inherited, identified by user_id. + repeated uint32 inherited_members = 7; + } + message ChanACL { + // True if this ACL applies to the current channel. + optional bool apply_here = 1 [default = true]; + // True if this ACL applies to the sub channels. + optional bool apply_subs = 2 [default = true]; + // True if the ACL has been inherited from the parent. + optional bool inherited = 3 [default = true]; + // ID of the user that is affected by this ACL. + optional uint32 user_id = 4; + // ID of the group that is affected by this ACL. + optional string group = 5; + // Bit flag field of the permissions granted by this ACL. + optional uint32 grant = 6; + // Bit flag field of the permissions denied by this ACL. + optional uint32 deny = 7; + } + // Channel ID of the channel this message affects. + required uint32 channel_id = 1; + // True if the channel inherits its parent's ACLs. + optional bool inherit_acls = 2 [default = true]; + // User group specifications. + repeated ChanGroup groups = 3; + // ACL specifications. + repeated ChanACL acls = 4; + // True if the message is a query for ACLs instead of setting them. + optional bool query = 5 [default = false]; +} + +// Client may use this message to refresh its registered user information. The +// client should fill the IDs or Names of the users it wants to refresh. The +// server fills the missing parts and sends the message back. +message QueryUsers { + // user_ids. + repeated uint32 ids = 1; + // User names in the same order as ids. + repeated string names = 2; +} + +// Used to initialize and resync the UDP encryption. Either side may request a +// resync by sending the message without any values filled. The resync is +// performed by sending the message with only the client or server nonce +// filled. +message CryptSetup { + // Encryption key. + optional bytes key = 1; + // Client nonce. + optional bytes client_nonce = 2; + // Server nonce. + optional bytes server_nonce = 3; +} + +// Used to add or remove custom context menu item on client-side. +message ContextActionModify { + enum Context { + // Action is applicable to the server. + Server = 0x01; + // Action can target a Channel. + Channel = 0x02; + // Action can target a User. + User = 0x04; + } + enum Operation { + Add = 0; + Remove = 1; + } + // The action identifier. Used later to initiate an action. + required string action = 1; + // The display name of the action. + optional string text = 2; + // Context bit flags defining where the action should be displayed. + // Flags can be OR-ed to combine different types. + optional uint32 context = 3; + // Choose either to add or to remove the context action. + // Note: This field only exists after Mumble 1.2.4-beta1 release. + // The message will be recognized as Add regardless of this field + // before said release. + optional Operation operation = 4; +} + +// Sent by the client when it wants to initiate a Context action. +message ContextAction { + // The target User for the action, identified by session. + optional uint32 session = 1; + // The target Channel for the action, identified by channel_id. + optional uint32 channel_id = 2; + // The action that should be executed. + required string action = 3; +} + +// Lists the registered users. +message UserList { + message User { + // Registered user ID. + required uint32 user_id = 1; + // Registered user name. + optional string name = 2; + optional string last_seen = 3; + optional uint32 last_channel = 4; + } + // A list of registered users. + repeated User users = 1; +} + +// Sent by the client when it wants to register or clear whisper targets. +// +// Note: The first available target ID is 1 as 0 is reserved for normal +// talking. Maximum target ID is 30. +message VoiceTarget { + message Target { + // Users that are included as targets. + repeated uint32 session = 1; + // Channel that is included as a target. + optional uint32 channel_id = 2; + // ACL group that is included as a target. + optional string group = 3; + // True if the voice should follow links from the specified channel. + optional bool links = 4 [default = false]; + // True if the voice should also be sent to children of the specific + // channel. + optional bool children = 5 [default = false]; + } + // Voice target ID. + optional uint32 id = 1; + // The receivers that this voice target includes. + repeated Target targets = 2; +} + +// Sent by the client when it wants permissions for a certain channel. Sent by +// the server when it replies to the query or wants the user to resync all +// channel permissions. +message PermissionQuery { + // channel_id of the channel for which the permissions are queried. + optional uint32 channel_id = 1; + // Channel permissions. + optional uint32 permissions = 2; + // True if the client should drop its current permission information for all + // channels. + optional bool flush = 3 [default = false]; +} + +// Sent by the server to notify the users of the version of the CELT codec they +// should use. This may change during the connection when new users join. +message CodecVersion { + // The version of the CELT Alpha codec. + required int32 alpha = 1; + // The version of the CELT Beta codec. + required int32 beta = 2; + // True if the user should prefer Alpha over Beta. + required bool prefer_alpha = 3 [default = true]; + optional bool opus = 4 [default = false]; +} + +// Used to communicate user stats between the server and clients. +message UserStats { + message Stats { + // The amount of good packets received. + optional uint32 good = 1; + // The amount of late packets received. + optional uint32 late = 2; + // The amount of packets never received. + optional uint32 lost = 3; + // The amount of nonce resyncs. + optional uint32 resync = 4; + } + + // User whose stats these are. + optional uint32 session = 1; + // True if the message contains only mutable stats (packets, ping). + optional bool stats_only = 2 [default = false]; + // Full user certificate chain of the user certificate in DER format. + repeated bytes certificates = 3; + // Packet statistics for packets received from the client. + optional Stats from_client = 4; + // Packet statistics for packets sent by the server. + optional Stats from_server = 5; + + // Amount of UDP packets sent. + optional uint32 udp_packets = 6; + // Amount of TCP packets sent. + optional uint32 tcp_packets = 7; + // UDP ping average. + optional float udp_ping_avg = 8; + // UDP ping variance. + optional float udp_ping_var = 9; + // TCP ping average. + optional float tcp_ping_avg = 10; + // TCP ping variance. + optional float tcp_ping_var = 11; + + // Client version. + optional Version version = 12; + // A list of CELT bitstream version constants supported by the client of this + // user. + repeated int32 celt_versions = 13; + // Client IP address. + optional bytes address = 14; + // Bandwidth used by this client. + optional uint32 bandwidth = 15; + // Connection duration. + optional uint32 onlinesecs = 16; + // Duration since last activity. + optional uint32 idlesecs = 17; + // True if the user has a strong certificate. + optional bool strong_certificate = 18 [default = false]; + optional bool opus = 19 [default = false]; +} + +// Used by the client to request binary data from the server. By default large +// comments or textures are not sent within standard messages but instead the +// hash is. If the client does not recognize the hash it may request the +// resource when it needs it. The client does so by sending a RequestBlob +// message with the correct fields filled with the user sessions or channel_ids +// it wants to receive. The server replies to this by sending a new +// UserState/ChannelState message with the resources filled even if they would +// normally be transmitted as hashes. +message RequestBlob { + // sessions of the requested UserState textures. + repeated uint32 session_texture = 1; + // sessions of the requested UserState comments. + repeated uint32 session_comment = 2; + // channel_ids of the requested ChannelState descriptions. + repeated uint32 channel_description = 3; +} + +// Sent by the server when it informs the clients on server configuration +// details. +message ServerConfig { + // The maximum bandwidth the clients should use. + optional uint32 max_bandwidth = 1; + // Server welcome text. + optional string welcome_text = 2; + // True if the server allows HTML. + optional bool allow_html = 3; + // Maximum text message length. + optional uint32 message_length = 4; + // Maximum image message length. + optional uint32 image_message_length = 5; + // The maximum number of users allowed on the server. + optional uint32 max_users = 6; + // Whether using Mumble's recording feature is allowed on the server + optional bool recording_allowed = 7; +} + +// Sent by the server to inform the clients of suggested client configuration +// specified by the server administrator. +message SuggestConfig { + // Suggested client version in the legacy format. + optional uint32 version_v1 = 1; + // Suggested client version in the new format. + // Necessary since patch level may exceed 255. (See https://github.com/mumble-voip/mumble/issues/5827) + optional uint64 version_v2 = 4; + + // True if the administrator suggests positional audio to be used on this + // server. + optional bool positional = 2; + // True if the administrator suggests push to talk to be used on this server. + optional bool push_to_talk = 3; +} + +// Used to send plugin messages between clients +message PluginDataTransmission { + // The session ID of the client this message was sent from + optional uint32 senderSession = 1; + // The session IDs of the clients that should receive this message + repeated uint32 receiverSessions = 2 [packed = true]; + // The data that is sent + optional bytes data = 3; + // The ID of the sent data. This will be used by plugins to check whether they will + // process it or not + optional string dataID = 4; +} diff --git a/src/audio_io.rs b/src/audio_io.rs new file mode 100644 index 0000000..495a12a --- /dev/null +++ b/src/audio_io.rs @@ -0,0 +1,22 @@ +use std::collections::HashMap; + +use alsa::PCM; + +pub trait AudioIO { + fn play(&self, sender_id: u32, buf: &[i16]); + fn capture(&self, buf: &mut [i16]); +} + +pub struct Alsa { + output_streams: HashMap, +} + +impl AudioIO for Alsa { + fn play(&self, sender_id: u32, buf: &[i16]) { + todo!() + } + + fn capture(&self, buf: &mut [i16]) { + todo!() + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..06a726c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,473 @@ +use std::{ + collections::HashMap, + io::{Cursor, Read, Write}, + net::TcpStream, + sync::Arc, + time::{Duration, Instant}, +}; + +use alsa::{ + pcm::{Access, Format, HwParams, State}, + Direction, ValueOr, PCM, +}; +use audio_io::AudioIO; +use opus::{Channels, Decoder}; +use prost::Message; +use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + internal::msgs::codec::Codec, + ClientConfig, ClientConnection, RootCertStore, SignatureScheme, Stream, +}; + +use crate::mumble::{voice::VoiceData, ControlMessage}; + +mod audio_io; +mod mumble; + +struct Client { + users: HashMap, + user_audio: HashMap, + channels: HashMap, + conn: ClientConnection, + sock: TcpStream, + decoder: Decoder, +} + +#[derive(Clone, Debug)] +struct MumbleUser { + name: String, + channel_id: u32, + mute: bool, + deaf: bool, + suppress: bool, + self_mute: bool, + self_deaf: bool, + priority_speaker: bool, +} + +#[derive(Clone, Debug)] +struct MumbleChannel { + name: String, + parent: Option, + max_users: u32, + is_enter_restricted: bool, + can_enter: bool, +} + +impl Client { + pub fn connect(server: &str) -> anyhow::Result { + let mut root_store = RootCertStore::empty(); + let mut config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + + config + .dangerous() + .set_certificate_verifier(Arc::new(Foo {})); + + let mut conn = ClientConnection::new(Arc::new(config), "localhost".try_into()?)?; + let mut sock = TcpStream::connect(server)?; + let mut tls = Stream::new(&mut conn, &mut sock); + + let msg = ControlMessage::Version(mumble::control::Version { + version_v1: Some(66815), + version_v2: Some(281492175388672), + release: Some("0.1.0".to_string()), + os: Some("RustMumble".to_string()), + os_version: Some("RustMumble".to_string()), + }) + .encode(); + tls.write_all(&msg)?; + + let msg = ControlMessage::Authenticate(mumble::control::Authenticate { + username: Some("rust".to_string()), + password: None, + tokens: vec![], + celt_versions: vec![], + opus: Some(true), + client_type: Some(0), + }) + .encode(); + tls.write_all(&msg)?; + + Ok(Self { + users: HashMap::new(), + user_audio: HashMap::new(), + channels: HashMap::new(), + conn, + sock, + decoder: Decoder::new(48000, Channels::Mono)?, + }) + } + pub fn handle_incoming(&mut self) -> anyhow::Result<()> { + let mut tls = Stream::new(&mut self.conn, &mut self.sock); + match ControlMessage::decode(&mut tls)? { + ControlMessage::UserRemove(u) => { + self.users.remove(&u.session); + self.user_audio.remove(&u.session); + } + ControlMessage::UserState(u) => { + if let Some(user) = self.users.get_mut(&u.session()) { + if let Some(name) = &u.name { + user.name = name.clone(); + } + if let Some(channel_id) = &u.channel_id { + user.channel_id = *channel_id; + } + if let Some(mute) = &u.mute { + user.mute = *mute; + } + if let Some(deaf) = &u.deaf { + user.deaf = *deaf; + } + if let Some(suppress) = &u.suppress { + user.suppress = *suppress; + } + if let Some(self_mute) = &u.self_mute { + user.self_mute = *self_mute; + } + if let Some(self_deaf) = &u.self_deaf { + user.self_deaf = *self_deaf; + } + if let Some(priority_speaker) = &u.priority_speaker { + user.priority_speaker = *priority_speaker; + } + } else { + let user = MumbleUser { + name: u.name().to_string(), + channel_id: u.channel_id(), + mute: u.mute(), + deaf: u.deaf(), + suppress: u.suppress(), + self_mute: u.self_mute(), + self_deaf: u.self_deaf(), + priority_speaker: u.priority_speaker(), + }; + self.users.insert(u.session(), user); + }; + } + ControlMessage::ChannelRemove(c) => { + self.channels.remove(&c.channel_id); + } + ControlMessage::ChannelState(c) => { + if let Some(channel) = self.channels.get_mut(&c.channel_id()) { + if let Some(name) = c.name { + channel.name = name.to_string(); + } + if let Some(parent) = c.parent { + channel.parent = Some(parent); + } + if let Some(max_users) = c.max_users { + channel.max_users = max_users; + } + if let Some(is_enter_restricted) = c.is_enter_restricted { + channel.is_enter_restricted = is_enter_restricted; + } + if let Some(can_enter) = c.can_enter { + channel.can_enter = can_enter; + } + } else { + let channel = MumbleChannel { + name: c.name().to_string(), + parent: c.parent, + max_users: c.max_users(), + is_enter_restricted: c.is_enter_restricted(), + can_enter: c.can_enter(), + }; + self.channels.insert(c.channel_id(), channel); + }; + } + ControlMessage::UdpTunnel(packet) => match packet { + VoiceData::Opus { target, packet } => { + let mut buf: Vec = vec![0_i16; 16 * packet.payload.data.len()]; + let len = + self.decoder + .decode(&packet.payload.data, buf.as_mut_slice(), false)?; + let buf = &buf[..len]; + + let pcm = match self.user_audio.get(&(packet.session_id.0 as u32)) { + Some(pcm) => pcm, + None => { + let pcm = PCM::new("default", Direction::Playback, true).unwrap(); + { + // Set hardware parameters: 44100 Hz / Mono / 16 bit + let hwp = HwParams::any(&pcm).unwrap(); + hwp.set_channels(1).unwrap(); + hwp.set_rate(48000, ValueOr::Nearest).unwrap(); + hwp.set_format(Format::s16()).unwrap(); + hwp.set_access(Access::RWInterleaved).unwrap(); + pcm.hw_params(&hwp).unwrap(); + } + { + let hwp = pcm.hw_params_current().unwrap(); + let swp = pcm.sw_params_current().unwrap(); + swp.set_start_threshold(960 * 10).unwrap(); + dbg!(hwp.get_buffer_size_min()); + pcm.sw_params(&swp).unwrap(); + } + + println!("START"); + self.user_audio.insert(packet.session_id.0 as u32, pcm); + self.user_audio.get(&(packet.session_id.0 as u32)).unwrap() + } + }; + + let io = pcm.io_i16()?; + + dbg!(&io.writei(&buf)); + println!("wrote"); + + if pcm.state() == State::Paused {} + + if packet.payload.last { + println!("STOP"); + pcm.wait(None)?; + } + } + _ => {} + }, + x => { + dbg!(x); + } + } + Ok(()) + } +} + +pub trait endecode { + fn encode(&self) -> Vec; + fn decode(stream: &mut impl Read) -> anyhow::Result + where + Self: Sized; +} + +fn main2() -> anyhow::Result<()> { + // Open default playback device + let pcm = PCM::new("default", Direction::Playback, false).unwrap(); + { + // Set hardware parameters: 44100 Hz / Mono / 16 bit + let hwp = HwParams::any(&pcm).unwrap(); + hwp.set_channels(1).unwrap(); + hwp.set_rate(48000, ValueOr::Nearest).unwrap(); + hwp.set_format(Format::s16()).unwrap(); + hwp.set_access(Access::RWInterleaved).unwrap(); + pcm.hw_params(&hwp).unwrap(); + } + let io = pcm.io_i16().unwrap(); + + let hwp = pcm.hw_params_current().unwrap(); + let swp = pcm.sw_params_current().unwrap(); + swp.set_start_threshold(hwp.get_buffer_size().unwrap()) + .unwrap(); + pcm.sw_params(&swp).unwrap(); + + let pcm2 = PCM::new("default", Direction::Capture, false).unwrap(); + { + // Set hardware parameters: 44100 Hz / Mono / 16 bit + let hwp = HwParams::any(&pcm2).unwrap(); + hwp.set_channels(1).unwrap(); + hwp.set_rate(48000, ValueOr::Nearest).unwrap(); + hwp.set_format(Format::s16()).unwrap(); + hwp.set_access(Access::RWInterleaved).unwrap(); + pcm2.hw_params(&hwp).unwrap(); + } + let io2 = pcm2.io_i16().unwrap(); + + // // Make a sine wave + // let mut buf = [0i16; 512]; + // for (i, a) in buf.iter_mut().enumerate() { + // *a = ((i as f32 * 8.0 * ::std::f32::consts::PI / 128.0).sin() * 8192.0) as i16 + // } + // + // // Play it back for 2 seconds. + // for _ in 0..2 * 8000 / 512 { + // assert_eq!(io.writei(&buf[..]).unwrap(), 512); + // } + // + pcm2.start()?; + + let mut buf = [0i16; 512]; + loop { + let x = io2.readi(&mut buf)?; + + buf.iter_mut().for_each(|x| *x = 2 * *x); + + io.writei(&buf)?; + if pcm.state() != State::Running { + pcm.start().unwrap() + } + } +} + +fn main() -> anyhow::Result<()> { + let mut client = Client::connect("127.0.0.1:64738")?; + loop { + client.handle_incoming(); + } + + let mut root_store = RootCertStore::empty(); + let mut config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + + config + .dangerous() + .set_certificate_verifier(Arc::new(Foo {})); + + let mut conn = ClientConnection::new(Arc::new(config), "localhost".try_into()?)?; + let mut sock = TcpStream::connect("127.0.0.1:64738")?; + let mut tls = Stream::new(&mut conn, &mut sock); + + println!("1"); + + dbg!(ControlMessage::decode(&mut tls)); + + println!("2"); + + let msg = ControlMessage::Version(mumble::control::Version { + version_v1: Some(66815), + version_v2: Some(281492175388672), + release: Some("0.1.0".to_string()), + os: Some("RustMumble".to_string()), + os_version: Some("RustMumble".to_string()), + }) + .encode(); + tls.write_all(&msg)?; + + let msg = ControlMessage::Authenticate(mumble::control::Authenticate { + username: Some("rust".to_string()), + password: None, + tokens: vec![], + celt_versions: vec![], + opus: Some(true), + client_type: Some(0), + }) + .encode(); + tls.write_all(&msg)?; + + let msg = ControlMessage::UserState(mumble::control::UserState { + channel_id: Some(1), + self_mute: Some(true), + ..Default::default() + }) + .encode(); + tls.write_all(&msg)?; + + let mut decoder = Decoder::new(48000, Channels::Mono)?; + + let pcm = PCM::new("default", Direction::Playback, false).unwrap(); + { + // Set hardware parameters: 44100 Hz / Mono / 16 bit + let hwp = HwParams::any(&pcm).unwrap(); + hwp.set_channels(1).unwrap(); + hwp.set_rate(48000, ValueOr::Nearest).unwrap(); + hwp.set_format(Format::s16()).unwrap(); + hwp.set_access(Access::RWInterleaved).unwrap(); + pcm.hw_params(&hwp).unwrap(); + } + let io = pcm.io_i16().unwrap(); + + let hwp = pcm.hw_params_current().unwrap(); + let swp = pcm.sw_params_current().unwrap(); + swp.set_start_threshold(hwp.get_buffer_size().unwrap()) + .unwrap(); + pcm.sw_params(&swp).unwrap(); + + let mut running = false; + + let mut last_ping = Instant::now(); + + loop { + //TODO: non blocking + let cm = ControlMessage::decode(&mut tls)?; + match cm { + ControlMessage::UdpTunnel(x) => match x { + VoiceData::Opus { target, packet } => { + let mut buf: Vec = vec![0_i16; 16 * packet.payload.data.len()]; + let len = decoder.decode(&packet.payload.data, buf.as_mut_slice(), false)?; + let buf = &buf[..len]; + + io.writei(&buf)?; + match pcm.state() { + State::Prepared if running => pcm.start()?, + State::Paused => pcm.pause(false)?, + _ => {} + } + + running = true; + if packet.payload.last { + running = false; + pcm.pause(true)?; + } + } + _ => {} + }, + x => { + dbg!(x); + } + } + if last_ping.elapsed() > Duration::from_secs(20) { + println!("TEST"); + let msg = ControlMessage::Ping(mumble::control::Ping { + ..Default::default() + }) + .encode(); + tls.write_all(&msg)?; + last_ping = Instant::now(); + } + } + + Ok(()) +} + +#[derive(Debug)] +struct Foo {} + +impl ServerCertVerifier for Foo { + fn verify_server_cert( + &self, + end_entity: &rustls::pki_types::CertificateDer<'_>, + intermediates: &[rustls::pki_types::CertificateDer<'_>], + server_name: &rustls::pki_types::ServerName<'_>, + ocsp_response: &[u8], + now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &rustls::pki_types::CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &rustls::pki_types::CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + vec![ + SignatureScheme::RSA_PKCS1_SHA1, + SignatureScheme::ECDSA_SHA1_Legacy, + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::ECDSA_NISTP521_SHA512, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::ED25519, + SignatureScheme::ED448, + ] + } +} diff --git a/src/mumble.rs b/src/mumble.rs new file mode 100644 index 0000000..26e63a6 --- /dev/null +++ b/src/mumble.rs @@ -0,0 +1,340 @@ +use std::io::{Cursor, Read}; + +use prost::Message; + +use crate::endecode; + +use self::voice::VoiceData; + +pub mod control { + include!(concat!(env!("OUT_DIR"), "/mumble_proto.rs")); +} + +pub mod voice { + use std::io::Read; + + use crate::endecode; + + #[derive(Debug)] + pub enum VoiceData { + CeltAlpha { + target: VoiceTarget, + packet: AudioDataPacket, + }, + Ping(u64), + Speex, + CeltBeta, + Opus { + target: VoiceTarget, + packet: AudioDataPacket, + }, + } + + impl endecode for VoiceData { + fn encode(&self) -> Vec { + todo!() + } + fn decode(stream: &mut impl Read) -> anyhow::Result { + let mut header = [0_u8; 1]; + stream.read_exact(&mut header)?; + let header = header[0]; + let target = VoiceTarget::from(header & 0b00011111); + let kind = (header >> 5) & 0b00000111; + Ok(match kind { + 0 => Self::CeltAlpha { + target, + packet: AudioDataPacket::::decode(stream)?, + }, + 1 => { + let mut timestamp = [0_u8; 8]; + stream.read_exact(&mut timestamp)?; + + Self::Ping(u64::from_be_bytes(timestamp)) + } + 2 => Self::Speex, + 3 => Self::CeltBeta, + 4 => Self::Opus { + target, + packet: AudioDataPacket::::decode(stream)?, + }, + _ => todo!(), + }) + } + } + + #[derive(Debug)] + pub enum VoiceTarget { + Normal, + Whisper(u8), + ServerLoopback, + } + + impl From for VoiceTarget { + fn from(value: u8) -> Self { + match value { + 0 => Self::Normal, + 31 => Self::ServerLoopback, + x if x < 32 => Self::Whisper(x), + _ => todo!(), + } + } + } + + #[derive(Debug)] + pub struct AudioDataPacket { + pub session_id: VarI64, + pub seq_nr: VarI64, + pub payload: T, + //position_info: [f32; 3], + } + + impl endecode for AudioDataPacket { + fn encode(&self) -> Vec { + todo!() + } + + fn decode(stream: &mut impl Read) -> anyhow::Result { + Ok(Self { + session_id: VarI64::decode(stream)?, + seq_nr: VarI64::decode(stream)?, + payload: T::decode(stream)?, + // position_info: { + // let mut f1 = [0_u8; 4]; + // //stream.read_exact(&mut f1)?; + // let mut f2 = [0_u8; 4]; + // //stream.read_exact(&mut f2)?; + // let mut f3 = [0_u8; 4]; + // //stream.read_exact(&mut f3)?; + // + // [ + // f32::from_be_bytes(f1), + // f32::from_be_bytes(f2), + // f32::from_be_bytes(f3), + // ] + // }, + }) + } + } + + #[derive(Debug)] + pub struct OpusAudioPacket { + pub last: bool, + pub data: Vec, + } + + impl endecode for OpusAudioPacket { + fn encode(&self) -> Vec { + todo!() + } + + fn decode(stream: &mut impl Read) -> anyhow::Result { + let length = VarI64::decode(stream)?.0; + let last = (length & 0x2000) == 0x2000; + let length = length & 0x1FFF; + let mut data = vec![0_u8; length as usize]; + stream.read_exact(&mut data)?; + Ok(Self { last, data }) + } + } + + #[derive(Debug)] + pub struct CeltAlphaAudioPacket(pub Vec); + + impl endecode for CeltAlphaAudioPacket { + fn encode(&self) -> Vec { + todo!() + } + + fn decode(stream: &mut impl Read) -> anyhow::Result { + let mut length = [0_u8; 1]; + stream.read_exact(&mut length)?; + let length = length[0]; + let continuation = ((length >> 7) & 0b1) == 1; + let length = length & 0b01111111; + + let mut data = vec![0_u8; length as usize]; + stream.read_exact(&mut data)?; + Ok(Self(data)) + } + } + + #[derive(Debug)] + pub struct VarI64(pub i64); + + impl endecode for VarI64 { + fn encode(&self) -> Vec { + let oct = self.0.to_be_bytes(); + match self.0 { + x if x < (1 << 7) && x >= 0 => vec![oct[7] & 0b01111111 + 0b00000000], + x if x < (1 << 14) && x >= 0 => vec![oct[6] & 0b00111111 + 0b10000000, oct[7]], + x if x < (1 << 21) && x >= 0 => { + vec![oct[5] & 0b00011111 + 0b11000000, oct[6], oct[7]] + } + x if x < (1 << 28) && x >= 0 => { + vec![oct[4] & 0b00001111 + 0b11100000, oct[5], oct[6], oct[7]] + } + x if x < (1 << 32) && x >= 0 => vec![0b11110000, oct[4], oct[5], oct[6], oct[7]], + x if x >= 0 => vec![ + 0b11110100, oct[0], oct[1], oct[2], oct[3], oct[4], oct[5], oct[6], oct[7], + ], + x if x < 0 && x > -4 => vec![0b11111100 + ((!(-(oct[7] as i8) as u8)) & 0b11)], + x => [&[0b11111100], VarI64(-self.0).encode().as_slice()].concat(), + } + } + fn decode(stream: &mut impl Read) -> anyhow::Result { + let mut first = [0_u8; 1]; + stream.read_exact(&mut first)?; + let first = first[0]; + + Ok(Self(match first { + x if ((x >> 7) & 0b00000001) == 0 => (x as i64) & 0b01111111, + x if ((x >> 6) & 0b00000011) == 0b10 => { + let mut next = [0_u8; 1]; + stream.read_exact(&mut next)?; + + i64::from_be_bytes([0, 0, 0, 0, 0, 0, x & 0b00111111, next[0]]) + } + x if ((x >> 5) & 0b00000111) == 0b110 => { + let mut next = [0_u8; 2]; + stream.read_exact(&mut next)?; + + i64::from_be_bytes([0, 0, 0, 0, 0, x & 0b00011111, next[0], next[1]]) + } + x if ((x >> 4) & 0b00001111) == 0b1110 => { + let mut next = [0_u8; 3]; + stream.read_exact(&mut next)?; + + i64::from_be_bytes([0, 0, 0, 0, x & 0b00001111, next[0], next[1], next[2]]) + } + x if ((x >> 2) & 0b00111111) == 0b111100 => { + let mut next = [0_u8; 4]; + stream.read_exact(&mut next)?; + + i64::from_be_bytes([0, 0, 0, 0, next[0], next[1], next[2], next[3]]) + } + x if ((x >> 2) & 0b00111111) == 0b111101 => { + let mut next = [0_u8; 8]; + stream.read_exact(&mut next)?; + + i64::from_be_bytes(next) + } + x if ((x >> 2) & 0b00111111) == 0b111110 => -(Self::decode(stream)?.0), + x if ((x >> 2) & 0b00111111) == 0b111111 => -!((x as i64) & 0b1111111), + _ => todo!(), + })) + } + } +} + +#[derive(Debug)] +pub enum ControlMessage { + Version(control::Version), + UdpTunnel(VoiceData), + Authenticate(control::Authenticate), + Ping(control::Ping), + Reject(control::Reject), + ServerSync(control::ServerSync), + ChannelRemove(control::ChannelRemove), + ChannelState(control::ChannelState), + UserRemove(control::UserRemove), + UserState(control::UserState), + BanList(control::BanList), + TextMessage(control::TextMessage), + PermissionDenied(control::PermissionDenied), + Acl(control::Acl), + QueryUsers(control::QueryUsers), + CryptSetup(control::CryptSetup), + ContextActionModify(control::ContextActionModify), + ContextAction(control::ContextAction), + UserList(control::UserList), + VoiceTarget(control::VoiceTarget), + PermissionQuery(control::PermissionQuery), + CodecVersion(control::CodecVersion), + UserStats(control::UserStats), + RequestBlob(control::RequestBlob), + ServerConfig(control::ServerConfig), + SuggestConfig(control::SuggestConfig), +} + +impl endecode for ControlMessage { + fn decode(stream: &mut impl Read) -> anyhow::Result { + let mut kind = [0_u8; 2]; + stream.read_exact(&mut kind)?; + let kind = u16::from_be_bytes(kind); + let mut length = [0_u8; 4]; + stream.read_exact(&mut length)?; + let length = u32::from_be_bytes(length); + let mut data = vec![0_u8; length as usize]; + stream.read_exact(&mut data)?; + let mut data = Cursor::new(data); + + macro_rules! cm { + ($x:ident) => { + Self::$x(control::$x::decode(data.get_ref().as_slice())?) + }; + } + + Ok(match kind { + 0 => cm!(Version), + 1 => Self::UdpTunnel(VoiceData::decode(&mut data)?), + 2 => cm!(Authenticate), + 3 => cm!(Ping), + 4 => cm!(Reject), + 5 => cm!(ServerSync), + 6 => cm!(ChannelRemove), + 7 => cm!(ChannelState), + 8 => cm!(UserRemove), + 9 => cm!(UserState), + 10 => cm!(BanList), + 11 => cm!(TextMessage), + 12 => cm!(PermissionDenied), + 13 => cm!(Acl), + 14 => cm!(QueryUsers), + 15 => cm!(CryptSetup), + 16 => cm!(ContextActionModify), + 17 => cm!(ContextAction), + 18 => cm!(UserList), + 19 => cm!(VoiceTarget), + 20 => cm!(PermissionQuery), + 21 => cm!(CodecVersion), + 22 => cm!(UserStats), + 23 => cm!(RequestBlob), + 24 => cm!(ServerConfig), + 25 => cm!(SuggestConfig), + _ => todo!(), + }) + } + fn encode(&self) -> Vec { + let (kind, data): (u16, Vec) = match self { + Self::Version(x) => (0, x.encode_to_vec()), + Self::UdpTunnel(x) => (1, x.encode()), + Self::Authenticate(x) => (2, x.encode_to_vec()), + Self::Ping(x) => (3, x.encode_to_vec()), + Self::Reject(x) => (4, x.encode_to_vec()), + Self::ServerSync(x) => (5, x.encode_to_vec()), + Self::ChannelRemove(x) => (6, x.encode_to_vec()), + Self::ChannelState(x) => (7, x.encode_to_vec()), + Self::UserRemove(x) => (8, x.encode_to_vec()), + Self::UserState(x) => (9, x.encode_to_vec()), + Self::BanList(x) => (10, x.encode_to_vec()), + Self::TextMessage(x) => (11, x.encode_to_vec()), + Self::PermissionDenied(x) => (12, x.encode_to_vec()), + Self::Acl(x) => (13, x.encode_to_vec()), + Self::QueryUsers(x) => (14, x.encode_to_vec()), + Self::CryptSetup(x) => (15, x.encode_to_vec()), + Self::ContextActionModify(x) => (16, x.encode_to_vec()), + Self::ContextAction(x) => (17, x.encode_to_vec()), + Self::UserList(x) => (18, x.encode_to_vec()), + Self::VoiceTarget(x) => (19, x.encode_to_vec()), + Self::PermissionQuery(x) => (20, x.encode_to_vec()), + Self::CodecVersion(x) => (21, x.encode_to_vec()), + Self::UserStats(x) => (22, x.encode_to_vec()), + Self::RequestBlob(x) => (23, x.encode_to_vec()), + Self::ServerConfig(x) => (24, x.encode_to_vec()), + Self::SuggestConfig(x) => (25, x.encode_to_vec()), + }; + let length = (data.len() as u32).to_be_bytes(); + let kind = kind.to_be_bytes(); + [kind.as_slice(), length.as_slice(), &data].concat() + } +}