diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/Cargo.lock | 957 | ||||
-rw-r--r-- | server/Cargo.toml | 11 | ||||
-rw-r--r-- | server/src/main.rs | 22 | ||||
-rw-r--r-- | server/src/room/handle.rs | 106 | ||||
-rw-r--r-- | server/src/room/messages.rs | 69 | ||||
-rw-r--r-- | server/src/room/mod.rs | 128 | ||||
-rw-r--r-- | server/src/room/websocket.rs | 66 | ||||
-rw-r--r-- | server/src/rooms.rs | 59 | ||||
-rw-r--r-- | server/src/routes.rs | 51 |
9 files changed, 1469 insertions, 0 deletions
diff --git a/server/Cargo.lock b/server/Cargo.lock new file mode 100644 index 0000000..6b0a690 --- /dev/null +++ b/server/Cargo.lock @@ -0,0 +1,957 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f8ccfd9221ee7d1f3d4b33e1f8319b3a81ed8f61f2ea40b37b859794b4491" +dependencies = [ + "async-trait", + "axum-core", + "base64 0.21.0", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0dd4be24fcdcfeaa12a432d588dc59bbad6cad3510c67e74a2b6b2fc950564" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rollback" +version = "0.1.0" +dependencies = [ + "axum", + "serde", + "serde_json", + "tokio", + "tower-http", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..98ffd29 --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rollback" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +axum = { version = "0.6.12", features = ["ws"] } +tower-http = { version = "0.4.0", features = ["fs"] } diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..34783ac --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,22 @@ +mod routes; +mod rooms; +mod room; + +#[tokio::main] +async fn main() { + let port = std::env::var("PORT") + .unwrap_or("8080".to_owned()) + .parse::<u16>() + .unwrap_or(8080); + + axum::Server::bind(&std::net::SocketAddr::new( + std::net::IpAddr::V6(std::net::Ipv6Addr::from(0)), + port + )) + .serve( + routes::routes() + .into_make_service() + ) + .await + .expect("Error running the web server"); +} diff --git a/server/src/room/handle.rs b/server/src/room/handle.rs new file mode 100644 index 0000000..d397c70 --- /dev/null +++ b/server/src/room/handle.rs @@ -0,0 +1,106 @@ +use std::collections::HashSet; + +use super::messages::{ClientMessage, ServerMessage}; + +// send a ServerMessage::Connections to all sockets +pub async fn send_connections(v: &mut super::Clients, added: Option<usize>, removed: Option<usize>, frame: u64) { + // get the list of connection IDs + let connections: Vec<usize> = v.iter() + .enumerate() + .filter(|(_, n)| n.is_some()) + .map(|(id, _)| id) + .collect(); + + super::send(v, |id, _c| { + Some(ServerMessage::Connections { + connections: connections.clone(), + added, + removed, + id, + frame, + }) + }).await; +} + +// handle incoming websocket messages +pub async fn handle( + v: &mut super::Clients, + requests: &mut HashSet<(u64, Option<usize>, usize)>, // frame, connection, client id + pending: &mut Vec<(Option<usize>, Option<usize>)>, + id: usize, + msg: ClientMessage, +) { + match msg { + // broadcast inputs to every other connection + ClientMessage::Input { data, frame } => { + super::broadcast(v, ServerMessage::Input { + data, + frame, + connection: id + }, Some(id)).await; + }, + // a client needs the current game state, grab it from another client + ClientMessage::RequestState { frame, connection } => { + let count = super::conn_count(v); + + if count < 2 { // nobody to request state *from* + if let Some(Some(client)) = v.get(id) { + client.send(ServerMessage::State { + state: serde_json::Value::Null, + frame: 0, + connection: None, + }).await.ok(); + } + return; + } + + // request state from other clients + requests.insert((frame, connection, id)); + + match connection { + None => { + super::broadcast(v, ServerMessage::RequestState { frame }, Some(id)).await; + }, + Some(id) => { // it's to a specific connection + let Some(Some(client)) = v.get(id) else { + return; + }; + client.send(ServerMessage::RequestState { frame }).await.ok(); + }, + } + }, + // a client responded to a request for game state, tell all the requestees + ClientMessage::State { state, frame } => { + let mut new_requests = HashSet::new(); + for (fr, conn, cid) in requests.drain() { + if + fr != frame || // this isn't the requested frame + (conn.is_some() && Some(id) != conn) // this isn't the requested connection + { + new_requests.insert((fr, conn, cid)); + continue; + } + if let Some(Some(client)) = v.get(cid) { + client.send(ServerMessage::State { + state: state.clone(), + frame, + connection: Some(id), + }).await.ok(); + } + } + *requests = new_requests; + }, + // a client said what frame they're on, actually send the connections message + ClientMessage::Frame { frame } => { + for (added, removed) in pending.into_iter() { + send_connections(v, *added, *removed, frame).await; + } + *pending = Vec::new(); + }, + ClientMessage::Ping { frame } => { + if let Some(Some(client)) = v.get(id) { + client.send(ServerMessage::Pong { frame }).await.ok(); + } + } + } +} diff --git a/server/src/room/messages.rs b/server/src/room/messages.rs new file mode 100644 index 0000000..72958a6 --- /dev/null +++ b/server/src/room/messages.rs @@ -0,0 +1,69 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value; + +#[derive(Deserialize, Clone, Debug)] +#[serde(tag = "type")] +pub enum ClientMessage { + #[serde(rename = "frame")] + Frame { + frame: u64, + }, + #[serde(rename = "input")] + Input { + data: Value, + frame: u64, + }, + #[serde(rename = "requeststate")] + RequestState { + connection: Option<usize>, + frame: u64, + }, + #[serde(rename = "state")] + State { + state: Value, + frame: u64, + }, + #[serde(rename = "ping")] + Ping { + frame: u64, + }, +} + +#[derive(Serialize, Clone, Debug)] +#[serde(tag = "type")] +pub enum ServerMessage { + #[serde(rename = "framerequest")] + FrameRequest, + #[serde(rename = "connections")] + Connections { + connections: Vec<usize>, + added: Option<usize>, + removed: Option<usize>, + id: usize, + frame: u64, + }, + #[serde(rename = "input")] + Input { + data: Value, + frame: u64, + connection: usize, + }, + #[serde(rename = "requeststate")] + RequestState { + frame: u64, + }, + #[serde(rename = "state")] + State { + state: Value, + frame: u64, + connection: Option<usize>, + }, + #[serde(rename = "pong")] + Pong { + frame: u64, + }, + #[serde(rename = "error")] + Error { + error: String, + }, +} diff --git a/server/src/room/mod.rs b/server/src/room/mod.rs new file mode 100644 index 0000000..8b3d8c2 --- /dev/null +++ b/server/src/room/mod.rs @@ -0,0 +1,128 @@ +use std::{time::Duration, collections::HashSet}; + +use axum::extract::ws::WebSocket; +use tokio::sync::mpsc; + +mod websocket; +mod messages; +mod handle; + +use messages::{ClientMessage, ServerMessage}; + +pub enum RoomMessage { + Add(WebSocket), + Remove(usize), + WsMessage(usize, ClientMessage), +} + +pub type Client = mpsc::Sender<ServerMessage>; +pub type Clients = Vec<Option<Client>>; + +pub type Room = mpsc::Sender<RoomMessage>; + +// spawns a task for the room that listens for incoming messages from websockets as well as connections and disconnections +pub fn start_room(room_id: String, room_service: super::rooms::RoomService) -> Room { + let (tx, rx) = mpsc::channel::<RoomMessage>(20); + + let txret = tx.clone(); + + tokio::spawn(room_task(tx, rx, room_id, room_service)); + + txret +} + +async fn room_task(tx: mpsc::Sender<RoomMessage>, mut rx: mpsc::Receiver<RoomMessage>, room_id: String, room_service: super::rooms::RoomService) { + let mut ws = Vec::new(); + let mut state_requests = HashSet::new(); + let mut pending: Vec<(Option<usize>, Option<usize>)> = Vec::new(); + + while let Some(message) = rx.recv().await { + match message { + RoomMessage::Add(w) => { // a new connection is added + // create channels for the websocket and start a task to send and receive from it + let (wstx, wsrx) = mpsc::channel(5); + let id = ws.len(); + ws.push(Some(wstx)); + tokio::spawn(websocket::start_ws(w, id, tx.clone(), wsrx)); + + if conn_count(&ws) < 2 { // the first connection is on frame 0 + handle::send_connections(&mut ws, Some(id), None, 0).await; + } else { + // connections need to be added on a specific frame + // so ask the clients for a frame to put this event on + broadcast(&mut ws, ServerMessage::FrameRequest, Some(id)).await; + pending.push((Some(id), None)); + } + }, + RoomMessage::Remove(id) => { // a connection is closed (sent by the websocket task on exiting) + // only remove it if it exists + if let Some(item) = ws.get_mut(id) { + *item = None; + }; + let count = conn_count(&ws); + if count == 0 { // remove rooms once they become empty + room_service.send(super::rooms::RoomServiceRequest::Remove(room_id.clone())).await.ok(); + break; + } + + // disconnections happen on a specific frame, ask the clients for a frame + broadcast(&mut ws, ServerMessage::FrameRequest, None).await; + pending.push((None, Some(id))); + }, + RoomMessage::WsMessage(id, msg) => { // new data from a websocket + handle::handle(&mut ws, &mut state_requests, &mut pending, id, msg).await; + } + } + } +} + +// send the websocket to the room task +pub async fn add_connection(tx: &Room, ws: WebSocket) { + tx.send_timeout(RoomMessage::Add(ws), Duration::from_secs(1)).await.ok(); +} + +pub fn conn_count(v: &Clients) -> usize { + v.iter().filter(|i| i.is_some()).count() +} + +// send a message to all or some of the clients, in parallel rather than series, +// based on a callback +pub async fn send(v: &mut Clients, create_message: impl Fn(usize, &Client) -> Option<ServerMessage>) -> usize { + let tasks = v.iter() + .enumerate() + .map(|(id, c)| { + // send to existing clients + let Some(client) = c.clone() else { + return None; + }; + + let Some(msg) = create_message(id, &client) else { + return None; + }; + + Some(tokio::spawn(async move { + client.send(msg).await.ok(); + })) + }); + + let count = tasks.len(); + // make sure all the tasks complete + for task in tasks { + if let Some(t) = task { + t.await.ok(); + } + } + + count +} + +// send a message to all the websockets in the room (optionally excluding one) +pub async fn broadcast(v: &mut Clients, msg: ServerMessage, except: Option<usize>) -> usize { + send(v, |id, _client| { + if Some(id) == except { + return None; + } + + Some(msg.clone()) + }).await +} diff --git a/server/src/room/websocket.rs b/server/src/room/websocket.rs new file mode 100644 index 0000000..50a4537 --- /dev/null +++ b/server/src/room/websocket.rs @@ -0,0 +1,66 @@ +use std::time::Duration; + +use axum::extract::ws::{WebSocket, Message}; +use tokio::sync::mpsc; + +use super::RoomMessage; +use super::messages::ServerMessage; + +// set up some senders and receivers so that the websocket can receive messages from the task, send messages to the task, and notify the task when it closes +pub async fn start_ws(mut ws: WebSocket, id: usize, tx: mpsc::Sender<RoomMessage>, mut rx: mpsc::Receiver<ServerMessage>) { + loop { + tokio::select! { + m = ws.recv() => { // receive from the websocket and send it to `tx` + if let Some(Ok(msg)) = m { + // get the string contents + let optionstring = match msg { + Message::Text(s) => { + Some(s) + }, + Message::Binary(bin) => { + String::from_utf8(bin).ok() + }, + Message::Close(_) => { // quit the whole loop on disconnect + break; + }, + _ => None + }; + + // ignore things that aren't strings + let Some(s) = optionstring else { + continue; + }; + + // decode and send to the room + match serde_json::from_str(&s) { + Ok(message) => { + tx.send_timeout(RoomMessage::WsMessage(id, message), Duration::from_secs(1)).await.ok(); + }, + Err(e) => { // let the client know if they sent a bad message + if let Ok(text) = serde_json::to_string(&ServerMessage::Error{ + error: format!("Failed to decode JSON message: {}: {}", e, s), + }) { + ws.send(Message::Text(text)).await.ok(); + } + } + } + } else { // websocket error + break; + } + } + s = rx.recv() => { // receive from `rx` and send it to the websocket + if let Some(msg) = s { + if let Ok(string) = serde_json::to_string(&msg) { + ws.send(Message::Text(string)).await.ok(); + } + } else { // shouldn't happen but this is if the room drops the sender, it should close the websocket anyways + break; + } + } + } + } + + // websocket disconnect due to either error or normal disconnect + // notify the room that the socket should be removed + tx.send_timeout(RoomMessage::Remove(id), Duration::from_secs(1)).await.ok(); +} diff --git a/server/src/rooms.rs b/server/src/rooms.rs new file mode 100644 index 0000000..c8199d1 --- /dev/null +++ b/server/src/rooms.rs @@ -0,0 +1,59 @@ +use std::collections::HashMap; + +use axum::extract::ws::WebSocket; +use tokio::sync::mpsc; +use tokio::sync::oneshot; + +use super::room; + +pub enum RoomServiceRequest { + Exists(String, oneshot::Sender<bool>), + Join(String, WebSocket), + Remove(String), +} + +pub type RoomService = mpsc::Sender<RoomServiceRequest>; + +type RoomMap = HashMap<String, room::Room>; + +async fn handle_room_server_message(rooms: &mut RoomMap, req: RoomServiceRequest, tx: RoomService) { + match req { + // check whether a given room exists + // the sender must provide a tokio::sync::oneshot sender to receive a response + RoomServiceRequest::Exists(code, reply) => { + reply.send(rooms.get(&code).is_some()).ok(); + }, + // send a websocket into the given room, starting it if it doesn't exist + RoomServiceRequest::Join(code, ws) => { + let room = match rooms.get(&code) { + Some(rm) => rm, + None => { + let rm = room::start_room(code.clone(), tx); + rooms.insert(code.clone(), rm); + &rooms[&code] + } + }; + + room::add_connection(room, ws).await; + }, + // remove a room (called by the room task itself once there are no more connections to it) + RoomServiceRequest::Remove(code) => { + rooms.remove(&code); + } + } +} + +// a task to manage a hashmap holding the room task senders +// returns a sender to interface with the task +pub fn start_room_server() -> RoomService { + let (tx, mut rx) = mpsc::channel::<RoomServiceRequest>(10); + let txret = tx.clone(); + + tokio::spawn(async move { + let mut rooms: RoomMap = HashMap::new(); + while let Some(req) = rx.recv().await { + handle_room_server_message(&mut rooms, req, tx.clone()).await; + } + }); + txret +} diff --git a/server/src/routes.rs b/server/src/routes.rs new file mode 100644 index 0000000..a8c96a3 --- /dev/null +++ b/server/src/routes.rs @@ -0,0 +1,51 @@ +use axum::{ + extract::{ws::WebSocketUpgrade, Path}, + routing::get, + response::Response, + Router, Extension, +}; +use tokio::sync::oneshot; +use tower_http::services::ServeDir; + +use super::rooms; + +pub fn routes() -> Router { + let room_server: rooms::RoomService = rooms::start_room_server(); + + Router::new() + .route("/api/check", get(|| async {"ok"})) + .route("/api/exists/:code", get(game_exists)) + .route("/api/join/:code", get(game_join)) + .nest_service("/", ServeDir::new("../client")) + .layer(Extension(room_server)) +} + +// check if a given room code exists already +async fn game_exists( + Path(code): Path<String>, + Extension(room_server): Extension<rooms::RoomService> +) -> &'static str { + let (tx, rx) = oneshot::channel(); + room_server.send(rooms::RoomServiceRequest::Exists(code, tx)).await.ok(); + + if let Ok(res) = rx.await { + if res { + "true" + } else { + "false" + } + } else { + return "error"; + } +} + +// start a websocket connection and join it to the room +async fn game_join( + Path(code): Path<String>, + ws: WebSocketUpgrade, + Extension(room_server): Extension<rooms::RoomService> +) -> Response { + ws.on_upgrade(|s| async move { + room_server.send(rooms::RoomServiceRequest::Join(code, s)).await.ok(); + }) +} |