Browse Source

Initial commit

Sergey Chushin 3 years ago
commit
77406367bd
9 changed files with 1774 additions and 0 deletions
  1. 6 0
      .gitignore
  2. 569 0
      Cargo.lock
  3. 19 0
      Cargo.toml
  4. 8 0
      build.rs
  5. 491 0
      src/connection.rs
  6. 69 0
      src/main.rs
  7. 1 0
      src/proto/mod.rs
  8. 573 0
      src/proto/mumble.proto
  9. 38 0
      src/server.rs

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+/target
+.idea/
+rumble.iml
+
+# generated files
+src/proto/mumble.rs

+ 569 - 0
Cargo.lock

@@ -0,0 +1,569 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "bumpalo"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "cc"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "getrandom"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "mio"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[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 = "protobuf"
+version = "2.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b7f4a129bb3754c25a4e04032a90173c68f85168f77118ac4cb4936e7f06f92"
+
+[[package]]
+name = "protobuf-codegen"
+version = "2.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d2fa3a461857508103b914da60dd7b489c1a834967c2e214ecc1496f0c486a"
+dependencies = [
+ "protobuf",
+]
+
+[[package]]
+name = "protoc"
+version = "2.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6653d384a260fedff0a466e894e05c5b8d75e261a14e9f93e81e43ef86cad23"
+dependencies = [
+ "log",
+ "which",
+]
+
+[[package]]
+name = "protoc-rust"
+version = "2.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5198afa8fca3f419b36db9a70ede51ff845938ef0386b49f4b02a5a322015a6"
+dependencies = [
+ "protobuf",
+ "protobuf-codegen",
+ "protoc",
+ "tempfile",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "rumble"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "protobuf",
+ "protoc-rust",
+ "tokio",
+ "tokio-rustls",
+]
+
+[[package]]
+name = "rustls"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
+dependencies = [
+ "base64",
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "sct"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "syn"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "tokio"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
+
+[[package]]
+name = "web-sys"
+version = "0.3.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "which"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe"
+dependencies = [
+ "either",
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

+ 19 - 0
Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "rumble"
+version = "0.1.0"
+authors = ["sergey"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+clap = "2.33.3"
+tokio = { version = "1.5.0", features = ["rt-multi-thread", "net", "io-util"] }
+tokio-rustls = "0.22.0"
+protobuf = "2.22.1"
+
+[build-dependencies]
+protoc-rust = "2.22.1"
+
+[profile.release]
+lto = true

+ 8 - 0
build.rs

@@ -0,0 +1,8 @@
+fn main() {
+    protoc_rust::Codegen::new()
+        .out_dir("src/proto")
+        .input("src/proto/mumble.proto")
+        .include("src/proto")
+        .run()
+        .expect("protoc");
+}

+ 491 - 0
src/connection.rs

@@ -0,0 +1,491 @@
+use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
+use protobuf::{Message, ProtobufError};
+use crate::proto::mumble::{Version, Authenticate, Ping, Reject, ServerSync,
+                           ChannelRemove, ChannelState, UserRemove, UserState, BanList,
+                           TextMessage, PermissionDenied, ACL as Acl, QueryUsers, CryptSetup,
+                           ContextActionModify, ContextAction, UserList, VoiceTarget, PermissionQuery,
+                           CodecVersion, UserStats, RequestBlob, ServerConfig, SuggestConfig};
+
+const VERSION: u16 = 0;
+const UDP_TUNNEL: u16 = 1;
+const AUTHENTICATE: u16 = 2;
+const PING: u16 = 3;
+const REJECT: u16 = 4;
+const SERVER_SYNC: u16 = 5;
+const CHANNEL_REMOVE: u16 = 6;
+const CHANNEL_STATE: u16 = 7;
+const USER_REMOVE: u16 = 8;
+const USER_STATE: u16 = 9;
+const BAN_LIST: u16 = 10;
+const TEXT_MESSAGE: u16 = 11;
+const PERMISSION_DENIED: u16 = 12;
+const ACL: u16 = 13;
+const QUERY_USERS: u16 = 14;
+const CRYPT_SETUP: u16 = 15;
+const CONTEXT_ACTION_MODIFY: u16 = 16;
+const CONTEXT_ACTION: u16 = 17;
+const USER_LIST: u16 = 18;
+const VOICE_TARGET: u16 = 19;
+const PERMISSION_QUERY: u16 = 20;
+const CODEC_VERSION: u16 = 21;
+const USER_STATS: u16 = 22;
+const REQUEST_BLOB: u16 = 23;
+const SERVER_CONFIG: u16 = 24;
+const SUGGEST_CONFIG: u16 = 25;
+const MAX_AUDIO_PACKET_SIZE: usize = 1020;
+
+pub enum MumblePacket {
+    Version(Version),
+    UdpTunnel(VoicePacket),
+    Authenticate(Authenticate),
+    Ping(Ping),
+    Reject(Reject),
+    ServerSync(ServerSync),
+    ChannelRemove(ChannelRemove),
+    ChannelState(ChannelState),
+    UserRemove(UserRemove),
+    UserState(UserState),
+    BanList(BanList),
+    TextMessage(TextMessage),
+    PermissionDenied(PermissionDenied),
+    Acl(Acl),
+    QueryUsers(QueryUsers),
+    CryptSetup(CryptSetup),
+    ContextActionModify(ContextActionModify),
+    ContextAction(ContextAction),
+    UserList(UserList),
+    VoiceTarget(VoiceTarget),
+    PermissionQuery(PermissionQuery),
+    CodecVersion(CodecVersion),
+    UserStats(UserStats),
+    RequestBlob(RequestBlob),
+    ServerConfig(ServerConfig),
+    SuggestConfig(SuggestConfig)
+}
+
+pub enum VoicePacket {
+    Ping(VoicePing),
+    AudioData(AudioData)
+}
+
+pub enum Error {
+    UnknownPacketType,
+    ConnectionError,
+    ParsingError
+}
+
+pub struct MumblePacketStream<S> {
+    stream: S
+}
+
+pub struct VoicePacketStream<S> {
+    stream: S
+}
+
+pub struct VoicePing {
+    timestamp: u64
+}
+
+pub struct AudioData {
+    codec: Codecs,
+    target: u8,
+    session_id: Option<u64>,
+    sequence_number: u64,
+    audio_payload: Vec<u8>,
+    positional_info: Option<[f32; 3]>
+}
+
+enum Codecs {
+    CeltAlpha,
+    Speex,
+    CeltBeta,
+    Opus,
+}
+
+impl<S> MumblePacketStream<S>
+    where
+        S: AsyncRead + AsyncWrite + Unpin + Send,
+{
+    pub fn new(stream: S) -> Self {
+        MumblePacketStream { stream }
+    }
+
+    pub async fn read(&mut self) -> Result<MumblePacket, Error> {
+        let packet_type = self.stream.read_u16().await?;
+        let payload_length = self.stream.read_u32().await?;
+
+        if packet_type == UDP_TUNNEL {
+            return Ok(MumblePacket::UdpTunnel(self.read_voice_packet().await?))
+        }
+
+        let payload = self.read_payload(payload_length).await?;
+
+        match packet_type {
+            VERSION => Ok(MumblePacket::Version(Version::parse_from_bytes(&payload)?)),
+            AUTHENTICATE => Ok(MumblePacket::Authenticate(Authenticate::parse_from_bytes(&payload)?)),
+            PING => Ok(MumblePacket::Ping(Ping::parse_from_bytes(&payload)?)),
+            REJECT => Ok(MumblePacket::Reject(Reject::parse_from_bytes(&payload)?)),
+            SERVER_SYNC => Ok(MumblePacket::ServerSync(ServerSync::parse_from_bytes(&payload)?)),
+            CHANNEL_REMOVE => Ok(MumblePacket::ChannelRemove(ChannelRemove::parse_from_bytes(&payload)?)),
+            CHANNEL_STATE => Ok(MumblePacket::ChannelState(ChannelState::parse_from_bytes(&payload)?)),
+            USER_REMOVE => Ok(MumblePacket::UserRemove(UserRemove::parse_from_bytes(&payload)?)),
+            USER_STATE => Ok(MumblePacket::UserState(UserState::parse_from_bytes(&payload)?)),
+            BAN_LIST => Ok(MumblePacket::BanList(BanList::parse_from_bytes(&payload)?)),
+            TEXT_MESSAGE => Ok(MumblePacket::TextMessage(TextMessage::parse_from_bytes(&payload)?)),
+            PERMISSION_DENIED => Ok(MumblePacket::PermissionDenied(PermissionDenied::parse_from_bytes(&payload)?)),
+            ACL => Ok(MumblePacket::Acl(Acl::parse_from_bytes(&payload)?)),
+            QUERY_USERS => Ok(MumblePacket::QueryUsers(QueryUsers::parse_from_bytes(&payload)?)),
+            CRYPT_SETUP => Ok(MumblePacket::CryptSetup(CryptSetup::parse_from_bytes(&payload)?)),
+            CONTEXT_ACTION_MODIFY => Ok(MumblePacket::ContextActionModify(ContextActionModify::parse_from_bytes(&payload)?)),
+            CONTEXT_ACTION => Ok(MumblePacket::ContextAction(ContextAction::parse_from_bytes(&payload)?)),
+            USER_LIST => Ok(MumblePacket::UserList(UserList::parse_from_bytes(&payload)?)),
+            VOICE_TARGET => Ok(MumblePacket::VoiceTarget(VoiceTarget::parse_from_bytes(&payload)?)),
+            PERMISSION_QUERY => Ok(MumblePacket::PermissionQuery(PermissionQuery::parse_from_bytes(&payload)?)),
+            CODEC_VERSION => Ok(MumblePacket::CodecVersion(CodecVersion::parse_from_bytes(&payload)?)),
+            USER_STATS => Ok(MumblePacket::UserStats(UserStats::parse_from_bytes(&payload)?)),
+            REQUEST_BLOB => Ok(MumblePacket::RequestBlob(RequestBlob::parse_from_bytes(&payload)?)),
+            SERVER_CONFIG => Ok(MumblePacket::ServerConfig(ServerConfig::parse_from_bytes(&payload)?)),
+            SUGGEST_CONFIG => Ok(MumblePacket::SuggestConfig(SuggestConfig::parse_from_bytes(&payload)?)),
+            _ => Err(Error::UnknownPacketType)
+        }
+    }
+
+    pub async fn write(&mut self, packet: MumblePacket) -> Result<(), Error> {
+        match packet {
+            MumblePacket::UdpTunnel(value) => {
+                let bytes = Self::serialize_voice_packet(value);
+                self.stream.write_u16(UDP_TUNNEL).await?;
+                self.stream.write_u32(bytes.len() as u32).await?;
+                self.stream.write_all(&bytes).await?;
+            }
+            MumblePacket::Version(value) => self.write_protobuf_packet(value, VERSION).await?,
+            MumblePacket::Authenticate(value) => self.write_protobuf_packet(value, AUTHENTICATE).await?,
+            MumblePacket::Ping(value) => self.write_protobuf_packet(value, PING).await?,
+            MumblePacket::Reject(value) => self.write_protobuf_packet(value, REJECT).await?,
+            MumblePacket::ServerSync(value) => self.write_protobuf_packet(value, SERVER_SYNC).await?,
+            MumblePacket::ChannelRemove(value) => self.write_protobuf_packet(value, CHANNEL_REMOVE).await?,
+            MumblePacket::ChannelState(value) => self.write_protobuf_packet(value, CHANNEL_STATE).await?,
+            MumblePacket::UserRemove(value) => self.write_protobuf_packet(value, USER_REMOVE).await?,
+            MumblePacket::UserState(value) => self.write_protobuf_packet(value, USER_STATE).await?,
+            MumblePacket::BanList(value) => self.write_protobuf_packet(value, BAN_LIST).await?,
+            MumblePacket::TextMessage(value) => self.write_protobuf_packet(value, TEXT_MESSAGE).await?,
+            MumblePacket::PermissionDenied(value) => self.write_protobuf_packet(value, PERMISSION_DENIED).await?,
+            MumblePacket::Acl(value) => self.write_protobuf_packet(value, ACL).await?,
+            MumblePacket::QueryUsers(value) => self.write_protobuf_packet(value, QUERY_USERS).await?,
+            MumblePacket::CryptSetup(value) => self.write_protobuf_packet(value, CRYPT_SETUP).await?,
+            MumblePacket::ContextActionModify(value) => self.write_protobuf_packet(value, CONTEXT_ACTION_MODIFY).await?,
+            MumblePacket::ContextAction(value) => self.write_protobuf_packet(value, CONTEXT_ACTION).await?,
+            MumblePacket::UserList(value) => self.write_protobuf_packet(value, USER_LIST).await?,
+            MumblePacket::VoiceTarget(value) => self.write_protobuf_packet(value, VOICE_TARGET).await?,
+            MumblePacket::PermissionQuery(value) => self.write_protobuf_packet(value, PERMISSION_QUERY).await?,
+            MumblePacket::CodecVersion(value) => self.write_protobuf_packet(value, CODEC_VERSION).await?,
+            MumblePacket::UserStats(value) => self.write_protobuf_packet(value, USER_STATS).await?,
+            MumblePacket::RequestBlob(value) => self.write_protobuf_packet(value, REQUEST_BLOB).await?,
+            MumblePacket::ServerConfig(value) => self.write_protobuf_packet(value, SERVER_CONFIG).await?,
+            MumblePacket::SuggestConfig(value) => self.write_protobuf_packet(value, SUGGEST_CONFIG).await?,
+        }
+
+        Ok(())
+    }
+
+    async fn read_payload(&mut self, payload_length: u32) -> tokio::io::Result<Vec<u8>> {
+        let mut payload = vec![0; payload_length as usize];
+        self.stream.read_exact(&mut payload).await?;
+        Ok(payload)
+    }
+
+    async fn read_varint(&mut self) -> Result<u64, Error> { //TODO negative number decode
+        let header = self.stream.read_u8().await?;
+
+        //7-bit number
+        if (header & 0b1000_0000) == 0b0000_0000 {
+            return Ok(header as u64);
+        }
+        //14-bit number
+        if (header & 0b1100_0000) == 0b1000_0000 {
+            let first_number_byte = header ^ 0b1000_0000;
+            return Ok(
+                ((first_number_byte       as u64) << 8) |
+                (self.stream.read_u8().await? as u64)
+            );
+        }
+        //21-bit number
+        if (header & 0b1110_0000) == 0b1100_0000 {
+            let first_number_byte = header ^ 0b1100_0000;
+            return Ok(
+                ((first_number_byte       as u64) << 16) |
+                ((self.stream.read_u8().await? as u64) << 8 ) |
+                (self.stream.read_u8().await? as u64)
+            );
+        }
+        //28-bit number
+        if (header & 0b1111_0000) == 0b1110_0000 {
+            let first_number_byte = header ^ 0b1110_0000;
+            return Ok(
+                ((first_number_byte       as u64) << 24) |
+                ((self.stream.read_u8().await? as u64) << 16) |
+                ((self.stream.read_u8().await? as u64) << 8 ) |
+                (self.stream.read_u8().await? as u64)
+            );
+        }
+        //32-bit number
+        if (header & 0b1111_1100) == 0b1111_0000 {
+            return Ok(self.stream.read_u32().await? as u64);
+        }
+        //64-bit number
+        if (header & 0b1111_1100) == 0b1111_0100 {
+            return Ok(self.stream.read_u64().await?);
+        }
+
+        Err(Error::ParsingError)
+    }
+
+    async fn read_voice_packet(&mut self) -> Result<VoicePacket, Error> {
+        let header = self.stream.read_u8().await?;
+        let (audio_packet_type, target) = Self::decode_header(header);
+
+        if audio_packet_type == 1 {
+            let timestamp = self.read_varint().await?;
+            return Ok(VoicePacket::Ping(VoicePing {
+                timestamp
+            }));
+        }
+
+        let codec = match audio_packet_type {
+            0 => Codecs::CeltAlpha,
+            2 => Codecs::Speex,
+            3 => Codecs::CeltBeta,
+            4 => Codecs::Opus,
+            _ => return Err(Error::ParsingError)
+        };
+        let sequence_number = self.read_varint().await?;
+        let audio_payload = self.read_audio_payload(&codec).await?;
+        Ok(VoicePacket::AudioData(AudioData {
+            codec,
+            target,
+            session_id: None,
+            sequence_number,
+            audio_payload,
+            positional_info: None //TODO
+        }))
+    }
+
+    async fn read_audio_payload(&mut self, codec_type: &Codecs) -> Result<Vec<u8>, Error> {
+        match codec_type {
+            Codecs::CeltAlpha | Codecs::Speex | Codecs::CeltBeta => {
+                let mut payload = vec![];
+                loop {
+                    let header = self.stream.read_u8().await?;
+                    let continuation_bit = header & 0b1000_0000;
+                    let length = header & 0b0111_1111;
+                    payload.push(header);
+                    if length == 0 {
+                        payload.push(0);
+                        break;
+                    }
+                    for _ in 0..length {
+                        payload.push(self.stream.read_u8().await?)
+                    }
+
+                    if continuation_bit == 0 {
+                        break;
+                    }
+                    if payload.len() > MAX_AUDIO_PACKET_SIZE {
+                        return Err(Error::ParsingError);
+                    }
+                }
+                Ok(payload)
+            }
+            Codecs::Opus => {
+                let mut payload = vec![];
+                let header = self.read_varint().await?;
+                let length = header & 0x1fff;
+                payload.append(&mut Self::encode_varint(header));
+
+                for _ in 0..length {
+                    payload.push(self.stream.read_u8().await?)
+                }
+                Ok(payload)
+            }
+        }
+    }
+
+    async fn write_protobuf_packet<T> (&mut self, packet: T, packet_type: u16) -> Result<(), Error>
+    where T: Message
+    {
+        let bytes = packet.write_to_bytes()?;
+        self.stream.write_u16(packet_type).await?;
+        self.stream.write_u32(bytes.len() as u32).await?;
+        self.stream.write_all(&bytes).await?;
+
+        Ok(())
+    }
+
+    fn decode_header(header: u8) -> (u8, u8) {
+        let packet_type = header >> 5;
+        let target = header & 0b0001_1111;
+        (packet_type, target)
+    }
+
+    fn encode_header(packet_type: u8, target: u8) -> u8 {
+        (packet_type << 5) | target
+    }
+
+    fn encode_varint(number: u64) -> Vec<u8> { //TODO negative number encode
+        let mut result = vec![];
+
+        if number < 0x80 {
+            //7-bit number
+            result.push(number as u8);
+        } else if number < 0x4000 {
+            //14-bit number
+            result.push(((number >> 8) | 0x80) as u8);
+            result.push((number & 0xFF) as u8);
+        } else if number < 0x200000 {
+            //21-bit number
+            result.push(((number >> 16) | 0xC0) as u8);
+            result.push(((number >> 8) & 0xFF) as u8);
+            result.push((number & 0xFF) as u8);
+        } else if number < 0x10000000 {
+            //28-bit number
+            result.push(((number >> 24) | 0xE0) as u8);
+            result.push(((number >> 16) & 0xFF) as u8);
+            result.push(((number >> 8) & 0xFF) as u8);
+            result.push((number & 0xFF) as u8);
+        } else if number < 0x100000000 {
+            //32-bit number
+            result.push(0xF0);
+            result.push(((number >> 24) & 0xFF) as u8);
+            result.push(((number >> 16) & 0xFF) as u8);
+            result.push(((number >> 8) & 0xFF) as u8);
+            result.push((number & 0xFF) as u8);
+        } else {
+            //64-bit number
+            result.push(0xF4);
+            result.push(((number >> 56) & 0xFF) as u8);
+            result.push(((number >> 48) & 0xFF) as u8);
+            result.push(((number >> 40) & 0xFF) as u8);
+            result.push(((number >> 32) & 0xFF) as u8);
+            result.push(((number >> 24) & 0xFF) as u8);
+            result.push(((number >> 16) & 0xFF) as u8);
+            result.push(((number >> 8) & 0xFF) as u8);
+            result.push((number & 0xFF) as u8);
+        }
+
+        result
+    }
+
+    fn serialize_voice_packet(packet: VoicePacket) -> Vec<u8> {
+        let mut result = vec![];
+
+        match packet {
+            VoicePacket::Ping(value) => {
+                result.push(0b0010_0000);
+                let mut varint = Self::encode_varint(value.timestamp);
+                result.append(&mut varint);
+            }
+            VoicePacket::AudioData(mut value) => {
+                let packet_type = match value.codec {
+                    Codecs::CeltAlpha => 0b0000_0000,
+                    Codecs::Speex => 0b0100_0000,
+                    Codecs::CeltBeta => 0b0110_0000,
+                    Codecs::Opus => 0b1000_0000,
+                };
+                let header = Self::encode_header(packet_type, value.target);
+                result.push(header);
+
+                if let Some(session_id) = value.session_id {
+                    let mut session_id = Self::encode_varint(session_id);
+                    result.append(&mut session_id);
+                }
+
+                let mut sequence_number = Self::encode_varint(value.sequence_number);
+                result.append(&mut sequence_number);
+
+                result.append(&mut value.audio_payload);
+
+                if let Some(position_info) = value.positional_info {
+                    result.extend_from_slice(&position_info[0].to_be_bytes());
+                    result.extend_from_slice(&position_info[1].to_be_bytes());
+                    result.extend_from_slice(&position_info[2].to_be_bytes());
+                }
+            }
+        }
+
+        result
+    }
+}
+
+impl <S> VoicePacketStream<S>
+    where
+        S: AsyncRead + AsyncWrite + Unpin + Send,
+{
+    pub fn new(stream: S) -> Self {
+        VoicePacketStream { stream }
+    }
+
+    pub async fn read(&mut self) -> Result<VoicePacket, Error> {
+        unimplemented!() //TODO
+    }
+
+    pub async fn write(&mut self, packet: VoicePacket) -> Result<(), Error> {
+        unimplemented!() //TODO
+    }
+}
+
+impl From<std::io::Error> for Error {
+    fn from(_: std::io::Error) -> Self {
+        Error::ConnectionError
+    }
+}
+
+impl From<ProtobufError> for Error {
+    fn from(error: ProtobufError) -> Self {
+        match error {
+            ProtobufError::IoError(_) | ProtobufError::WireError(_) => Error::ConnectionError,
+            ProtobufError::Utf8(_) | ProtobufError::MessageNotInitialized { .. } => Error::ParsingError
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use tokio::net::TcpStream;
+
+    #[test]
+    fn test_decode_header() {
+        assert_eq!(MumblePacketStream::<TcpStream>::decode_header(0b0100_1000), (2, 8));
+        assert_eq!(MumblePacketStream::<TcpStream>::decode_header(0b0111_1111), (3, 31));
+        assert_eq!(MumblePacketStream::<TcpStream>::decode_header(0b1000_0000), (4, 0));
+    }
+
+    #[test]
+    fn test_encode_header() {
+        assert_eq!(MumblePacketStream::<TcpStream>::encode_header(2, 8), 0b0100_1000);
+        assert_eq!(MumblePacketStream::<TcpStream>::encode_header(3, 31), 0b0111_1111);
+        assert_eq!(MumblePacketStream::<TcpStream>::encode_header(4, 0), 0b1000_0000);
+    }
+
+    #[test]
+    fn test_encode_varint() {
+        let varint_7bit_positive = vec![0b0000_1000];
+        let varint_14bit_positive = vec![0b1010_0010, 0b0000_0011];
+        let varint_21bit_positive = vec![0b1101_0100, 0b0000_0000, 0b0000_0000];
+        let varint_28bit_positive =
+            vec![0b1110_1100, 0b0100_0000, 0b0010_0000, 0b0000_0001];
+        let varint_32bit_positive =
+            vec![0b1111_0000, 0b1100_0000, 0b0000_0000, 0b0000_0000, 0b0000_0001];
+        let varint_64bit_positive =
+            vec![0b1111_0100, 0b1100_0000, 0b0000_0000, 0b0000_0000, 0b0000_0001,
+                 0b0000_0000, 0b0000_0000, 0b0000_0000, 0b0001_0000];
+
+        assert_eq!(MumblePacketStream::<TcpStream>::encode_varint(0x8), varint_7bit_positive);
+        assert_eq!(MumblePacketStream::<TcpStream>::encode_varint(0x2203), varint_14bit_positive);
+        assert_eq!(MumblePacketStream::<TcpStream>::encode_varint(0x140000), varint_21bit_positive);
+        assert_eq!(MumblePacketStream::<TcpStream>::encode_varint(0xc402001), varint_28bit_positive);
+        assert_eq!(MumblePacketStream::<TcpStream>::encode_varint(0xc0000001), varint_32bit_positive);
+        assert_eq!(MumblePacketStream::<TcpStream>::encode_varint(0xc000000100000010), varint_64bit_positive);
+    }
+}
+

+ 69 - 0
src/main.rs

@@ -0,0 +1,69 @@
+mod server;
+mod proto;
+mod connection;
+
+use std::fs::File;
+use std::io::BufReader;
+use clap::{Arg, App};
+use tokio::runtime::Builder;
+use tokio_rustls::rustls::{Certificate, PrivateKey, internal::pemfile};
+
+fn main() {
+    let matches = App::new("Rumble")
+        .version("0.0.1")
+        .about("Rumble is a mumble server written in Rust.")
+        .arg(Arg::with_name("ip")
+            .long("ip")
+            .default_value("0.0.0.0")
+            .takes_value(true)
+            .help("Specific IP or hostname to bind to"))
+        .arg(Arg::with_name("port")
+            .long("port")
+            .short("p")
+            .default_value("64738")
+            .takes_value(true)
+            .help("Port to use"))
+        .arg(Arg::with_name("certificate")
+            .long("cert_file")
+            .short("c")
+            .takes_value(true)
+            .required(true)
+            .help("Path to a ssl certificate"))
+        .arg(Arg::with_name("private key")
+            .long("private_key")
+            .short("k")
+            .takes_value(true)
+            .required(true)
+            .help("Path to a ssl keyfile"))
+        .get_matches();
+
+    let ip = matches.value_of("ip").unwrap();
+    let port = matches.value_of("port").unwrap();
+    let cert_file = matches.value_of("certificate").unwrap();
+    let keyfile = matches.value_of("private key").unwrap();
+
+    let config = server::Config {
+        ip_address: ip.parse().unwrap(),
+        port: port.parse().unwrap(),
+        certificate: read_certificate(cert_file),
+        private_key: read_private_key(keyfile)
+    };
+
+    let tokio_rt = Builder::new_multi_thread()
+        .enable_all()
+        .build()
+        .unwrap();
+    tokio_rt.block_on(async {
+        server::run(config).await;
+    });
+}
+
+fn read_certificate(path: &str) -> Certificate {
+    let mut file = BufReader::new(File::open(path).unwrap());
+    pemfile::certs(&mut file).unwrap().remove(0)
+}
+
+fn read_private_key(path: &str) -> PrivateKey {
+    let mut file = BufReader::new(File::open(path).unwrap());
+    pemfile::pkcs8_private_keys(&mut file).unwrap().remove(0)
+}

+ 1 - 0
src/proto/mod.rs

@@ -0,0 +1 @@
+pub mod mumble;

+ 573 - 0
src/proto/mumble.proto

@@ -0,0 +1,573 @@
+syntax = "proto2";
+
+option optimize_for = SPEED;
+
+message Version {
+  // 2-byte Major, 1-byte Minor and 1-byte Patch version number.
+  optional uint32 version = 1;
+  // Client release name.
+  optional string release = 2;
+  // Client OS name.
+  optional string os = 3;
+  // Client OS version.
+  optional string os_version = 4;
+}
+
+// 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];
+}
+
+// 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.
+  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 {
+  // 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;
+}
+
+// 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;
+}
+
+// Sent by the server to inform the clients of suggested client configuration
+// specified by the server administrator.
+message SuggestConfig {
+  // Suggested client version.
+  optional uint32 version = 1;
+  // 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;
+}

+ 38 - 0
src/server.rs

@@ -0,0 +1,38 @@
+use std::net::{IpAddr, SocketAddr};
+use std::sync::Arc;
+use tokio::net::{TcpListener, TcpStream};
+use tokio_rustls::rustls::{Certificate, PrivateKey, ServerConfig, NoClientAuth};
+use tokio_rustls::{TlsAcceptor, server::TlsStream};
+use crate::connection::{MumblePacketStream};
+
+pub struct Config {
+    pub ip_address: IpAddr,
+    pub port: u16,
+    pub certificate: Certificate,
+    pub private_key: PrivateKey,
+}
+
+pub async fn run(config: Config) -> std::io::Result<()> {
+    let mut tls_config = ServerConfig::new(NoClientAuth::new());
+    tls_config.set_single_cert(vec![config.certificate], config.private_key)
+        .expect("Invalid private key");
+
+    let acceptor = TlsAcceptor::from(Arc::new(tls_config));
+    let listener = TcpListener::bind(
+        SocketAddr::new(config.ip_address, config.port)
+    ).await?;
+
+    loop {
+        let (stream, _) = listener.accept().await?;
+        let acceptor = acceptor.clone();
+
+        tokio::spawn(async move {
+            let stream = acceptor.accept(stream).await;
+            if let Ok(stream) = stream {
+                process(MumblePacketStream::new(stream)).await;
+            }
+        });
+    }
+}
+
+async fn process(stream: MumblePacketStream<TlsStream<TcpStream>>) {}