Compare commits
16 Commits
fbaf386ebb
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 368d6585c4 | |||
| 92f7e5aa5f | |||
| 6189b82bd3 | |||
| 152e7926ee | |||
| e661526e78 | |||
| c41bf83eb8 | |||
| daedfe72d0 | |||
| 4c0f571317 | |||
| bf84433d5b | |||
| 3ab9cedbcd | |||
| ab3c28ff37 | |||
| ab25fa0267 | |||
| 13fb9a9fdd | |||
| b6d54d086e | |||
| f28c9fee83 | |||
| 41175e487e |
400
Cargo.lock
generated
400
Cargo.lock
generated
@@ -50,34 +50,12 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
@@ -107,36 +85,12 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.1.0"
|
||||
@@ -146,6 +100,95 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
@@ -167,14 +210,26 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hexland-server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"rand",
|
||||
"regex",
|
||||
"sha2",
|
||||
"tungstenite",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -204,15 +259,6 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.3"
|
||||
@@ -231,6 +277,16 @@ version = "0.2.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
@@ -247,72 +303,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.10"
|
||||
name = "mio"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
|
||||
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.14.0"
|
||||
name = "num_cpus"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.41"
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -322,10 +354,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.25"
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
@@ -408,46 +446,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
@@ -471,6 +473,40 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.100"
|
||||
@@ -482,20 +518,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.35"
|
||||
@@ -531,6 +553,49 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.17.3"
|
||||
@@ -543,7 +608,6 @@ dependencies = [
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"native-tls",
|
||||
"rand",
|
||||
"sha-1",
|
||||
"thiserror",
|
||||
@@ -595,12 +659,6 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
||||
@@ -6,7 +6,10 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tungstenite = { version = "0.17.3", features = ["native-tls"] }
|
||||
sha2 = "0.10.6"
|
||||
rand = "0.8.5"
|
||||
regex = "1.6.0"
|
||||
tokio = { version = "1.21.2", features = ["full"] }
|
||||
tokio-tungstenite = "0.17.2"
|
||||
futures = "0.3.24"
|
||||
lazy_static = "1.4.0"
|
||||
52
doc/flow.txt
Normal file
52
doc/flow.txt
Normal file
@@ -0,0 +1,52 @@
|
||||
Sign on process:
|
||||
|
||||
New client
|
||||
client server
|
||||
HELO
|
||||
SIGNUP
|
||||
PLAYER_CODE
|
||||
|
||||
Existing client
|
||||
client server
|
||||
HELO
|
||||
LOGIN
|
||||
LOGIN_OK
|
||||
|
||||
Expired login
|
||||
client server
|
||||
HELO
|
||||
LOGIN
|
||||
PLAYER_CODE
|
||||
|
||||
Anything else
|
||||
client server
|
||||
HELO
|
||||
HOST
|
||||
ERROR
|
||||
<Close Frame>
|
||||
This is the only scenario where an error is followed by
|
||||
an immediate close frame.
|
||||
|
||||
Room join process:
|
||||
|
||||
Create new room
|
||||
client server
|
||||
HOSTJOIN
|
||||
HOST
|
||||
ROOM_CODE
|
||||
|
||||
Join an existing room
|
||||
client server
|
||||
HOSTJOIN
|
||||
JOIN
|
||||
JOIN_OK
|
||||
|
||||
Join an expired room
|
||||
client server
|
||||
HOSTJOIN
|
||||
JOIN
|
||||
BAD_ROOM
|
||||
At this point the client is responsible for procuring
|
||||
a new room code
|
||||
|
||||
|
||||
68
public/index.html
Normal file
68
public/index.html
Normal file
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.6.1.slim.min.js"
|
||||
integrity="sha256-w8CvhFs7iHNVUtnSP0YKEg00p9Ih13rlL9zGqvLdePA="
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<h1 id="roomName">
|
||||
Not Connected
|
||||
</h1>
|
||||
<div>
|
||||
<button id="hostBtn" disabled>Host</button>
|
||||
<input id="roomTxt" type="text">
|
||||
<button id="joinBtn" disabled>Join</button>
|
||||
</div>
|
||||
<textarea readonly></textarea>
|
||||
<div>
|
||||
<input id="chatTxt" type="text" disabled>
|
||||
<button id="chatBtn" disabled>Chat</button>
|
||||
</div>
|
||||
<script>
|
||||
const ws = new WebSocket("ws://localhost:8080");
|
||||
|
||||
ws.addEventListener('open', e => {
|
||||
$("#hostBtn").attr('disabled', false);
|
||||
$("#joinBtn").attr('disabled', false);
|
||||
});
|
||||
|
||||
$("#hostBtn").on('click', e => ws.send("HOST:"));
|
||||
$("#joinBtn").on('click', e => {
|
||||
const roomCode = $('#roomTxt').val();
|
||||
const msg = `JOIN: ${roomCode}`;
|
||||
ws.send(msg);
|
||||
$("#roomName").text(roomCode);
|
||||
});
|
||||
$("#chatBtn").on('click', e => {
|
||||
let text = $('#chatTxt').val();
|
||||
$('#chatTxt').val('');
|
||||
msg = `CHAT: ${text}`;
|
||||
ws.send(msg);
|
||||
});
|
||||
|
||||
ws.addEventListener('message', e => {
|
||||
const msg = e.data;
|
||||
const pos = msg.indexOf(':');
|
||||
const command = msg.substring(0, pos);
|
||||
const args = msg.substring(pos+1).split(',');
|
||||
|
||||
switch(command) {
|
||||
case "ROOM_CODE":
|
||||
$("#roomName").text(args[0]);
|
||||
// Fallthrough
|
||||
case "JOIN_OK":
|
||||
$("#chatTxt").attr('disabled', false);
|
||||
$("#chatBtn").attr('disabled', false);
|
||||
break;
|
||||
case "CHAT":
|
||||
let text = $("textarea").val();
|
||||
text = text += `${args[0]}\n`;
|
||||
$("textarea").val(text);
|
||||
default:
|
||||
console.log("Unhandled message", msg);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
97
src/client.rs
Normal file
97
src/client.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
use futures::{select, FutureExt};
|
||||
|
||||
use crate::{msg, GlobalState};
|
||||
use crate::game::{channel_pair, Channel, GameController};
|
||||
use crate::message::{Message, MessageWebSocket};
|
||||
|
||||
pub struct Client {
|
||||
global_state: Arc<GlobalState>,
|
||||
ws: MessageWebSocket,
|
||||
channel: Option<Channel>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(ws: MessageWebSocket, global_state: Arc<GlobalState>) -> Self {
|
||||
Client {
|
||||
global_state,
|
||||
ws,
|
||||
channel: None,
|
||||
}
|
||||
}
|
||||
pub async fn run(&mut self) {
|
||||
loop {
|
||||
match &mut self.channel {
|
||||
Some(channel) => {
|
||||
select! {
|
||||
msg = channel.rx.recv().fuse() =>
|
||||
self.handle_server_msg(msg.unwrap()).await,
|
||||
msg = self.ws.next().fuse() =>
|
||||
self.handle_client_msg(msg.unwrap()).await,
|
||||
};
|
||||
}
|
||||
None => {
|
||||
let msg = self.ws.next().await;
|
||||
self.handle_client_msg(msg.unwrap()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn handle_client_msg(&mut self, msg: Message) {
|
||||
// HOST and JOIN are handled by the client to set up a connection
|
||||
// TODO add id support for reconnecting users
|
||||
match msg.command.as_str() {
|
||||
"HOST" => {
|
||||
let room_code = self.global_state.code_generator.lock().await.generate();
|
||||
let game_controller = Arc::new(Mutex::new(GameController::default()));
|
||||
|
||||
let (client_channel, server_channel) = channel_pair();
|
||||
self.channel = Some(client_channel);
|
||||
game_controller.lock().await.push(server_channel);
|
||||
|
||||
self.global_state
|
||||
.rooms
|
||||
.lock()
|
||||
.await
|
||||
.insert(room_code.clone(), Arc::clone(&game_controller));
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
game_controller.lock().await.poll().await;
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
});
|
||||
self.ws.send(msg!(ROOM_CODE, room_code)).await.unwrap();
|
||||
}
|
||||
"JOIN" => {
|
||||
let room_code = &msg.args[0];
|
||||
let rooms = self.global_state.rooms.lock().await;
|
||||
let room = rooms.get(room_code);
|
||||
|
||||
match room {
|
||||
Some(room) => {
|
||||
let (client_channel, server_channel) = channel_pair();
|
||||
self.channel = Some(client_channel);
|
||||
room.lock().await.push(server_channel);
|
||||
self.ws.send(msg!(JOIN_OK)).await.unwrap();
|
||||
|
||||
}
|
||||
None => {
|
||||
self.ws.send(msg!(JOIN_INVALID)).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(channel) = &self.channel {
|
||||
// Forward message to the server
|
||||
channel.tx.send(msg).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn handle_server_msg(&mut self, msg: Message) {
|
||||
// Forward the message via websocket
|
||||
self.ws.send(msg).await.unwrap();
|
||||
}
|
||||
}
|
||||
39
src/code_generator.rs
Normal file
39
src/code_generator.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use rand::RngCore;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub struct CodeGenerator {
|
||||
counter: u64,
|
||||
salt: [u8; 32],
|
||||
}
|
||||
|
||||
impl CodeGenerator {
|
||||
pub fn generate(&mut self) -> String {
|
||||
let count = self.counter;
|
||||
self.counter += 1;
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(self.salt);
|
||||
hasher.update(count.to_be_bytes());
|
||||
|
||||
format!("{:x}", hasher.finalize())[..6].to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CodeGenerator {
|
||||
fn default() -> Self {
|
||||
let mut salt = [0; 32];
|
||||
rand::thread_rng().fill_bytes(&mut salt);
|
||||
|
||||
CodeGenerator { counter: 0, salt }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_generate() {
|
||||
let code = CodeGenerator::default().generate();
|
||||
assert_eq!(code.len(), 6);
|
||||
}
|
||||
}
|
||||
69
src/game.rs
Normal file
69
src/game.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::error::TryRecvError;
|
||||
|
||||
use crate::message::Message;
|
||||
|
||||
struct Player {
|
||||
name: String,
|
||||
channel: Option<Channel>,
|
||||
seat: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GameController {
|
||||
players: Vec<Player>,
|
||||
}
|
||||
|
||||
pub struct Channel {
|
||||
pub tx: mpsc::Sender<Message>,
|
||||
pub rx: mpsc::Receiver<Message>,
|
||||
}
|
||||
|
||||
impl GameController {
|
||||
pub fn try_recv(&mut self) -> Result<Message, TryRecvError> {
|
||||
for player in self.players.iter_mut() {
|
||||
if let Some(channel) = &mut player.channel {
|
||||
let res = channel.rx.try_recv();
|
||||
if !(res == Err(TryRecvError::Empty)) {
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(TryRecvError::Empty)
|
||||
}
|
||||
pub async fn broadcast(&mut self, msg: Message) {
|
||||
for player in self.players.iter() {
|
||||
if let Some(channel) = &player.channel {
|
||||
channel.tx.send(msg.clone()).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn push(&mut self, channel: Channel) {
|
||||
let player = Player {
|
||||
name: "Guest".to_string(),
|
||||
channel: Some(channel),
|
||||
seat: None, // TODO
|
||||
};
|
||||
self.players.push(player);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channel_pair() -> (Channel, Channel) {
|
||||
let (atx, brx) = mpsc::channel(32);
|
||||
let (btx, arx) = mpsc::channel(32);
|
||||
(Channel { tx: atx, rx: arx }, Channel { tx: btx, rx: brx })
|
||||
}
|
||||
|
||||
impl GameController {
|
||||
pub async fn poll(&mut self) {
|
||||
if let Ok(msg) = self.try_recv() {
|
||||
self.dispatch(msg).await;
|
||||
}
|
||||
}
|
||||
async fn dispatch(&mut self, msg: Message) {
|
||||
match msg.command.as_str() {
|
||||
"CHAT" => self.broadcast(msg).await,
|
||||
_ => ()
|
||||
};
|
||||
}
|
||||
}
|
||||
18
src/lib.rs
Normal file
18
src/lib.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub mod game;
|
||||
pub mod message;
|
||||
pub mod client;
|
||||
|
||||
use game::GameController;
|
||||
mod code_generator;
|
||||
use code_generator::CodeGenerator;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GlobalState {
|
||||
pub code_generator: Arc<Mutex<CodeGenerator>>,
|
||||
pub rooms: Arc<Mutex<HashMap<String, Arc<Mutex<GameController>>>>>,
|
||||
}
|
||||
111
src/main.rs
111
src/main.rs
@@ -1,95 +1,36 @@
|
||||
use std::net::TcpListener;
|
||||
use std::thread;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::collections::HashMap;
|
||||
use std::{io::Error as IoError, sync::Arc};
|
||||
|
||||
mod message;
|
||||
use message::Message;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use rand::RngCore;
|
||||
use hexland_server::GlobalState;
|
||||
use hexland_server::client::Client;
|
||||
use hexland_server::message::MessageWebSocket;
|
||||
|
||||
use tungstenite::protocol::Message as WsMessage;
|
||||
|
||||
use sha2::{Sha256, Digest};
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), IoError> {
|
||||
let global_state = Arc::new(GlobalState::default());
|
||||
|
||||
fn main() {
|
||||
let code_generator = Arc::new(Mutex::new(CodeGenerator::default()));
|
||||
let server = TcpListener::bind("127.0.0.1:8080").unwrap();
|
||||
// Bind socket
|
||||
let socket = TcpListener::bind("127.0.0.1:8080").await;
|
||||
let listener = socket.expect("Could not bind to localhost:8080");
|
||||
println!("Server running");
|
||||
|
||||
let rooms = Arc::new(Mutex::new(HashMap::new()));
|
||||
// Accept all incoming connections
|
||||
while let Ok((stream, addr)) = listener.accept().await {
|
||||
let global_state = Arc::clone(&global_state);
|
||||
tokio::spawn(async move {
|
||||
// Upgrade to a WS connection
|
||||
let ws = MessageWebSocket(
|
||||
tokio_tungstenite::accept_async(stream)
|
||||
.await
|
||||
.expect("Could not establish connection"),
|
||||
);
|
||||
println!("Connected to {}", addr);
|
||||
Client::new(ws, global_state).run().await;
|
||||
|
||||
for stream in server.incoming() {
|
||||
let code_generator = Arc::clone(&code_generator);
|
||||
let rooms = Arc::clone(&rooms);
|
||||
thread::spawn (move || {
|
||||
let mut ws = tungstenite::accept(stream.unwrap()).unwrap();
|
||||
println!("New client!");
|
||||
ws.write_message(WsMessage::Text("HOSTJOIN:".to_string())).unwrap();
|
||||
loop {
|
||||
let message = ws.read_message();
|
||||
println!("{:?}", message);
|
||||
match message {
|
||||
Err(_) => break,
|
||||
Ok(WsMessage::Close(_)) => break,
|
||||
Ok(WsMessage::Text(msg)) => {
|
||||
let msg = Message::parse(msg.as_str().trim());
|
||||
match msg {
|
||||
Ok(msg) =>
|
||||
match msg.command {
|
||||
"HOST" => {
|
||||
let code = code_generator.lock().unwrap().generate();
|
||||
let mut room = Room::default();
|
||||
let player = Player { name: "Guest".to_string() };
|
||||
room.players.push(player);
|
||||
rooms.lock().unwrap().insert(code.clone(), room);
|
||||
ws.write_message(WsMessage::Text(code)).unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
Err(_) => todo!(),
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
});
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct Room {
|
||||
players: Vec<Player>,
|
||||
}
|
||||
|
||||
struct Player {
|
||||
name: String,
|
||||
}
|
||||
|
||||
struct CodeGenerator {
|
||||
counter: u64,
|
||||
salt: [u8; 32],
|
||||
}
|
||||
|
||||
impl CodeGenerator {
|
||||
fn generate(&mut self) -> String {
|
||||
let count = self.counter;
|
||||
self.counter += 1;
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(self.salt);
|
||||
hasher.update(count.to_be_bytes());
|
||||
|
||||
format!("{:x}", hasher.finalize())[..6].to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CodeGenerator {
|
||||
fn default() -> Self {
|
||||
let mut salt = [0; 32];
|
||||
rand::thread_rng().fill_bytes(&mut salt);
|
||||
|
||||
CodeGenerator {
|
||||
counter: 0,
|
||||
salt,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
132
src/message.rs
132
src/message.rs
@@ -1,7 +1,14 @@
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Message<'a> {
|
||||
pub command: &'a str,
|
||||
pub args: Vec<&'a str>,
|
||||
use std::convert::{From, TryFrom};
|
||||
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use lazy_static::lazy_static;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{tungstenite::Message as WsMessage, WebSocketStream};
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct Message {
|
||||
pub command: String,
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -10,22 +17,30 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum Error {
|
||||
BadParse,
|
||||
UnknownCommand,
|
||||
NonText,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl<'a> Message<'a> {
|
||||
pub fn parse(text: &'a str) -> Result<Message<'a>> {
|
||||
let re = regex::Regex::new(r"^([A-Z_]+):\s*(.*)").unwrap();
|
||||
match re.captures(text) {
|
||||
impl Message {
|
||||
pub fn parse(text: String) -> Result<Message> {
|
||||
use regex::Regex;
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"^([A-Z_]+):\s*(.*)").unwrap();
|
||||
static ref ARGS_RE: Regex = Regex::new(r"((\\,|[^,])+)").unwrap();
|
||||
}
|
||||
match RE.captures(text.as_str()) {
|
||||
Some(captures) => {
|
||||
if captures.len() < 3 {
|
||||
Err(Error::BadParse)
|
||||
} else {
|
||||
let command = captures.get(1).unwrap().as_str();
|
||||
let args = captures.get(2).unwrap().as_str()
|
||||
.split(',')
|
||||
.map(|s| s.trim())
|
||||
.collect();
|
||||
let command = captures[1].to_string();
|
||||
let args = &captures[2];
|
||||
let args = ARGS_RE.captures_iter(args).map(|captures| {
|
||||
captures[1]
|
||||
.replace("\\,", ",")
|
||||
.trim()
|
||||
.to_string()
|
||||
}).collect();
|
||||
Ok(Message { command, args })
|
||||
}
|
||||
}
|
||||
@@ -34,18 +49,95 @@ impl<'a> Message<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WsMessage> for Message {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(ws_message: WsMessage) -> Result<Self> {
|
||||
let text = match ws_message {
|
||||
WsMessage::Text(text) => text,
|
||||
_ => return Err(Error::NonText),
|
||||
};
|
||||
Message::parse(text)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for WsMessage {
|
||||
fn from(message: Message) -> Self {
|
||||
WsMessage::Text(message.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MessageWebSocket(pub WebSocketStream<TcpStream>);
|
||||
|
||||
impl MessageWebSocket {
|
||||
pub async fn next(&mut self) -> Result<Message> {
|
||||
if let Some(Ok(msg)) = self.0.next().await {
|
||||
msg.try_into()
|
||||
} else {
|
||||
Err(Error::Unknown)
|
||||
}
|
||||
}
|
||||
pub async fn send(&mut self, msg: Message) -> Result<()> {
|
||||
if self.0.send(msg.into()).await.is_err() {
|
||||
Err(Error::Unknown)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! msg {
|
||||
( $command:ident) => {
|
||||
{
|
||||
let command = stringify!($command).to_string();
|
||||
let args = vec![];
|
||||
Message { command, args }
|
||||
}
|
||||
};
|
||||
( $command:ident, $( $arg:expr ),*) => {
|
||||
{
|
||||
let command = stringify!($command).to_string();
|
||||
let args = vec![$($arg.to_string()),*];
|
||||
Message { command, args }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Message {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
if self.args.is_empty() {
|
||||
write!(f, "{}:", self.command)
|
||||
} else {
|
||||
let args: Vec<_> = self.args.iter().map(|s| s.replace(",", "\\,")).collect();
|
||||
write!(f, "{}: {}", self.command, args.join(", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_parse() -> Result<()> {
|
||||
// Simple test
|
||||
let text = "COMMAND: arg1, arg2";
|
||||
let msg = Message::parse(text)?;
|
||||
assert_eq!(Message {
|
||||
command: "COMMAND",
|
||||
args: vec!["arg1", "arg2"],
|
||||
}, msg);
|
||||
|
||||
let msg = Message::parse(text.to_string())?;
|
||||
assert_eq!(msg!(COMMAND, "arg1", "arg2"), msg);
|
||||
// With backtick delimited arguments
|
||||
let text = "COMMAND: arg1\\, comment, arg2";
|
||||
let msg = Message::parse(text.to_string())?;
|
||||
assert_eq!(msg!(COMMAND, "arg1, comment", "arg2"), msg);
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn test_to_string() {
|
||||
// Simple test
|
||||
let msg = msg!(COMMAND, "arg1", "arg2");
|
||||
assert_eq!(msg.to_string(), "COMMAND: arg1, arg2".to_string());
|
||||
|
||||
// Escaped commas in arguments
|
||||
let msg = msg!(COMMAND, "arg1, comment", "arg2");
|
||||
assert_eq!(msg.to_string(), "COMMAND: arg1\\, comment, arg2");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ async def test():
|
||||
assert(response == "HOSTJOIN:")
|
||||
await ws.send("HOST:")
|
||||
response = await ws.recv()
|
||||
assert(re.match(r"(\d|[a-f]){6}", response))
|
||||
assert(re.match(r"CODE: [a-f\d]{6}", response))
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(test())
|
||||
print("All tests passed")
|
||||
|
||||
Reference in New Issue
Block a user