initial commit
authorGeoffrey Allott <geoffrey@allott.email>
Wed, 3 Feb 2021 21:41:32 +0000 (21:41 +0000)
committerGeoffrey Allott <geoffrey@allott.email>
Wed, 3 Feb 2021 21:41:32 +0000 (21:41 +0000)
16 files changed:
.gitignore [new file with mode: 0644]
Cargo.lock [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
cert/cert.pem [new file with mode: 0644]
cert/key.pem [new file with mode: 0644]
site/404.html [new file with mode: 0644]
site/index.html [new file with mode: 0644]
site/main.js [new file with mode: 0644]
src/api.rs [new file with mode: 0644]
src/auth.rs [new file with mode: 0644]
src/card.rs [new file with mode: 0644]
src/client.rs [new file with mode: 0644]
src/game.rs [new file with mode: 0644]
src/gamestate.rs [new file with mode: 0644]
src/main.rs [new file with mode: 0644]
src/server.rs [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ea8c4bf
--- /dev/null
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644 (file)
index 0000000..a6cc0e8
--- /dev/null
@@ -0,0 +1,1979 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "aead"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
+dependencies = [
+ "aes-soft",
+ "aesni",
+ "cipher",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
+name = "aes-soft"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
+dependencies = [
+ "cipher",
+ "opaque-debug",
+]
+
+[[package]]
+name = "aesni"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
+dependencies = [
+ "cipher",
+ "opaque-debug",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1"
+
+[[package]]
+name = "async-attributes"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-channel"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-dup"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7427a12b8dc09291528cfb1da2447059adb4a257388c2acd6497a79d55cf6f7c"
+dependencies = [
+ "futures-io",
+ "simple-mutex",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+ "vec-arena",
+]
+
+[[package]]
+name = "async-global-executor"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-io",
+ "async-mutex",
+ "blocking",
+ "futures-lite",
+ "num_cpus",
+ "once_cell",
+]
+
+[[package]]
+name = "async-h1"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5c68a75f812ff0f299e142c06dd0c34e3295a594d935e61eeb6c77041d1d4dc"
+dependencies = [
+ "async-channel",
+ "async-dup",
+ "async-std",
+ "byte-pool",
+ "futures-core",
+ "http-types",
+ "httparse",
+ "lazy_static",
+ "log",
+ "pin-project",
+]
+
+[[package]]
+name = "async-io"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd"
+dependencies = [
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "libc",
+ "log",
+ "nb-connect",
+ "once_cell",
+ "parking",
+ "polling",
+ "vec-arena",
+ "waker-fn",
+ "winapi",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1996609732bde4a9988bc42125f55f2af5f3c36370e27c778d5191a4a1b63bfb"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-mutex"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-process"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8cea09c1fb10a317d1b5af8024eeba256d6554763e85ecd90ff8df31c7bbda"
+dependencies = [
+ "async-io",
+ "blocking",
+ "cfg-if 0.1.10",
+ "event-listener",
+ "futures-lite",
+ "once_cell",
+ "signal-hook 0.1.17",
+ "winapi",
+]
+
+[[package]]
+name = "async-sse"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53bba003996b8fd22245cd0c59b869ba764188ed435392cf2796d03b805ade10"
+dependencies = [
+ "async-channel",
+ "async-std",
+ "http-types",
+ "log",
+ "memchr",
+ "pin-project-lite 0.1.11",
+]
+
+[[package]]
+name = "async-std"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9f06685bad74e0570f5213741bea82158279a4103d988e57bfada11ad230341"
+dependencies = [
+ "async-attributes",
+ "async-channel",
+ "async-global-executor",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "crossbeam-utils 0.8.1",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-lite",
+ "gloo-timers",
+ "kv-log-macro",
+ "log",
+ "memchr",
+ "num_cpus",
+ "once_cell",
+ "pin-project-lite 0.2.4",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "async-task"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
+
+[[package]]
+name = "async-tls"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f23d769dbf1838d5df5156e7b1ad404f4c463d1ac2c6aeb6cd943630f8a8400"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "rustls",
+ "webpki",
+ "webpki-roots",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-tungstenite"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39eca8dd578b18e557361e50ca767df55c5e62f690a5e53868c3c7a8123145b7"
+dependencies = [
+ "futures-io",
+ "futures-util",
+ "log",
+ "pin-project",
+ "tungstenite",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base-x"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
+
+[[package]]
+name = "base64"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "blocking"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
+
+[[package]]
+name = "byte-pool"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38e98299d518ec351ca016363e0cbfc77059dcd08dfa9700d15e405536097a"
+dependencies = [
+ "crossbeam-queue",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
+
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
+[[package]]
+name = "bytes"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
+
+[[package]]
+name = "cache-padded"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
+
+[[package]]
+name = "cc"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cipher"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "combine"
+version = "4.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc4369b5e4c0cddf64ad8981c0111e7df4f7078f4d6ba98fb31f2e17c4c57b7e"
+dependencies = [
+ "bytes 1.0.1",
+ "futures-util",
+ "memchr",
+ "pin-project-lite 0.2.4",
+ "tokio",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
+dependencies = [
+ "cache-padded",
+]
+
+[[package]]
+name = "const_fn"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
+
+[[package]]
+name = "cookie"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f"
+dependencies = [
+ "aes-gcm",
+ "base64 0.12.3",
+ "hkdf",
+ "hmac",
+ "percent-encoding",
+ "rand 0.7.3",
+ "sha2",
+ "time",
+ "version_check",
+]
+
+[[package]]
+name = "cpuid-bool"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
+
+[[package]]
+name = "cpuid-bool"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
+dependencies = [
+ "cfg-if 0.1.10",
+ "crossbeam-utils 0.7.2",
+ "maybe-uninit",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
+dependencies = [
+ "autocfg",
+ "cfg-if 0.1.10",
+ "lazy_static",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
+dependencies = [
+ "autocfg",
+ "cfg-if 1.0.0",
+ "lazy_static",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "ctor"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10bcb9d7dcbf7002aaffbb53eac22906b64cdcc127971dcc387d8eb7c95d5560"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "ctr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "discard"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
+
+[[package]]
+name = "dtoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
+
+[[package]]
+name = "env_logger"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
+
+[[package]]
+name = "fastrand"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500"
+
+[[package]]
+name = "futures-lite"
+version = "1.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite 0.2.4",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6"
+
+[[package]]
+name = "futures-task"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "futures-util"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite 0.2.4",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[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",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.10.2+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "ghash"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
+name = "gloo-timers"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hkdf"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
+dependencies = [
+ "digest",
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
+dependencies = [
+ "crypto-mac",
+ "digest",
+]
+
+[[package]]
+name = "http"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747"
+dependencies = [
+ "bytes 1.0.1",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-client"
+version = "6.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "010092b71b94ee49293995625ce7a607778b8b4099c8088fa84fd66bd3e0f21c"
+dependencies = [
+ "async-trait",
+ "http-types",
+ "log",
+]
+
+[[package]]
+name = "http-types"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32613ebb139d1d430ef5783676f84abfa06fc5f2b4b5a25220cdeeff7e16ef5c"
+dependencies = [
+ "anyhow",
+ "async-channel",
+ "async-std",
+ "base64 0.13.0",
+ "cookie",
+ "futures-lite",
+ "infer",
+ "pin-project-lite 0.2.4",
+ "rand 0.7.3",
+ "serde",
+ "serde_json",
+ "serde_qs",
+ "serde_urlencoded",
+ "url",
+]
+
+[[package]]
+name = "httparse"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "idna"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "infer"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
+
+[[package]]
+name = "input_buffer"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754"
+dependencies = [
+ "bytes 0.5.6",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+
+[[package]]
+name = "js-sys"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kv-log-macro"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+ "value-bag",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
+
+[[package]]
+name = "maybe-uninit"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "nb-connect"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "parking"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pin-project"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pokerwave"
+version = "0.1.0"
+dependencies = [
+ "async-std",
+ "env_logger",
+ "futures",
+ "log",
+ "rand 0.8.3",
+ "redis",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "signal-hook 0.3.4",
+ "signal-hook-async-std",
+ "tide",
+ "tide-rustls",
+ "tide-websockets",
+]
+
+[[package]]
+name = "polling"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "log",
+ "wepoll-sys",
+ "winapi",
+]
+
+[[package]]
+name = "polyval"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd"
+dependencies = [
+ "cpuid-bool 0.2.0",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
+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 0.2.0",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.0",
+ "rand_core 0.6.1",
+ "rand_hc 0.3.0",
+]
+
+[[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",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.1",
+]
+
+[[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",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5"
+dependencies = [
+ "getrandom 0.2.2",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+dependencies = [
+ "rand_core 0.6.1",
+]
+
+[[package]]
+name = "redis"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a6ddfecac9391fed21cce10e83c65fa4abafd77df05c98b1c647c65374ce9b3"
+dependencies = [
+ "async-std",
+ "async-trait",
+ "bytes 1.0.1",
+ "combine",
+ "dtoa",
+ "futures-util",
+ "itoa",
+ "percent-encoding",
+ "pin-project-lite 0.2.4",
+ "sha1",
+ "tokio",
+ "tokio-util",
+ "url",
+]
+
+[[package]]
+name = "regex"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+
+[[package]]
+name = "ring"
+version = "0.16.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "route-recognizer"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56770675ebc04927ded3e60633437841581c285dc6236109ea25fbf3beb7b59e"
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustls"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
+dependencies = [
+ "base64 0.13.0",
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "sct"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "serde"
+version = "1.0.123"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.123"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_qs"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5af82de3c6549b001bec34961ff2d6a54339a87bab37ce901b693401f27de6cb"
+dependencies = [
+ "data-encoding",
+ "percent-encoding",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c"
+dependencies = [
+ "block-buffer",
+ "cfg-if 1.0.0",
+ "cpuid-bool 0.1.2",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha1"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
+
+[[package]]
+name = "sha2"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
+dependencies = [
+ "block-buffer",
+ "cfg-if 1.0.0",
+ "cpuid-bool 0.1.2",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "signal-hook"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "780f5e3fe0c66f67197236097d89de1e86216f1f6fdeaf47c442f854ab46c240"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-async-std"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90526e74631c69a79b38212e3d4fda4b00de9d6be56b3cead133bf67ad371af1"
+dependencies = [
+ "async-io",
+ "futures-lite",
+ "libc",
+ "signal-hook 0.3.4",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "simple-mutex"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38aabbeafa6f6dead8cebf246fe9fae1f9215c8d29b3a69f93bd62a9e4a3dcd6"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "standback"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c66a8cff4fa24853fdf6b51f75c6d7f8206d7c75cab4e467bcd7f25c2b1febe0"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "stdweb"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
+dependencies = [
+ "discard",
+ "rustc_version",
+ "stdweb-derive",
+ "stdweb-internal-macros",
+ "stdweb-internal-runtime",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "stdweb-derive"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "syn",
+]
+
+[[package]]
+name = "stdweb-internal-macros"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
+dependencies = [
+ "base-x",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sha1",
+ "syn",
+]
+
+[[package]]
+name = "stdweb-internal-runtime"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
+
+[[package]]
+name = "subtle"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+
+[[package]]
+name = "sval"
+version = "1.0.0-alpha.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45f6ee7c7b87caf59549e9fe45d6a69c75c8019e79e212a835c5da0e92f0ba08"
+
+[[package]]
+name = "syn"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tide"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c459573f0dd2cc734b539047f57489ea875af8ee950860ded20cf93a79a1dee0"
+dependencies = [
+ "async-h1",
+ "async-sse",
+ "async-std",
+ "async-trait",
+ "futures-util",
+ "http-client",
+ "http-types",
+ "kv-log-macro",
+ "log",
+ "pin-project-lite 0.2.4",
+ "route-recognizer",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tide-rustls"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf8521961215f0ebbb27e10c9d610ff2a64f853dc3707c03748350c6dbe022"
+dependencies = [
+ "async-dup",
+ "async-h1",
+ "async-std",
+ "async-tls",
+ "rustls",
+ "tide",
+]
+
+[[package]]
+name = "tide-websockets"
+version = "0.1.0"
+source = "git+https://github.com/http-rs/tide-websockets?branch=main#c7f5fc6fd81bb592d6f55f09bddc62cc7a60bdeb"
+dependencies = [
+ "async-dup",
+ "async-std",
+ "async-tungstenite",
+ "base64 0.13.0",
+ "futures-util",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "sha-1",
+ "tide",
+]
+
+[[package]]
+name = "time"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1195b046942c221454c2539395f85413b33383a067449d78aab2b7b052a142f7"
+dependencies = [
+ "const_fn",
+ "libc",
+ "standback",
+ "stdweb",
+ "time-macros",
+ "version_check",
+ "winapi",
+]
+
+[[package]]
+name = "time-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
+dependencies = [
+ "proc-macro-hack",
+ "time-macros-impl",
+]
+
+[[package]]
+name = "time-macros-impl"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "standback",
+ "syn",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6714d663090b6b0acb0fa85841c6d66233d150cdb2602c8f9b8abb03370beb3f"
+dependencies = [
+ "autocfg",
+ "bytes 1.0.1",
+ "memchr",
+ "pin-project-lite 0.2.4",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b"
+dependencies = [
+ "bytes 1.0.1",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite 0.2.4",
+ "tokio",
+]
+
+[[package]]
+name = "tungstenite"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23"
+dependencies = [
+ "base64 0.12.3",
+ "byteorder",
+ "bytes 0.5.6",
+ "http",
+ "httparse",
+ "input_buffer",
+ "log",
+ "rand 0.7.3",
+ "sha-1",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "typenum"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+dependencies = [
+ "matches",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "universal-hash"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "url"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
+
+[[package]]
+name = "value-bag"
+version = "1.0.0-alpha.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b676010e055c99033117c2343b33a40a30b91fecd6c49055ac9cd2d6c305ab1"
+dependencies = [
+ "ctor",
+ "sval",
+]
+
+[[package]]
+name = "vec-arena"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
+[[package]]
+name = "waker-fn"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94"
+dependencies = [
+ "cfg-if 1.0.0",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64"
+
+[[package]]
+name = "web-sys"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376"
+dependencies = [
+ "webpki",
+]
+
+[[package]]
+name = "wepoll-sys"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..521a4f8
--- /dev/null
@@ -0,0 +1,21 @@
+[package]
+name = "pokerwave"
+version = "0.1.0"
+authors = ["Geoffrey Allott <geoffrey@allott.email>"]
+edition = "2018"
+
+[dependencies]
+async-std = { version = "1", features = ["attributes"] }
+env_logger = "0.8"
+log = "0.4"
+futures = "0.3"
+rand = "0.8"
+redis = { version = "0.19", features = ["async-std-comp"] }
+serde = "1"
+serde_derive = "1"
+serde_json = "1"
+signal-hook = "0.3"
+signal-hook-async-std = "0.2"
+tide = { version = "0.16.0", default-features = false, features = ["h1-server"] }
+tide-rustls = "0.2"
+tide-websockets = { git = "https://github.com/http-rs/tide-websockets", branch = "main" }
diff --git a/cert/cert.pem b/cert/cert.pem
new file mode 100644 (file)
index 0000000..78c58ae
--- /dev/null
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEKTCCApGgAwIBAgIRANSpSYVi2aOvByhI0RgGwRAwDQYJKoZIhvcNAQELBQAw
+VzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMRYwFAYDVQQLDA1nZW9m
+ZkBHZW9mZlBDMR0wGwYDVQQDDBRta2NlcnQgZ2VvZmZAR2VvZmZQQzAeFw0yMTAx
+MzEyMzIxMDhaFw0yMzA1MDEyMjIxMDhaMEExJzAlBgNVBAoTHm1rY2VydCBkZXZl
+bG9wbWVudCBjZXJ0aWZpY2F0ZTEWMBQGA1UECwwNZ2VvZmZAR2VvZmZQQzCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzdzX/aPttLL+WGa5E1T3d6jmGQ
+TLElkRUZtGbYbR0PmVj2uBbIPhwe9n7R4QcY6MroGZiem5/tO+/bAvz4x6dDHFww
+//wCzsBZaO+KgqC5839JRyRsHyoLVZfcv/IbJDh52Nubs7ursiDDCo2Vtv+Y0lV4
+iMKxzy882g13Jkfhv7Of+kWlABA/LS+CEjKbbCKI9W7zIl5RvdNXN0dPNws6p5gN
+G96ZIhHWQ0Ah+RYw5bcOzu5AkSlFpUfKBaSyXi0wlCjdglFe4hwCEHU0SwM8qzgX
+WXcp6U+QfDmWg1vb9Vfd7tUUzk6LjhIqR72slMCtmm2AhXkk0nTTnViFLUUCAwEA
+AaOBhTCBgjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYD
+VR0TAQH/BAIwADAfBgNVHSMEGDAWgBQle3YBraPadyDsV2cbZUG6rxEYEDAsBgNV
+HREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI
+hvcNAQELBQADggGBAJ+mFs0I2/TDv+6Ni19++I2D3qUGcb3nM7RXUOHMrzcVcQHq
+eE+MoNLtyVjZZFqBzZox+RlCCWbHAfqFYblpL27crLXcttDyXIdeQGjuGwE4NGe9
+xi1rlvt0A9IPfrli6SKdH24vijorryXF+GOToBXM7dS8bg1fra5g7uiQgoFhToQp
+TH6eEO8y9X3qgCtY1EcWkGIchxfM0qPhwP+zqe7ZTU8XdCQZqBL53DTVj5HmvOd5
+rGB/zJ1C4X6QJ+WivM6228bSm6vyxSj4zx2RM1Fs4LfPBEzd14vyxXJrbIjDk6Be
+9V8lY3taYhw0GCslGLtUua8rBrvec+XpMrViJ361pMNvG7BhIseg4BET0DA9Er2/
+gZ67hI9gAfHHs8M2SeZUPJdMHMU3FMtpfA3fPBiHKn1gx0/CRx1Yt47VBobVHth9
+con+wkUOne/kXPDLzWiUyOAdjREN84dM/nwmTcKjPJbzoAUzAF/Zs8ZWUH60Tn7T
+eaU0yb10XMxl40b05w==
+-----END CERTIFICATE-----
diff --git a/cert/key.pem b/cert/key.pem
new file mode 100644 (file)
index 0000000..ea649d7
--- /dev/null
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDM3c1/2j7bSy/l
+hmuRNU93eo5hkEyxJZEVGbRm2G0dD5lY9rgWyD4cHvZ+0eEHGOjK6BmYnpuf7Tvv
+2wL8+MenQxxcMP/8As7AWWjvioKgufN/SUckbB8qC1WX3L/yGyQ4edjbm7O7q7Ig
+wwqNlbb/mNJVeIjCsc8vPNoNdyZH4b+zn/pFpQAQPy0vghIym2wiiPVu8yJeUb3T
+VzdHTzcLOqeYDRvemSIR1kNAIfkWMOW3Ds7uQJEpRaVHygWksl4tMJQo3YJRXuIc
+AhB1NEsDPKs4F1l3KelPkHw5loNb2/VX3e7VFM5Oi44SKke9rJTArZptgIV5JNJ0
+051YhS1FAgMBAAECggEANlXluTFDVppce73pLbw79W518P56uMlD75q/tD9WwUxv
+2Z6mOLSndIHp0r0Lt/wtUhO5Jjd7AzHCod0ur7W0DnLIRNNiMY71EyMjp9TOzJ5F
+LM9cpRZgNaCH9R1Bc48rTidTEvv68Z2aQwI3/FFnFeeMccrkSu8qCldTZhrh9lI5
+6of9yedK5Vv6tcbEvW4MRSWS9E9QuqWogDKklAYJggX3sEsDHt6cEeqkUxGIayHm
+kUrYUEFAj+pLktGALSLO7ByrfPMtnZVLMfu366y5vYIUbhf79sWdMGsGI+/g/2bm
+0NGTBpnWnLwpFQKbhYe09CKsmXqmsdhch6/yK+N9CQKBgQDWF7yGPjJqBepn79jq
+Q0IcvoRCt6JO0729jTm57Gu1NZSiM71dmylOy6JMxmRMcYnmEfk+xmm/3ANifo70
+iX1zw8gyrDKYHp0t0kr5BfaMph1W2OYgP1YzV97KlN0+FiZmaLjq9FQUsS+Tr/aR
+5E0h3HQXCIuM/go3+j6OrINxMwKBgQD097uf1NOhjEbs3snYIuoPZStfPXuUruu7
+nVinpMi9IMxCRecf/9b+tranidNrmFXLIxrIV0x7NwHRt+ykFMVWHi8JTVKKCyAR
+rGDnyLfgNHngozqeedBpjxBqWKstTk/Qn+chggKonDqE3gngFi5PORAO5QMbWWQX
+C+jzC3pXpwKBgH3U76pbLhhgp7g1IWH27Amm9GeeNNht3SxmsBIV4exctKJtFJTK
+2ImGaDsFs/e2F93QnJUH2ym374lZZz5U/RYocSdPTGFaRPmkiwo4NgPOspnT29YC
+Q/DjaX/Z5PtT23f/fwghWLXcumDsYFi5PZh0UQskq5jPy0PBYjlLLeo/AoGBAN+z
+BqRLTsM8fVw7iqkUIRfT9Epxs9Ov9NcMNfRJV4LWW5C9kvU2xVcu9ReDlgywPNBX
+C1Md3Vq7fa7MvY2M5jPhNmTRNmmXBT7+YjPnqHpWkWGgzZwAc2Ch44Sp2g9ybxJ9
+oyyHM/RTTmEc/nmi58Eyw8ZGPZMRC4S8PIsN1TgLAoGAdc8i9zGBp+PwjTIeLZry
+byP9GQV6dsBcCFswuGICqO98SdWPVzdTQj7j/9Jnv48FKVEli/KR27POrbIQ+EZY
+20IRywZLe+QB+dAs0SapLRJjq7jABoGgF12ol6R04as2ZgIBBz/cdndhFghjEO4d
+Unvj9zDM6L67LknR+PyaD2c=
+-----END PRIVATE KEY-----
diff --git a/site/404.html b/site/404.html
new file mode 100644 (file)
index 0000000..8268b39
--- /dev/null
@@ -0,0 +1,9 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8" />
+    </head>
+    <body>
+        <h1>404 Not Found</h1>
+    </body>
+</html>
diff --git a/site/index.html b/site/index.html
new file mode 100644 (file)
index 0000000..da7e8f7
--- /dev/null
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8" />
+        <script src="main.js"></script>
+    </head>
+    <body>
+        <h1>Pokerwave</h1>
+    </body>
+</html>
diff --git a/site/main.js b/site/main.js
new file mode 100644 (file)
index 0000000..a99a9ff
--- /dev/null
@@ -0,0 +1,35 @@
+"use strict";
+
+var socket;
+
+function socket_open() {
+    let proto = window.location.protocol === "https:" ? "wss:" : "ws:";
+    let uri = proto + "//" + window.location.host + "/api";
+    socket = new WebSocket(uri);
+    socket.onopen = socket_onopen;
+    socket.onmessage = socket_onmessage;
+}
+
+function socket_onopen() {
+    console.log("WebSocket connected");
+    socket_send({type: "Login", username: "geoff"});
+}
+
+function socket_send(object) {
+    console.log(">>", object);
+    socket.send(JSON.stringify(object));
+}
+
+function socket_onmessage(msg) {
+    var message = JSON.parse(msg.data);
+    console.log("<<", message);
+    switch (message.type) {
+        case "LoginAuthChallenge":
+            socket_send({type: "LoginAuthResponse", signature: "hunter2"});
+            break;
+    }
+}
+
+window.onload = function() {
+    socket_open();
+};
diff --git a/src/api.rs b/src/api.rs
new file mode 100644 (file)
index 0000000..17cb91d
--- /dev/null
@@ -0,0 +1,57 @@
+use crate::auth::Auth;
+use crate::game::{Action, Game, GameSettings, GameSummary, UserAction};
+
+#[derive(Debug, Clone, Deserialize)]
+enum Scope {
+    Global,
+    Game { id: u32 },
+    Hand { id: u32 },
+    Player { username: String },
+}
+
+#[derive(Debug, Clone, Deserialize)]
+#[serde(tag = "type")]
+pub enum ClientMessage {
+    CreateUser { username: String, auth: Auth, nickname: String },
+    Login { username: String },
+    LoginAuthResponse { signature: String },
+    ChangeAuth { auth: Auth },
+    ChangeNickname { nickname: String },
+    Logout,
+    CreateGame { settings: GameSettings },
+    GetGameList { tags: Vec<String> },
+    JoinGame { id: u32 },
+    TakeAction { action: Action },
+    SendMessage { scope: Scope, message: String },
+    LeaveGame,
+    GetHandHistory { scope: Scope },
+}
+
+#[derive(Debug, Clone, Serialize)]
+#[serde(tag = "type")]
+pub enum ServerMessage {
+    CreateUserSuccess,
+    CreateUserFailure { reason: String },
+    LoginAuthChallenge { challenge: String },
+    LoginSuccess,
+    LoginFailure { reason: String },
+    ChangeAuthSuccess,
+    ChangeAuthFailure { reason: String },
+    ChangeNicknameSuccess,
+    ChangeNicknameFailure { reason: String },
+    LogoutSuccess,
+    CreateGameSuccess { id: u32 },
+    CreateGameFailure { reason: String },
+    GameList { games: Vec<GameSummary> },
+    GameListFailure { reason: String },
+    JoinGameSuccess { game: Game },
+    JoinGameFailure { reason: String },
+    NewAction { action: UserAction },
+    TakeActionSuccess,
+    TakeActionFailure { reason: String },
+    NewMessage { username: String, message: String },
+    LeaveGameSuccess,
+    LeaveGameFailure { reason: String },
+    HandHistory { games: Vec<Game> },
+    ProtocolError { reason: String },
+}
diff --git a/src/auth.rs b/src/auth.rs
new file mode 100644 (file)
index 0000000..c25f8d2
--- /dev/null
@@ -0,0 +1,15 @@
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "method")]
+pub enum Auth {
+    NoLogin,
+    Plain { password: String },
+}
+
+impl Auth {
+    pub fn verify(&self, challenge: &str, signature: &str) -> bool {
+        match self {
+            Auth::NoLogin => false,
+            Auth::Plain{password} => signature == password,
+        }
+    }
+}
diff --git a/src/card.rs b/src/card.rs
new file mode 100644 (file)
index 0000000..81fe743
--- /dev/null
@@ -0,0 +1,185 @@
+#![allow(non_upper_case_globals)]
+
+use std::fmt::{self, Display, Formatter};
+
+use self::Rank::*;
+use self::Suit::*;
+
+#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Serialize, Deserialize)]
+pub enum Rank {
+    Two = 2,
+    Three = 3,
+    Four = 4,
+    Five = 5,
+    Six = 6,
+    Seven = 7,
+    Eight = 8,
+    Nine = 9,
+    Ten = 10,
+    Jack = 11,
+    Queen = 12,
+    King = 13,
+    Ace = 14,
+}
+
+impl Display for Rank {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.write_str(match *self {
+            Two => "2",
+            Three => "3",
+            Four => "4",
+            Five => "5",
+            Six => "6",
+            Seven => "7",
+            Eight => "8",
+            Nine => "9",
+            Ten => "T",
+            Jack => "J",
+            Queen => "Q",
+            King => "K",
+            Ace => "A",
+        })
+    }
+}
+
+#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Serialize, Deserialize)]
+pub enum Suit {
+    Clubs,
+    Diamonds,
+    Hearts,
+    Spades,
+}
+
+impl Display for Suit {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.write_str(match *self {
+            Clubs => "♣",
+            Diamonds => "♢",
+            Hearts => "♡",
+            Spades => "â™ ",
+        })
+    }
+}
+
+#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Serialize, Deserialize)]
+pub struct Card {
+    pub rank: Rank,
+    pub suit: Suit,
+}
+
+impl Display for Card {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        write!(f, "{}{}", self.rank, self.suit)
+    }
+}
+
+pub const TwoOfClubs: Card = Card{rank: Two, suit: Clubs};
+pub const ThreeOfClubs: Card = Card{rank: Three, suit: Clubs};
+pub const FourOfClubs: Card = Card{rank: Four, suit: Clubs};
+pub const FiveOfClubs: Card = Card{rank: Five, suit: Clubs};
+pub const SixOfClubs: Card = Card{rank: Six, suit: Clubs};
+pub const SevenOfClubs: Card = Card{rank: Seven, suit: Clubs};
+pub const EightOfClubs: Card = Card{rank: Eight, suit: Clubs};
+pub const NineOfClubs: Card = Card{rank: Nine, suit: Clubs};
+pub const TenOfClubs: Card = Card{rank: Ten, suit: Clubs};
+pub const JackOfClubs: Card = Card{rank: Jack, suit: Clubs};
+pub const QueenOfClubs: Card = Card{rank: Queen, suit: Clubs};
+pub const KingOfClubs: Card = Card{rank: King, suit: Clubs};
+pub const AceOfClubs: Card = Card{rank: Ace, suit: Clubs};
+
+pub const TwoOfDiamonds: Card = Card{rank: Two, suit: Diamonds};
+pub const ThreeOfDiamonds: Card = Card{rank: Three, suit: Diamonds};
+pub const FourOfDiamonds: Card = Card{rank: Four, suit: Diamonds};
+pub const FiveOfDiamonds: Card = Card{rank: Five, suit: Diamonds};
+pub const SixOfDiamonds: Card = Card{rank: Six, suit: Diamonds};
+pub const SevenOfDiamonds: Card = Card{rank: Seven, suit: Diamonds};
+pub const EightOfDiamonds: Card = Card{rank: Eight, suit: Diamonds};
+pub const NineOfDiamonds: Card = Card{rank: Nine, suit: Diamonds};
+pub const TenOfDiamonds: Card = Card{rank: Ten, suit: Diamonds};
+pub const JackOfDiamonds: Card = Card{rank: Jack, suit: Diamonds};
+pub const QueenOfDiamonds: Card = Card{rank: Queen, suit: Diamonds};
+pub const KingOfDiamonds: Card = Card{rank: King, suit: Diamonds};
+pub const AceOfDiamonds: Card = Card{rank: Ace, suit: Diamonds};
+
+pub const TwoOfHearts: Card = Card{rank: Two, suit: Hearts};
+pub const ThreeOfHearts: Card = Card{rank: Three, suit: Hearts};
+pub const FourOfHearts: Card = Card{rank: Four, suit: Hearts};
+pub const FiveOfHearts: Card = Card{rank: Five, suit: Hearts};
+pub const SixOfHearts: Card = Card{rank: Six, suit: Hearts};
+pub const SevenOfHearts: Card = Card{rank: Seven, suit: Hearts};
+pub const EightOfHearts: Card = Card{rank: Eight, suit: Hearts};
+pub const NineOfHearts: Card = Card{rank: Nine, suit: Hearts};
+pub const TenOfHearts: Card = Card{rank: Ten, suit: Hearts};
+pub const JackOfHearts: Card = Card{rank: Jack, suit: Hearts};
+pub const QueenOfHearts: Card = Card{rank: Queen, suit: Hearts};
+pub const KingOfHearts: Card = Card{rank: King, suit: Hearts};
+pub const AceOfHearts: Card = Card{rank: Ace, suit: Hearts};
+
+pub const TwoOfSpades: Card = Card{rank: Two, suit: Spades};
+pub const ThreeOfSpades: Card = Card{rank: Three, suit: Spades};
+pub const FourOfSpades: Card = Card{rank: Four, suit: Spades};
+pub const FiveOfSpades: Card = Card{rank: Five, suit: Spades};
+pub const SixOfSpades: Card = Card{rank: Six, suit: Spades};
+pub const SevenOfSpades: Card = Card{rank: Seven, suit: Spades};
+pub const EightOfSpades: Card = Card{rank: Eight, suit: Spades};
+pub const NineOfSpades: Card = Card{rank: Nine, suit: Spades};
+pub const TenOfSpades: Card = Card{rank: Ten, suit: Spades};
+pub const JackOfSpades: Card = Card{rank: Jack, suit: Spades};
+pub const QueenOfSpades: Card = Card{rank: Queen, suit: Spades};
+pub const KingOfSpades: Card = Card{rank: King, suit: Spades};
+pub const AceOfSpades: Card = Card{rank: Ace, suit: Spades};
+
+pub const FIFTY_TWO_CARD_DECK: [Card; 52] = [
+    AceOfSpades,
+    AceOfHearts,
+    AceOfDiamonds,
+    AceOfClubs,
+    KingOfSpades,
+    KingOfHearts,
+    KingOfDiamonds,
+    KingOfClubs,
+    QueenOfSpades,
+    QueenOfHearts,
+    QueenOfDiamonds,
+    QueenOfClubs,
+    JackOfSpades,
+    JackOfHearts,
+    JackOfDiamonds,
+    JackOfClubs,
+    TenOfSpades,
+    TenOfHearts,
+    TenOfDiamonds,
+    TenOfClubs,
+    NineOfSpades,
+    NineOfHearts,
+    NineOfDiamonds,
+    NineOfClubs,
+    EightOfSpades,
+    EightOfHearts,
+    EightOfDiamonds,
+    EightOfClubs,
+    SevenOfSpades,
+    SevenOfHearts,
+    SevenOfDiamonds,
+    SevenOfClubs,
+    SixOfSpades,
+    SixOfHearts,
+    SixOfDiamonds,
+    SixOfClubs,
+    FiveOfSpades,
+    FiveOfHearts,
+    FiveOfDiamonds,
+    FiveOfClubs,
+    FourOfSpades,
+    FourOfHearts,
+    FourOfDiamonds,
+    FourOfClubs,
+    ThreeOfSpades,
+    ThreeOfHearts,
+    ThreeOfDiamonds,
+    ThreeOfClubs,
+    TwoOfSpades,
+    TwoOfHearts,
+    TwoOfDiamonds,
+    TwoOfClubs,
+];
diff --git a/src/client.rs b/src/client.rs
new file mode 100644 (file)
index 0000000..f2006a1
--- /dev/null
@@ -0,0 +1,151 @@
+use std::sync::{Arc, RwLock};
+
+use futures::stream::{Stream, StreamExt, empty, iter, once};
+
+use crate::api::{ClientMessage, ServerMessage};
+use crate::game::{Game, UserAction};
+use crate::server::{ActionStatus, ServerState};
+
+pub struct ConnectionState {
+    server: ServerState,
+    client: ClientState,
+}
+
+#[derive(Debug, Clone)]
+pub enum ClientState {
+    Connected,
+    LoginAuthIssued { username: String, challenge: String },
+    LoggedIn {
+        username: String,
+        state: LoggedInState,
+    },
+}
+
+pub enum ClientUpdate {
+    GameList,
+    Game,
+    Users,
+}
+
+#[derive(Debug, Clone)]
+pub enum LoggedInState {
+    InLobby,
+    InGame { game: Game },
+}
+
+impl ConnectionState {
+    pub fn new(server: ServerState) -> Self {
+        Self {
+            server,
+            client: ClientState::Connected,
+        }
+    }
+
+    pub async fn retrieve_updates(&mut self, update: ClientUpdate) -> impl Stream<Item=ServerMessage> {
+        match update {
+            ClientUpdate::GameList => empty().boxed(), // TODO
+            ClientUpdate::Game => match &mut self.client {
+                ClientState::LoggedIn{ref username, state: LoggedInState::InGame{ref mut game}} => {
+                    let from = game.actions_len();
+                    match self.server.update_game_state(game).await {
+                        Ok(()) => iter(game.update_view_for(username, from)).map(|action| ServerMessage::NewAction{action}).boxed(),
+                        Err(err) => once(async move { ServerMessage::ProtocolError{reason: err.to_string()} }).boxed(),
+                    }
+                }
+                _ => empty().boxed(),
+            }
+            ClientUpdate::Users => empty().boxed(), // TODO
+        }
+    }
+
+    pub async fn apply_message(&mut self, message: ClientMessage) -> ServerMessage {
+        match (&mut self.client, message) {
+            (_, ClientMessage::CreateUser{username, auth, nickname}) => {
+                match self.server.create_user(&username, auth, &nickname).await {
+                    Ok(()) => ServerMessage::CreateUserSuccess,
+                    Err(_) => ServerMessage::CreateUserFailure{reason: "User already exists".to_string()},
+                }
+            }
+            (ClientState::Connected, ClientMessage::Login{username}) => {
+                let challenge = format!("{:032x}{:032x}", rand::random::<u128>(), rand::random::<u128>());
+                self.client = ClientState::LoginAuthIssued{username, challenge: challenge.clone()};
+                ServerMessage::LoginAuthChallenge{challenge}
+            }
+            (ClientState::LoginAuthIssued{username, challenge}, ClientMessage::LoginAuthResponse{signature}) => {
+                if self.server.verify(&username, &challenge, &signature).await {
+                    self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InLobby};
+                    ServerMessage::LoginSuccess
+                } else {
+                    self.client = ClientState::Connected;
+                    ServerMessage::LoginFailure{reason: "Invalid username or password".to_string()}
+                }
+            }
+            (ClientState::LoggedIn{username, ..}, ClientMessage::GetGameList{tags}) => {
+                match self.server.get_game_list().await {
+                    Ok(games) => ServerMessage::GameList{games},
+                    Err(err) => ServerMessage::GameListFailure{reason: err.to_string()},
+                }
+            }
+            (ClientState::LoggedIn{username, ..}, ClientMessage::CreateGame{settings}) => {
+                match self.server.create_game(settings).await {
+                    Ok(id) => ServerMessage::CreateGameSuccess{id},
+                    Err(err) => ServerMessage::CreateGameFailure{reason: err.to_string()},
+                }
+            }
+            (ClientState::LoggedIn{username, state: LoggedInState::InLobby}, ClientMessage::JoinGame{id}) => {
+                match self.server.get_game(id).await {
+                    Ok(game) => {
+                        let game_view = game.view_for(&username);
+                        self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InGame{game}};
+                        ServerMessage::JoinGameSuccess{game: game_view}
+                    }
+                    Err(err) => ServerMessage::JoinGameFailure{reason: err.to_string()},
+                }
+            }
+            (ClientState::LoggedIn{ref username, state: LoggedInState::InGame{ref mut game}}, ClientMessage::TakeAction{action}) => {
+                let action = UserAction{username: username.clone(), action};
+                loop {
+                    let len = game.actions_len();
+                    if let Err(err) = game.verify(&action) {
+                        return ServerMessage::TakeActionFailure{reason: err.to_string()};
+                    }
+                    match self.server.take_action(game.id(), len, &action).await {
+                        Ok(ActionStatus::Committed) => match game.take_action(action) {
+                            Ok(()) => return ServerMessage::TakeActionSuccess,
+                            Err(err) => return ServerMessage::TakeActionFailure{reason: err.to_string()},
+                        }
+                        Ok(ActionStatus::Interrupted) => {
+                            if let Err(err) = self.server.update_game_state(game).await {
+                                return ServerMessage::TakeActionFailure{reason: err.to_string()};
+                            }
+                        }
+                        Err(err) => return ServerMessage::TakeActionFailure{reason: err.to_string()},
+                    }
+                }
+            }
+            (ClientState::LoggedIn{username, state: LoggedInState::InGame{..}}, ClientMessage::LeaveGame) => {
+                self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InLobby};
+                ServerMessage::LeaveGameSuccess
+            }
+            (ClientState::LoggedIn{username, ..}, ClientMessage::Logout) => {
+                self.client = ClientState::Connected;
+                ServerMessage::LogoutSuccess
+            }
+            (ClientState::LoggedIn{username, ..}, ClientMessage::ChangeAuth{auth}) => {
+                match self.server.set_user_auth(&username, auth).await {
+                    Ok(()) => ServerMessage::ChangeAuthSuccess,
+                    Err(err) => ServerMessage::ChangeAuthFailure{reason: err.to_string()},
+                }
+            }
+            (ClientState::LoggedIn{username, ..}, ClientMessage::ChangeNickname{nickname}) => {
+                match self.server.set_user_nickname(&username, &nickname).await {
+                    Ok(()) => ServerMessage::ChangeNicknameSuccess,
+                    Err(err) => ServerMessage::ChangeNicknameFailure{reason: err.to_string()},
+                }
+            }
+            (_, _) => {
+                ServerMessage::ProtocolError{reason: "Protocol error".to_string() }
+            }
+        }
+    }
+}
diff --git a/src/game.rs b/src/game.rs
new file mode 100644 (file)
index 0000000..ce72da5
--- /dev/null
@@ -0,0 +1,151 @@
+use std::fmt::Debug;
+
+use crate::card::Card;
+use crate::gamestate::GameState;
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "format")]
+pub enum GameSettings {
+    Chatroom {max_users: u32},
+    TexasHoldEm {
+        // TODO
+    },
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "action")]
+pub enum Action {
+    Join { seat: u32, chips: u64 },
+    AddOn { chips: u64 },
+    NextToAct,
+    CommunityCard { card: Card },
+    ReceiveCard { card: Option<Card> },
+    RevealCard { card: Card },
+    Fold,
+    Bet { chips: u64 },
+    WinPot { chips: u64 },
+    Message { message: String },
+    Leave,
+}
+
+impl Action {
+    pub fn anonymise(&self) -> Self {
+        match self {
+            Action::ReceiveCard{..} => Action::ReceiveCard{card: None},
+            action => action.clone(),
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct UserAction {
+    pub username: String,
+    pub action: Action,
+}
+
+impl UserAction {
+    pub fn view_for(&self, username: &str) -> Self {
+        Self {
+            username: self.username.clone(),
+            action: if username == self.username { self.action.clone() } else { self.action.anonymise() },
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub enum ActionError {
+    NotAuthorised,
+    AlreadyJoined,
+    NoSeatAvailable,
+    OutOfTurn,
+    InvalidActionForGameType,
+}
+
+use std::fmt::{Display, Formatter};
+
+impl Display for ActionError {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        match self {
+            ActionError::NotAuthorised => f.write_str("NotAuthorised"),
+            ActionError::AlreadyJoined => f.write_str("AlreadyJoined"),
+            ActionError::NoSeatAvailable => f.write_str("NoSeatAvailable"),
+            ActionError::OutOfTurn => f.write_str("OutOfTurn"),
+            ActionError::InvalidActionForGameType => f.write_str("InvalidActionForGameType"),
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct GameSummary {
+    id: u32,
+    settings: GameSettings,
+}
+
+impl GameSummary {
+    pub fn new(id: u32, settings: GameSettings) -> Self {
+        Self{id, settings}
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Game {
+    summary: GameSummary,
+    state: GameState,
+}
+
+impl Game {
+    pub fn new(id: u32, settings: GameSettings) -> Self {
+        Self {
+            summary: GameSummary{id, settings},
+            state: GameState::new(),
+        }
+    }
+
+    pub fn id(&self) -> u32 {
+        self.summary.id
+    }
+
+    pub fn actions_len(&self) -> usize {
+        self.state.actions_len()
+    }
+
+    pub fn view_for(&self, username: &str) -> Game {
+        Game {
+            summary: self.summary.clone(),
+            state: self.state.view_for(username),
+        }
+    }
+
+    pub fn update_view_for(&self, username: &str, from: usize) -> Vec<UserAction> {
+        self.state.update_view_for(username, from)
+    }
+
+    pub fn verify(&self, UserAction{ref username, ref action}: &UserAction) -> Result<(), ActionError> {
+        debug!("Verifying action: UserAction {{ username: {:?}, action: {:?} }}", username, action);
+        match self.summary.settings {
+            GameSettings::Chatroom{max_users} => match action {
+                Action::Join{seat: 0, chips: 0} if self.state.user_has_joined(username) => Err(ActionError::AlreadyJoined),
+                Action::Join{seat: 0, chips: 0} if self.state.num_players() + 1 > max_users => Err(ActionError::NoSeatAvailable),
+                Action::Join{seat: 0, chips: 0} => Ok(()),
+                Action::Message{ref message} if self.state.user_has_joined(username) => Ok(()),
+                Action::Message{..} => Err(ActionError::NotAuthorised),
+                Action::Leave if self.state.user_has_joined(username) => Ok(()),
+                Action::Leave => Err(ActionError::NotAuthorised),
+                _ => Err(ActionError::InvalidActionForGameType),
+            },
+            GameSettings::TexasHoldEm{..} => {
+                // TODO
+                Err(ActionError::NotAuthorised)
+            }
+        }
+    }
+
+    pub fn take_action(&mut self, user_action: UserAction) -> Result<(), ActionError> {
+        self.verify(&user_action)?;
+        debug!("Taking action: {:?}", user_action);
+        debug!("State before: {:?}", self.state);
+        self.state.take_action(user_action);
+        debug!("State after: {:?}", self.state);
+        Ok(())
+    }
+}
diff --git a/src/gamestate.rs b/src/gamestate.rs
new file mode 100644 (file)
index 0000000..c1d1350
--- /dev/null
@@ -0,0 +1,79 @@
+use crate::game::{Action, UserAction};
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct GameState {
+    actions: Vec<UserAction>,
+}
+
+impl GameState {
+    pub fn new() -> Self {
+        Self {
+            actions: Vec::new(),
+        }
+    }
+
+    pub fn actions_len(&self) -> usize {
+        self.actions.len()
+    }
+
+    pub fn view_for(&self, username: &str) -> Self {
+        Self {
+            actions: self.actions.iter().map(|action| action.view_for(username)).collect(),
+        }
+    }
+
+    pub fn update_view_for(&self, username: &str, from: usize) -> Vec<UserAction> {
+        self.actions.iter().skip(from).map(|action| action.view_for(username)).collect()
+    }
+
+    pub fn user_has_joined(&self, user: &str) -> bool {
+        let mut logged_in = false;
+        for action in &self.actions {
+            match action {
+                UserAction{ref username, action: Action::Join{..}} if user == username => logged_in = true,
+                UserAction{ref username, action: Action::Leave} if user == username => logged_in = false,
+                _ => continue,
+            }
+        }
+        logged_in
+    }
+
+    pub fn num_players(&self) -> u32 {
+        let mut num_players = 0;
+        for action in &self.actions {
+            match action.action {
+                Action::Join{..} => num_players += 1,
+                Action::Leave => num_players -= 1,
+                _ => continue,
+            }
+        }
+        num_players
+    }
+
+    pub fn take_action(&mut self, action: UserAction) {
+        self.actions.push(action);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    
+    #[test]
+    fn user_has_joined() {
+        let mut state = GameState::new();
+        state.take_action(UserAction{username: "user".to_string(), action: Action::Join{seat: 0, chips: 10000}});
+        assert!(state.user_has_joined("user"));
+    }
+    
+    #[test]
+    fn num_players() {
+        let mut state = GameState::new();
+        state.take_action(UserAction{username: "user".to_string(), action: Action::Join{seat: 0, chips: 10000}});
+        assert_eq!(1, state.num_players());
+        state.take_action(UserAction{username: "user2".to_string(), action: Action::Join{seat: 1, chips: 10000}});
+        assert_eq!(2, state.num_players());
+        state.take_action(UserAction{username: "user".to_string(), action: Action::Leave});
+        assert_eq!(1, state.num_players());
+    }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644 (file)
index 0000000..25386be
--- /dev/null
@@ -0,0 +1,105 @@
+#[macro_use] extern crate log;
+#[macro_use] extern crate serde_derive;
+
+use std::pin::Pin;
+
+use async_std::prelude::*;
+use futures::{future::select, future::Either, pin_mut, Stream, StreamExt, stream};
+use signal_hook::consts::signal::*;
+use signal_hook_async_std::Signals;
+use tide::{Body, Request, Error, Response, StatusCode};
+use tide::utils::After;
+use tide_rustls::TlsListener;
+use tide_websockets::{Message, WebSocket, WebSocketConnection};
+
+mod api;
+mod auth;
+mod card;
+mod client;
+mod game;
+mod gamestate;
+mod server;
+
+use crate::api::ServerMessage;
+use crate::server::ServerState;
+use crate::client::{ClientUpdate, ConnectionState};
+
+pub async fn handle_websocket_request(request: Request<ServerState>, mut stream: WebSocketConnection) -> Result<(), Error> {
+    let mut client = ConnectionState::new(request.state().clone());
+    let update_stream: Pin<Box<dyn Stream<Item=ClientUpdate> + Send>>;
+    update_stream = stream::empty().boxed(); // TODO
+    let mut combined = stream::select(stream.clone().map(Either::Left), update_stream.map(Either::Right));
+    while let Some(message) = combined.next().await {
+        let message: Either<Result<Message, tide_websockets::Error>, ClientUpdate> = message;
+        match message {
+            Either::Left(Ok(Message::Text(input))) => {
+                let response = match serde_json::from_str(&input) {
+                    Ok(message) => client.apply_message(message).await,
+                    Err(err) => ServerMessage::ProtocolError{reason: err.to_string()},
+                };
+                stream.send_json(&response).await?;
+            }
+            Either::Right(update) => {
+                let mut responses = client.retrieve_updates(update).await;
+                while let Some(response) = responses.next().await {
+                    stream.send_json(&response).await?
+                }
+            }
+            Either::Left(Ok(_)) => {
+                error!("Websocket received non-text");
+                break;
+            }
+            Either::Left(Err(err)) => {
+                error!("Websocket error: {}", err);
+                break;
+            }
+        }
+    }
+    Ok(())
+}
+
+async fn handle_signals(mut signals: Signals) -> Result<(), std::io::Error> {
+    signals.next().await;
+    info!("Shutting down...");
+    Ok(())
+}
+
+async fn serve_404(response: Response) -> Result<Response, Error> {
+    match response.status() {
+        StatusCode::NotFound =>
+            Ok(Response::builder(404)
+                .body(Body::from_file("site/404.html").await?)
+                .content_type("text/html")
+                .build()),
+        _ => Ok(response)
+    }
+}
+
+#[async_std::main]
+async fn main() -> Result<(), Error> {
+    env_logger::init();
+
+    let mut app = tide::with_state(ServerState::new("redis://localhost/").await?);
+
+    app.at("/").serve_dir("site/")?;
+    app.at("/").serve_file("site/index.html")?;
+    app.at("/api").get(WebSocket::new(handle_websocket_request));
+    app.with(After(serve_404));
+
+    let signals = Signals::new(&[SIGINT])?;
+    let signal_handler = handle_signals(signals);
+
+    let listener = TlsListener::build()
+        .addrs("localhost:4433")
+        .cert("cert/cert.pem")
+        .key("cert/key.pem");
+
+    let app = app.listen(listener);
+    pin_mut!(signal_handler, app);
+
+    select(app, signal_handler).await.factor_first().0?;
+
+    info!("Pokerwave shut down gracefully.");
+
+    Ok(())
+}
diff --git a/src/server.rs b/src/server.rs
new file mode 100644 (file)
index 0000000..54d5f03
--- /dev/null
@@ -0,0 +1,170 @@
+use futures::Stream;
+use redis::{AsyncCommands, Client, ErrorKind, FromRedisValue, IntoConnectionInfo, Msg, RedisError, RedisResult, RedisWrite, ToRedisArgs, Value, aio::MultiplexedConnection};
+use serde::{Serialize, Deserialize};
+
+use crate::auth::Auth;
+use crate::game::{Game, GameSettings, GameSummary, UserAction};
+
+#[derive(Clone)]
+pub struct ServerState {
+    client: Client,
+    redis: MultiplexedConnection,
+}
+
+fn user_key(username: &str) -> String {
+    format!("user:{}", username)
+}
+
+fn game_key(id: u32) -> String {
+    format!("game:{}", id)
+}
+
+const TAKE_ACTION_LUA_SCRIPT: &'static str = r#"
+    local len = redis.call('llen', KEYS[1])
+    local expected = tonumber(ARGV[1])
+    if (len == expected) then
+        return redis.call('rpush', KEYS[1], ARGV[2])
+    elseif (len > expected) then
+        return 0
+    else
+        local error = string.format('Inconsistent game state - database reports %d actions but expected at least %d', len, expected)
+        return redis.error_reply(error)
+    end
+"#;
+
+impl ServerState {
+    pub async fn new<T: IntoConnectionInfo>(params: T) -> RedisResult<Self> {
+        let client = Client::open(params)?;
+        let redis = client.get_multiplexed_async_std_connection().await?;
+        Ok(Self{client, redis})
+    }
+
+    pub async fn create_user(&mut self, username: &str, auth: Auth, nickname: &str) -> RedisResult<()> {
+        let key = user_key(username);
+        if self.redis.hset_nx::<_, _, _, i32>(&key, "auth", AsJson(auth)).await? == 0 {
+            return Err(RedisError::from((ErrorKind::ResponseError, "User already exists")));
+        }
+        self.redis.hset(&key, "nickname", username).await
+    }
+
+    pub async fn set_user_auth(&mut self, username: &str, auth: Auth) -> RedisResult<()> {
+        let key = user_key(username);
+        self.redis.hset(key, "auth", AsJson(auth)).await
+    }
+
+    async fn get_user_auth(&mut self, username: &str) -> Option<Auth> {
+        let key = user_key(username);
+        self.redis.hget(key, "auth").await.ok().map(AsJson::get)
+    }
+
+    pub async fn set_user_nickname(&mut self, username: &str, nickname: &str) -> RedisResult<()> {
+        let key = user_key(username);
+        self.redis.hset(key, "nickname", nickname).await
+    }
+
+    pub async fn verify(&mut self, username: &str, challenge: &str, signature: &str) -> bool {
+        match self.get_user_auth(username).await {
+            None => false,
+            Some(auth) => auth.verify(challenge, signature),
+        }
+    }
+
+    pub async fn create_game(&mut self, settings: GameSettings) -> RedisResult<u32> {
+        self.redis.rpush("games", AsJson(settings)).await.map(|i: u32| i - 1)
+    }
+
+    pub async fn get_game_list(&mut self) -> RedisResult<Vec<GameSummary>> {
+        const GAME_LIST_BLOCK_SIZE: isize = 1024;
+        let mut ret = Vec::new();
+        for i in (0..).step_by(GAME_LIST_BLOCK_SIZE as usize) {
+            let games: Vec<AsJson<GameSettings>> = self.redis.lrange("games", i, i + GAME_LIST_BLOCK_SIZE).await?;
+            if games.is_empty() { break; }
+            for (j, AsJson(settings)) in games.into_iter().enumerate() {
+                ret.push(GameSummary::new(i as u32 + j as u32, settings));
+            }
+        }
+        Ok(ret)
+    }
+
+    pub async fn update_game_state(&mut self, game: &mut Game) -> RedisResult<()> {
+        const GAME_ACTION_BLOCK_SIZE: isize = 1024;
+        let key = game_key(game.id());
+        for i in (game.actions_len() as isize..).step_by(GAME_ACTION_BLOCK_SIZE as usize) {
+            let actions: Vec<AsJson<UserAction>> = self.redis.lrange(&key, i, i + GAME_ACTION_BLOCK_SIZE).await?;
+            if actions.is_empty() { break; }
+            for AsJson(action) in actions {
+                game.take_action(action).map_err(|err| RedisError::from((ErrorKind::ResponseError, "Invalid action")))?;
+            }
+        }
+        Ok(())
+    }
+
+    pub async fn get_game(&mut self, id: u32) -> RedisResult<Game> {
+        let settings = self.redis.lindex("games", id as isize).await.map(AsJson::get)?;
+        let mut game = Game::new(id, settings);
+        self.update_game_state(&mut game).await?;
+        Ok(game)
+    }
+
+    pub async fn take_action(&mut self, id: u32, len: usize, action: &UserAction) -> RedisResult<ActionStatus> {
+        let key = game_key(id);
+        debug!("redis: executing: EVAL {:?} 1 {:?} {} {:?}", TAKE_ACTION_LUA_SCRIPT, key, len, serde_json::to_string(&action).unwrap());
+        redis::cmd("EVAL").arg(TAKE_ACTION_LUA_SCRIPT).arg(1).arg(key).arg(len).arg(AsJson(action)).query_async(&mut self.redis).await
+    }
+}
+
+pub enum ActionStatus {
+    Committed,
+    Interrupted,
+}
+
+impl FromRedisValue for ActionStatus {
+    fn from_redis_value(value: &Value) -> RedisResult<Self> {
+        match value {
+            Value::Int(0) => Ok(ActionStatus::Interrupted),
+            Value::Int(_) => Ok(ActionStatus::Committed),
+            _ => Err(RedisError::from((ErrorKind::TypeError, "ActionStatus may only be converted from an integer")))
+        }
+    }
+}
+
+struct AsJson<T>(T);
+
+impl<T> AsJson<T> {
+    pub fn get(self) -> T {
+        self.0
+    }
+}
+
+impl<T> FromRedisValue for AsJson<T>
+    where T: for<'a> Deserialize<'a>
+{
+    fn from_redis_value(value: &Value) -> RedisResult<Self> {
+        match value {
+            Value::Data(ref bytes) => serde_json::from_slice(bytes).map(AsJson)
+                .map_err(|err| RedisError::from((
+                    ErrorKind::TypeError,
+                    "Failed to parse as JSON",
+                    err.to_string()
+                ))),
+            _ => Err(RedisError::from((ErrorKind::TypeError, "Value was not Value::Data"))),
+        }
+    }
+}
+
+impl<T> ToRedisArgs for AsJson<T>
+    where T: Serialize
+{
+    fn write_redis_args<W>(&self, out: &mut W)
+        where W: ?Sized + RedisWrite
+    {
+        match serde_json::to_vec(&self.0) {
+            Ok(bytes) => out.write_arg(&bytes),
+            Err(_) => return,
+        }
+    }
+
+    fn is_single_arg(&self) -> bool {
+        true
+    }
+}