1
0
Fork 0
mirror of https://codeberg.org/demostf/sync.git synced 2026-06-03 16:44:07 +02:00

rewrite with tokio-tungstenite

This commit is contained in:
Robin Appelman 2024-11-24 18:45:23 +01:00
commit 608a4a1bde
6 changed files with 541 additions and 1125 deletions

623
Cargo.lock generated
View file

@ -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",
@ -1009,18 +910,6 @@ dependencies = [
"serde",
]
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer",
"digest",
"fake-simd",
"opaque-debug",
]
[[package]]
name = "sha1"
version = "0.6.1"
@ -1030,12 +919,32 @@ dependencies = [
"sha1_smol",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha1_smol"
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",

View file

@ -2,13 +2,20 @@
name = "sync"
version = "0.1.0"
authors = ["Robin Appelman <robin@icewind.nl>"]
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"

View file

@ -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<Sender> 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<RefCell<Vec<String>>>,
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<SyncCommand>) {
let map = RefCell::borrow(&self.received);
let received: Vec<_> = map
.iter()
.map(|msg| serde_json::from_str::<SyncCommand>(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<Sender> for Client {
fn from(sender: Sender) -> Self {
Client::Sender(sender.into())
}
}

View file

@ -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<Box<dyn NetworkStream + Sync + Send + 'static>> {
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<T: std::io::Write>(client: &mut Client<T>, command: SyncCommand) {
client
.send(Message::text(serde_json::to_string(&command).unwrap()))
.unwrap();
sleep(DELAY);
}
fn assert_receive<T: std::io::Read>(client: &mut Client<T>, expected: SyncCommand) {
let message = client.receive().unwrap().unwrap();
let text = message.as_text().unwrap();
assert_eq!(expected, serde_json::from_str(text).unwrap());
}

View file

@ -1,14 +1,25 @@
mod session;
use serde::{Deserialize, Serialize};
use parity_ws::{listen, util::Token, CloseCode, Error, Handler, Message, Result};
use std::collections::HashMap;
use std::rc::Rc;
mod client;
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<Message>;
type PeerMap = DashMap<SocketAddr, Tx>;
type Sessions = DashMap<String, Session>;
#[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<Token, Client>,
tick: u64,
playing: bool,
owner_left: Option<Instant>,
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<RefCell<HashMap<String, Session>>>,
}
fn handle_command(
command: SyncCommand,
sender: &Client,
sessions: &RefCell<HashMap<String, Session>>,
) {
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);
fn send_text<S: Into<String>>(&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),
},
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,
),
}
}
pub fn send_command(&self, peer: &SocketAddr, command: &SyncCommand) {
self.send_text(peer, serde_json::to_string(command).unwrap())
}
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);
}
}
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();
}
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"),
},
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);
}
}
None => {
error!(session, "session not found for command");
}
},
}
}
/// 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,
});
}
#[instrument(skip(self, raw_stream))]
async fn handle_connection(&self, raw_stream: TcpStream, addr: SocketAddr) {
debug!("incoming connection");
let ws_stream = tokio_tungstenite::accept_async(raw_stream)
.await
.expect("Error during the websocket handshake occurred");
info!("connection established");
// Insert the write part of this peer to the peer map.
let (tx, rx) = channel(16);
self.peers.insert(addr, tx);
let (outgoing, incoming) = ws_stream.split();
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");
}
}
} else {
debug!("ignoring non-text message");
}
Ok(())
});
let receive_from_others = rx.map(Ok).forward(outgoing);
let handle_messages = pin!(handle_messages);
let receive_from_others = pin!(receive_from_others);
select(handle_messages, receive_from_others).await;
info!(%addr, "disconnected");
self.peers.remove(&addr);
}
}
const TIMEOUT: Duration = Duration::from_secs(15 * 60);
/// cleanup sessions where the owner hasn't reconnected in 15 minutes
fn gc_sessions(sessions: &RefCell<HashMap<String, Session>>) {
let now = Instant::now();
sessions
.borrow_mut()
.retain(|_, session| match session.owner_left {
Some(left) => now.duration_since(left) > TIMEOUT,
None => true,
});
#[tokio::main]
async fn main() -> MainResult {
tracing_subscriber::fmt::init();
let port: u16 = std::env::var("PORT")
.unwrap_or_else(|_| "80".to_string())
.parse()?;
let listen_address = SocketAddr::from((Ipv4Addr::UNSPECIFIED, port));
let state = Arc::new(Server::new());
// Create the event loop and TCP listener we'll accept connections on.
let listener = TcpListener::bind(&listen_address).await.expect("Failed to bind");
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 });
}
Ok(())
}
fn update_session_and_forward<F>(
sender: &Client,
sessions: &RefCell<HashMap<String, Session>>,
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);
}
}
None => println!("session {} not found", session_name),
}
}
impl Handler for Server {
fn on_message(&mut self, msg: Message) -> Result<()> {
match serde_json::from_str::<SyncCommand>(msg.as_text().unwrap_or_default()) {
Ok(command) => {
handle_command(command, &self.out, &self.sessions);
Ok(())
}
Err(_) => Ok(()),
}
}
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);
}
}
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<RefCell<HashMap<String, Session>>> = 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<RefCell<HashMap<String, Session>>> = 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<HashMap<String, Session>> = 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<HashMap<String, Session>> = 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::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
}
},
sessions.into_inner()
);
}
#[test]
fn test_play_not_owner() {
let sessions: RefCell<HashMap<String, Session>> = 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
}
},
sessions.into_inner()
);
}
#[test]
fn test_tick_owner() {
let sessions: RefCell<HashMap<String, Session>> = 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
}
},
sessions.into_inner()
);
}
#[test]
fn test_tick_not_owner() {
let sessions: RefCell<HashMap<String, Session>> = 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()
);
}
#[test]
fn test_join() {
let sessions: RefCell<HashMap<String, Session>> = 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" };
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()
);
if let Client::Mock(mock) = sender {
mock.assert_received(vec![
SyncCommand::Tick {
session: "test",
tick: 99,
},
SyncCommand::Play {
session: "test",
play: true,
},
]);
};
}
#[test]
fn test_join_non_existing() {
let sessions: RefCell<HashMap<String, Session>> = 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" };
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_forward() {
let sessions: RefCell<HashMap<String, Session>> = 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
}
});
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 command = SyncCommand::Join { session: "test2" };
handle_command(command, &sender2, &sessions);
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());
};
}
#[test]
fn test_forward_non_owner() {
let sessions: RefCell<HashMap<String, Session>> = 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" };
handle_command(command.clone(), &sender1, &sessions);
handle_command(command.clone(), &sender2, &sessions);
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, &sender1, &sessions);
if let Client::Mock(mock) = sender1 {
assert_eq!(0, mock.received_count());
};
if let Client::Mock(mock) = sender2 {
assert_eq!(0, mock.received_count());
};
}
}
#[cfg(test)]
mod integration_tests;

78
src/session.rs Normal file
View file

@ -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<SocketAddr>,
tick: u64,
playing: bool,
owner_left: Option<Instant>,
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<Duration> {
self.owner_left.map(|left| left.duration_since(now))
}
pub fn initial_state(&self) -> impl Iterator<Item = SyncCommand> {
[
SyncCommand::Tick {
session: &self.token,
tick: self.tick,
},
SyncCommand::Play {
session: &self.token,
play: self.playing,
},
]
.into_iter()
}
pub fn clients(&self) -> impl Iterator<Item = &SocketAddr> {
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,
_ => {}
}
}
}