diff --git a/Cargo.lock b/Cargo.lock index a642a92..770a6eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,7 +30,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", @@ -54,12 +54,6 @@ dependencies = [ "console", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" @@ -68,47 +62,19 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" -version = "0.7.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "block-padding", - "byte-tools", - "byteorder", "generic-array", ] -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "iovec", -] - [[package]] name = "bytes" version = "1.8.0" @@ -124,12 +90,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -165,12 +125,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "digest" -version = "0.8.1" +name = "cpufeatures" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[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 = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", ] [[package]] @@ -190,18 +196,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "enum_dispatch" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" -dependencies = [ - "once_cell", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "errno" version = "0.3.9" @@ -212,18 +206,18 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fastrand" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -248,22 +242,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags 1.3.2", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "futures" version = "0.3.31" @@ -355,22 +333,12 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.12.4" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "version_check", ] [[package]] @@ -379,9 +347,9 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -390,12 +358,29 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "httparse" version = "1.9.5" @@ -541,43 +526,18 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "itoa" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.164" @@ -592,9 +552,19 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] name = "log" @@ -602,6 +572,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "main_error" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155db5e86c6e45ee456bf32fad5a290ee1f7151c2faca27ea27097568da67d1a" + [[package]] name = "maplit" version = "1.0.2" @@ -623,25 +599,6 @@ dependencies = [ "adler2", ] -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - [[package]] name = "mio" version = "1.0.2" @@ -650,34 +607,10 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] -[[package]] -name = "mio-extras" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" -dependencies = [ - "lazycell", - "log", - "mio 0.6.23", - "slab", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - [[package]] name = "native-tls" version = "0.2.12" @@ -696,14 +629,13 @@ dependencies = [ ] [[package]] -name = "net2" -version = "0.2.39" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", + "overload", + "winapi", ] [[package]] @@ -721,20 +653,14 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "openssl" version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.6.0", - "cfg-if 1.0.0", + "bitflags", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -772,21 +698,22 @@ dependencies = [ ] [[package]] -name = "parity-ws" -version = "0.11.1" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5983d3929ad50f12c3eb9a6743f19d691866ecd44da74c0a3308c3f8a56df0c6" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "byteorder", - "bytes 0.4.12", - "httparse", - "log", - "mio 0.6.23", - "mio-extras", - "rand 0.7.3", - "sha-1", - "slab", - "url", + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", ] [[package]] @@ -819,7 +746,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" dependencies = [ - "rand 0.8.5", + "rand", ] [[package]] @@ -849,19 +776,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -869,18 +783,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -890,16 +794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -908,16 +803,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "redox_syscall" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "rand_core 0.5.1", + "bitflags", ] [[package]] @@ -932,7 +827,7 @@ version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -954,13 +849,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1010,24 +911,23 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.8.2" +name = "sha1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", + "sha1_smol", ] [[package]] name = "sha1" -version = "0.6.1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "sha1_smol", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] @@ -1036,6 +936,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1089,12 +998,19 @@ name = "sync" version = "0.1.0" dependencies = [ "better-panic", - "enum_dispatch", + "dashmap", + "futures-channel", + "futures-util", + "log", + "main_error", "maplit", - "parity-ws", "portpicker", "serde", "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "tracing-subscriber", "websocket-lite", ] @@ -1115,13 +1031,43 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -1139,14 +1085,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", - "bytes 1.8.0", + "bytes", "libc", - "mio 1.0.2", + "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -1157,19 +1115,106 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ - "bytes 1.8.0", + "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1 0.10.6", + "thiserror", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" @@ -1184,15 +1229,21 @@ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 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 = "utf16_iter" version = "1.0.5" @@ -1205,6 +1256,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1212,10 +1269,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -1231,10 +1288,10 @@ checksum = "2108c9c18a6e746addc085c18cedb66b672e8ffea6a993712decc295b0d8ae55" dependencies = [ "base64", "byteorder", - "bytes 1.8.0", + "bytes", "httparse", - "rand 0.8.5", - "sha1", + "rand", + "sha1 0.6.1", "tokio-util", ] @@ -1245,10 +1302,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6cae39139c6e837afebd915935e7adc8af5c28425935de606d0e8c9d3268f6" dependencies = [ "base64", - "bytes 1.8.0", + "bytes", "futures", "native-tls", - "rand 0.8.5", + "rand", "tokio", "tokio-native-tls", "tokio-util", @@ -1256,12 +1313,6 @@ dependencies = [ "websocket-codec", ] -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -1272,12 +1323,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1384,21 +1429,11 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -1408,9 +1443,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", @@ -1441,18 +1476,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index d9fd09f..9303785 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,20 @@ name = "sync" version = "0.1.0" authors = ["Robin Appelman "] -edition = "2018" +edition = "2021" [dependencies] -parity-ws = "0.11" +tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "sync"] } +tokio-tungstenite = "0.24.0" serde = { version = "1", features = ["derive"] } serde_json = "1" -enum_dispatch = "0.3" +dashmap = "6.1.0" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +main_error = "0.1.2" +futures-channel = "0.3.31" +log = "0.4.22" +futures-util = "0.3.31" [dev-dependencies] maplit = "1" diff --git a/src/client.rs b/src/client.rs deleted file mode 100644 index 0955b7a..0000000 --- a/src/client.rs +++ /dev/null @@ -1,120 +0,0 @@ -use enum_dispatch::enum_dispatch; -use parity_ws::{util::Token, Result, Sender}; - -#[enum_dispatch(Client)] -pub(crate) trait ClientTrait { - #[allow(clippy::result_large_err)] - fn send(&self, msg: &str) -> Result<()>; - - fn token(&self) -> Token; -} - -#[derive(PartialEq, Clone, Debug)] -pub(crate) struct SenderClient(Sender); - -impl ClientTrait for SenderClient { - fn send(&self, msg: &str) -> Result<()> { - self.0.send(msg) - } - - fn token(&self) -> Token { - self.0.token() - } -} - -impl From for SenderClient { - fn from(sender: Sender) -> Self { - SenderClient(sender) - } -} - -#[cfg(test)] -mod mock { - use crate::client::ClientTrait; - use crate::SyncCommand; - use parity_ws::{util::Token, Result}; - use std::cell::RefCell; - use std::rc::Rc; - - #[derive(Clone, Debug)] - pub(crate) struct MockClient { - received: Rc>>, - token: Token, - } - - impl PartialEq for MockClient { - fn eq(&self, other: &MockClient) -> bool { - self.token == other.token - } - } - - impl ClientTrait for MockClient { - fn send(&self, msg: &str) -> Result<()> { - self.received.borrow_mut().push(msg.into()); - Ok(()) - } - - fn token(&self) -> Token { - self.token - } - } - - impl MockClient { - pub fn new(token: usize) -> Self { - MockClient { - received: Rc::new(RefCell::new(Vec::new())), - token: Token(token), - } - } - - pub fn received_count(&self) -> usize { - RefCell::borrow(&self.received).len() - } - - pub fn assert_received(&self, expected: Vec) { - let map = RefCell::borrow(&self.received); - - let received: Vec<_> = map - .iter() - .map(|msg| serde_json::from_str::(msg).expect("invalid message")) - .collect(); - - assert_eq!(expected, received); - } - - pub fn clear(&self) { - self.received.borrow_mut().clear() - } - } -} - -#[cfg(test)] -pub(crate) use mock::MockClient; - -#[cfg(not(test))] -#[enum_dispatch] -#[derive(PartialEq, Clone, Debug)] -pub(crate) enum Client { - Sender(SenderClient), -} - -#[cfg(test)] -#[enum_dispatch] -#[derive(PartialEq, Clone, Debug)] -pub(crate) enum Client { - Sender(SenderClient), - Mock(MockClient), -} - -#[cfg(test)] -impl Client { - pub fn mock(token: usize) -> Self { - Client::Mock(MockClient::new(token)) - } -} - -impl From for Client { - fn from(sender: Sender) -> Self { - Client::Sender(sender.into()) - } -} diff --git a/src/integration_tests.rs b/src/integration_tests.rs deleted file mode 100644 index e644062..0000000 --- a/src/integration_tests.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crate::{spawn_local_server, SyncCommand}; -use parity_ws::Sender; -use portpicker::pick_unused_port; -use std::thread::sleep; -use std::time::Duration; -use websocket_lite::{Client, ClientBuilder, Message, NetworkStream}; - -const DELAY: Duration = Duration::from_millis(50); - -struct TestHandle { - server_sender: Sender, - connect: String, -} - -impl TestHandle { - pub fn new() -> Self { - better_panic::install(); - - let port = pick_unused_port().expect("No ports free"); - - let server_sender = spawn_local_server(port); - - // give the server some time to start - sleep(DELAY); - - TestHandle { - server_sender, - connect: format!("ws://localhost:{}", port), - } - } - - pub fn get_client(&self) -> Client> { - ClientBuilder::new(&self.connect) - .unwrap() - .connect() - .unwrap() - } -} - -impl Drop for TestHandle { - fn drop(&mut self) { - self.server_sender.shutdown().unwrap() - } -} - -#[test] -fn integration_tests() { - let test = TestHandle::new(); - let mut owner = test.get_client(); - let mut client = test.get_client(); - - send( - &mut owner, - SyncCommand::Create { - session: "foo", - token: "bar", - }, - ); - send( - &mut owner, - SyncCommand::Tick { - session: "foo", - tick: 99, - }, - ); - - send(&mut client, SyncCommand::Join { session: "foo" }); - - assert_receive( - &mut client, - SyncCommand::Tick { - session: "foo", - tick: 99, - }, - ); - assert_receive( - &mut client, - SyncCommand::Play { - session: "foo", - play: false, - }, - ); - - send( - &mut owner, - SyncCommand::Play { - session: "foo", - play: true, - }, - ); - assert_receive( - &mut client, - SyncCommand::Play { - session: "foo", - play: true, - }, - ); - - // should be ignored - send( - &mut client, - SyncCommand::Tick { - session: "foo", - tick: 5, - }, - ); - - let mut client2 = test.get_client(); - - send(&mut client2, SyncCommand::Join { session: "foo" }); - - assert_receive( - &mut client2, - SyncCommand::Tick { - session: "foo", - tick: 99, - }, - ); - assert_receive( - &mut client2, - SyncCommand::Play { - session: "foo", - play: true, - }, - ); - - // owner reconnecting - std::mem::drop(owner); - - let mut owner2 = test.get_client(); - - send( - &mut owner2, - SyncCommand::Create { - session: "foo", - token: "bar", - }, - ); - - send( - &mut owner2, - SyncCommand::Play { - session: "foo", - play: false, - }, - ); - - assert_receive( - &mut client, - SyncCommand::Play { - session: "foo", - play: false, - }, - ); - assert_receive( - &mut client2, - SyncCommand::Play { - session: "foo", - play: false, - }, - ); -} - -fn send(client: &mut Client, command: SyncCommand) { - client - .send(Message::text(serde_json::to_string(&command).unwrap())) - .unwrap(); - sleep(DELAY); -} - -fn assert_receive(client: &mut Client, expected: SyncCommand) { - let message = client.receive().unwrap().unwrap(); - let text = message.as_text().unwrap(); - assert_eq!(expected, serde_json::from_str(text).unwrap()); -} diff --git a/src/main.rs b/src/main.rs index 9c05bc6..c2613f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,25 @@ -use serde::{Deserialize, Serialize}; - -use parity_ws::{listen, util::Token, CloseCode, Error, Handler, Message, Result}; -use std::collections::HashMap; -use std::rc::Rc; +mod session; -mod client; +use serde::{Deserialize, Serialize}; -use client::{Client, ClientTrait}; -use std::cell::RefCell; +use crate::session::Session; +use dashmap::DashMap; +use futures_channel::mpsc::{channel, Sender}; +use futures_util::future::select; +use futures_util::StreamExt; +use futures_util::TryStreamExt; +use main_error::MainResult; +use std::net::{Ipv4Addr, SocketAddr}; +use std::pin::pin; +use std::sync::Arc; use std::time::{Duration, Instant}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_tungstenite::tungstenite::Message; +use tracing::{debug, error, info, instrument, warn}; + +type Tx = Sender; +type PeerMap = DashMap; +type Sessions = DashMap; #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(tag = "type")] @@ -20,573 +31,153 @@ pub enum SyncCommand<'a> { Play { session: &'a str, play: bool }, } -#[derive(PartialEq, Debug)] -struct Session { - owner: Token, - owner_token: String, - clients: HashMap, - tick: u64, - playing: bool, - owner_left: Option, +pub struct Server { + peers: PeerMap, + sessions: Sessions, } -impl Session { - pub fn new(owner: Token, owner_token: String) -> Self { - Session { - owner, - owner_token, - clients: HashMap::new(), - playing: false, - tick: 0, - owner_left: None, +impl Server { + fn new() -> Self { + Server { + peers: PeerMap::with_capacity(128), + sessions: Sessions::with_capacity(64), } } - pub fn join(&mut self, client: &Client) { - self.clients.insert(client.token(), client.clone()); - } -} - -impl Session { - pub fn send_command(&self, command: &SyncCommand) { - let command_text = serde_json::to_string(command).unwrap(); - for client in self.clients.values() { - client.send(&command_text).ok(); - } - } -} - -struct Server { - out: Client, - sessions: Rc>>, -} - -fn handle_command( - command: SyncCommand, - sender: &Client, - sessions: &RefCell>, -) { - match &command { - SyncCommand::Create { session, token } => { - sessions - .borrow_mut() - .entry(session.to_string()) - .and_modify(|session| { - if token == &session.owner_token { - session.owner = sender.token(); - session.owner_left = None; - } - }) - .or_insert_with(|| Session::new(sender.token(), token.to_string())); - gc_sessions(sessions); - } - SyncCommand::Join { - session: session_name, - } => match sessions.borrow_mut().get_mut(*session_name) { - Some(session) => { - let _ = sender.send( - &serde_json::to_string(&SyncCommand::Tick { - tick: session.tick, - session: session_name, - }) - .unwrap(), - ); - let _ = sender.send( - &serde_json::to_string(&SyncCommand::Play { - play: session.playing, - session: session_name, - }) - .unwrap(), - ); - session.join(sender); - } - None => println!("session {} not found", session_name), - }, - SyncCommand::Tick { - tick, - session: session_name, - } => update_session_and_forward( - sender, - sessions, - session_name, - |session| session.tick = *tick, - &command, - ), - SyncCommand::Play { - play, - session: session_name, - } => update_session_and_forward( - sender, - sessions, - session_name, - |session| session.playing = *play, - &command, - ), - } -} - -const TIMEOUT: Duration = Duration::from_secs(15 * 60); - -/// cleanup sessions where the owner hasn't reconnected in 15 minutes -fn gc_sessions(sessions: &RefCell>) { - let now = Instant::now(); - sessions - .borrow_mut() - .retain(|_, session| match session.owner_left { - Some(left) => now.duration_since(left) > TIMEOUT, - None => true, - }); -} - -fn update_session_and_forward( - sender: &Client, - sessions: &RefCell>, - session_name: &str, - mut update_fn: F, - command: &SyncCommand, -) where - F: FnMut(&mut Session), -{ - match sessions.borrow_mut().get_mut(session_name) { - Some(session) => { - if session.owner == sender.token() { - update_fn(session); - session.send_command(command); + fn send_text>(&self, peer: &SocketAddr, text: S) { + if let Some(mut tx) = self.peers.get_mut(peer) { + if let Err(e) = tx.try_send(Message::Text(text.into())) { + error!(%peer, ?e, "failed to send message to client") } } - None => println!("session {} not found", session_name), } -} -impl Handler for Server { - fn on_message(&mut self, msg: Message) -> Result<()> { - match serde_json::from_str::(msg.as_text().unwrap_or_default()) { - Ok(command) => { - handle_command(command, &self.out, &self.sessions); - Ok(()) - } - Err(_) => Ok(()), - } + pub fn send_command(&self, peer: &SocketAddr, command: &SyncCommand) { + self.send_text(peer, serde_json::to_string(command).unwrap()) } - fn on_close(&mut self, _: CloseCode, _: &str) { - let mut sessions = self.sessions.borrow_mut(); - let token = self.out.token(); - - for session in sessions.values_mut() { - if session.owner == token { - session.owner_left = Some(Instant::now()) - } - - session.clients.remove(&token); + pub fn send_to_clients(&self, session: &Session, command: &SyncCommand) { + let command_text = serde_json::to_string(command).unwrap(); + for peer in session.clients() { + self.send_text(peer, &command_text); } } - fn on_error(&mut self, err: Error) { - println!("The server encountered an error: {:?}", err); - } -} - -/// Used to spawn a server in integration tests -#[cfg(test)] -pub fn spawn_local_server(port: u16) -> parity_ws::Sender { - use parity_ws::WebSocket; - use std::sync::mpsc::channel; - use std::thread::spawn; - - let listen_address = format!("localhost:{}", port); - - let (tx, rx) = channel(); - - spawn(move || { - let sessions: Rc>> = Rc::default(); - - let ws = WebSocket::new(|out: parity_ws::Sender| Server { - out: out.into(), - sessions: sessions.clone(), - }) - .unwrap(); - let ws = ws.bind(listen_address).unwrap(); - - tx.send(ws.broadcaster()).unwrap(); - - ws.run().unwrap(); - }); - - rx.recv().unwrap() -} - -fn main() { - let port = std::env::var("PORT").unwrap_or_else(|_| "80".to_string()); - let listen_address = format!("0.0.0.0:{}", port); - - println!("listening on: {:?}", listen_address); - - let sessions: Rc>> = Rc::default(); - - listen(listen_address, |out| Server { - out: out.into(), - sessions: sessions.clone(), - }) - .unwrap() -} - -#[cfg(test)] -mod tests { - use super::*; - use maplit::hashmap; - - #[test] - fn test_deserialize() { - let input = "{\"type\": \"create\", \"session\": \"foo\", \"token\": \"bar\"}"; - assert_eq!( - SyncCommand::Create { - session: "foo", - token: "bar", - }, - serde_json::from_str(input).unwrap() - ); - } - - #[test] - fn test_create() { - let sessions: RefCell> = RefCell::new(HashMap::new()); - let sender = Client::mock(1); - let command = SyncCommand::Create { - session: "test", - token: "bar", - }; - - handle_command(command, &sender, &sessions); - - assert_eq!( - hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: false, - owner_token: "bar".to_string(), - owner_left: None - } - }, - sessions.into_inner() - ); - } - - #[test] - fn test_play_owner() { - let sessions: RefCell> = RefCell::new(hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: false, - owner_token: "bar".to_string(), - owner_left: None + async fn handle_command<'a>(&self, command: SyncCommand<'_>, sender: SocketAddr) { + match &command { + SyncCommand::Create { session, token } => { + self.sessions + .entry(session.to_string()) + .and_modify(|session| { + if !session.set_owner(sender, token) { + warn!(%sender, token, "invalid owner token"); + } + }) + .or_insert_with(|| Session::new(sender, (*session).into(), token.to_string())); + self.gc_sessions(); } - }); - let sender = Client::mock(1); - let command = SyncCommand::Play { - session: "test", - play: true, - }; - - handle_command(command, &sender, &sessions); - - assert_eq!( - hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: true, - owner_token: "bar".to_string(), - owner_left: None + SyncCommand::Join { + session: session_name, + } => match self.sessions.get_mut(*session_name) { + Some(mut session) => { + for initial_command in session.initial_state() { + self.send_command(&sender, &initial_command); + } + session.join(sender); } + None => error!(session = session_name, "session not found for command"), }, - sessions.into_inner() - ); - } - - #[test] - fn test_play_not_owner() { - let sessions: RefCell> = RefCell::new(hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: false, - owner_token: "bar".to_string(), - owner_left: None - } - }); - let sender = Client::mock(2); - let command = SyncCommand::Play { - session: "test", - play: true, - }; - - handle_command(command, &sender, &sessions); - - assert_eq!( - hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: false, - owner_token: "bar".to_string(), - owner_left: None + session_command @ (SyncCommand::Play { session, .. } + | SyncCommand::Tick { session, .. }) => match self.sessions.get_mut(*session) { + Some(mut session) => { + if session.owner == sender { + session.handle_command(session_command); + self.send_to_clients(&session, &command); + } } - }, - sessions.into_inner() - ); - } - - #[test] - fn test_tick_owner() { - let sessions: RefCell> = RefCell::new(hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: false, - owner_token: "bar".to_string(), - owner_left: None - } - }); - let sender = Client::mock(1); - let command = SyncCommand::Tick { - session: "test", - tick: 99, - }; - - handle_command(command, &sender, &sessions); - - assert_eq!( - hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 99, - playing: false, - owner_token: "bar".to_string(), - owner_left: None + None => { + error!(session, "session not found for command"); } }, - sessions.into_inner() - ); + } } - #[test] - fn test_tick_not_owner() { - let sessions: RefCell> = RefCell::new(hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: false, - owner_token: "bar".to_string(), - owner_left: None - } - }); - let sender = Client::mock(2); - let command = SyncCommand::Tick { - session: "test", - tick: 99, - }; - - handle_command(command, &sender, &sessions); - - assert_eq!( - hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: false, - owner_token: "bar".to_string(), - owner_left: None - } - }, - sessions.into_inner() - ); + /// cleanup sessions where the owner hasn't reconnected in 15 minutes + fn gc_sessions(&self) { + let now = Instant::now(); + self.sessions + .retain(|_, session| match session.inactive_time(now) { + Some(inactive) => inactive > TIMEOUT, + None => true, + }); } - #[test] - fn test_join() { - let sessions: RefCell> = RefCell::new(hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 99, - playing: true, - owner_token: "bar".to_string(), - owner_left: None - } - }); - let sender = Client::mock(2); - let command = SyncCommand::Join { session: "test" }; + #[instrument(skip(self, raw_stream))] + async fn handle_connection(&self, raw_stream: TcpStream, addr: SocketAddr) { + debug!("incoming connection"); - handle_command(command, &sender, &sessions); - - assert_eq!( - hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![Token(2) => sender.clone()], - tick: 99, - playing: true, - owner_token: "bar".to_string(), - owner_left: None - } - }, - sessions.into_inner() - ); + let ws_stream = tokio_tungstenite::accept_async(raw_stream) + .await + .expect("Error during the websocket handshake occurred"); + info!("connection established"); - if let Client::Mock(mock) = sender { - mock.assert_received(vec![ - SyncCommand::Tick { - session: "test", - tick: 99, - }, - SyncCommand::Play { - session: "test", - play: true, - }, - ]); - }; - } + // Insert the write part of this peer to the peer map. + let (tx, rx) = channel(16); + self.peers.insert(addr, tx); - #[test] - fn test_join_non_existing() { - let sessions: RefCell> = RefCell::new(hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: false, - owner_token: "bar".to_string(), - owner_left: None - } - }); - let sender = Client::mock(2); - let command = SyncCommand::Join { session: "test2" }; + let (outgoing, incoming) = ws_stream.split(); - handle_command(command, &sender, &sessions); - - assert_eq!( - hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 0, - playing: false, - owner_token: "bar".to_string(), - owner_left: None + let handle_messages = incoming.try_for_each(|msg| async move { + if let Ok(message) = msg.to_text() { + match serde_json::from_str(message) { + Ok(command) => { + debug!(sender = %addr, message = ?command, "Received a message"); + self.handle_command(command, addr).await; + } + Err(e) => { + warn!(sender = %addr, message, error = %e, "Error while decoding message"); + } } - }, - sessions.into_inner() - ); - } - - #[test] - fn test_forward() { - let sessions: RefCell> = RefCell::new(hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 99, - playing: true, - owner_token: "bar".to_string(), - owner_left: None - }, - "test2".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 99, - playing: true, - owner_token: "bar".to_string(), - owner_left: None + } else { + debug!("ignoring non-text message"); } + Ok(()) }); - let owner = Client::mock(1); - let sender1 = Client::mock(2); - let sender2 = Client::mock(3); - let command = SyncCommand::Join { session: "test" }; - handle_command(command, &sender1, &sessions); + let receive_from_others = rx.map(Ok).forward(outgoing); - let command = SyncCommand::Join { session: "test2" }; - handle_command(command, &sender2, &sessions); + let handle_messages = pin!(handle_messages); + let receive_from_others = pin!(receive_from_others); + select(handle_messages, receive_from_others).await; - if let Client::Mock(mock) = &sender1 { - mock.clear(); - } - if let Client::Mock(mock) = &sender2 { - mock.clear(); - } - - let command = SyncCommand::Tick { - session: "test", - tick: 999, - }; - - handle_command(command, &owner, &sessions); - - if let Client::Mock(mock) = sender1 { - mock.assert_received(vec![SyncCommand::Tick { - session: "test", - tick: 999, - }]); - }; - if let Client::Mock(mock) = sender2 { - assert_eq!(0, mock.received_count()); - }; + info!(%addr, "disconnected"); + self.peers.remove(&addr); } +} - #[test] - fn test_forward_non_owner() { - let sessions: RefCell> = RefCell::new(hashmap! { - "test".into() => Session { - owner: Token(1), - clients: hashmap![], - tick: 99, - playing: true, - owner_token: "bar".to_string(), - owner_left: None - } - }); - let sender1 = Client::mock(2); - let sender2 = Client::mock(3); - let command = SyncCommand::Join { session: "test" }; +const TIMEOUT: Duration = Duration::from_secs(15 * 60); - handle_command(command.clone(), &sender1, &sessions); - handle_command(command.clone(), &sender2, &sessions); +#[tokio::main] +async fn main() -> MainResult { + tracing_subscriber::fmt::init(); - if let Client::Mock(mock) = &sender1 { - mock.clear(); - } - if let Client::Mock(mock) = &sender2 { - mock.clear(); - } + let port: u16 = std::env::var("PORT") + .unwrap_or_else(|_| "80".to_string()) + .parse()?; + let listen_address = SocketAddr::from((Ipv4Addr::UNSPECIFIED, port)); - let command = SyncCommand::Tick { - session: "test", - tick: 999, - }; + let state = Arc::new(Server::new()); - handle_command(command, &sender1, &sessions); + // Create the event loop and TCP listener we'll accept connections on. + let listener = TcpListener::bind(&listen_address).await.expect("Failed to bind"); - if let Client::Mock(mock) = sender1 { - assert_eq!(0, mock.received_count()); - }; - if let Client::Mock(mock) = sender2 { - assert_eq!(0, mock.received_count()); - }; + info!("listening on: {:?}", listen_address); + + // Let's spawn the handling of each connection in a separate task. + while let Ok((stream, addr)) = listener.accept().await { + let state = state.clone(); + tokio::spawn(async move { state.handle_connection(stream, addr).await }); } -} -#[cfg(test)] -mod integration_tests; + Ok(()) +} diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 0000000..ea984cd --- /dev/null +++ b/src/session.rs @@ -0,0 +1,78 @@ +use crate::SyncCommand; +use std::net::SocketAddr; +use std::time::{Duration, Instant}; + +#[derive(Debug)] +pub struct Session { + pub owner: SocketAddr, + owner_token: String, + clients: Vec, + tick: u64, + playing: bool, + owner_left: Option, + token: String, +} + +impl PartialEq for Session { + fn eq(&self, other: &Self) -> bool { + self.token.eq(&other.token) + } +} + +impl Session { + pub fn new(owner: SocketAddr, token: String, owner_token: String) -> Self { + Session { + owner, + owner_token, + clients: Vec::new(), + playing: false, + tick: 0, + owner_left: None, + token, + } + } + + pub fn join(&mut self, client: SocketAddr) { + self.clients.push(client); + } + + pub fn set_owner(&mut self, owner: SocketAddr, owner_token: &str) -> bool { + if owner_token == self.owner_token { + self.owner = owner; + self.owner_left = None; + } + owner_token == self.owner_token + } + + pub fn inactive_time(&self, now: Instant) -> Option { + self.owner_left.map(|left| left.duration_since(now)) + } + + pub fn initial_state(&self) -> impl Iterator { + [ + SyncCommand::Tick { + session: &self.token, + tick: self.tick, + }, + SyncCommand::Play { + session: &self.token, + play: self.playing, + }, + ] + .into_iter() + } + + pub fn clients(&self) -> impl Iterator { + self.clients.iter() + } + + pub fn handle_command(&mut self, command: &SyncCommand) { + match command { + SyncCommand::Tick { tick, .. } => { + self.tick = *tick; + } + SyncCommand::Play { play, .. } => self.playing = *play, + _ => {} + } + } +}