From c68dc16bef476203a4424c4b857f16eeb8f0e119 Mon Sep 17 00:00:00 2001 From: stale Date: Fri, 1 Jul 2022 15:15:05 -0300 Subject: initial commit, but with scarequotes --- .gitignore | 4 + Cargo.lock | 1501 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 17 + assets/VT323-Regular.ttf | Bin 0 -> 149688 bytes assets/client.js | 267 +++++++++ assets/index.html | 132 ++++ assets/room.html | 30 + assets/style.css | 86 +++ conf.json.sample | 4 + src/conn.rs | 191 ++++++ src/livepos.rs | 78 +++ src/main.rs | 328 ++++++++++ src/minesweeper.rs | 286 +++++++++ src/types.rs | 135 +++++ 14 files changed, 3059 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 assets/VT323-Regular.ttf create mode 100644 assets/client.js create mode 100644 assets/index.html create mode 100644 assets/room.html create mode 100644 assets/style.css create mode 100644 conf.json.sample create mode 100644 src/conn.rs create mode 100644 src/livepos.rs create mode 100644 src/main.rs create mode 100644 src/minesweeper.rs create mode 100644 src/types.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dc1483 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +conf.json +cert.pem +cert.rsa diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..40fa6fb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1501 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ammonia" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5ed2509ee88cc023cccee37a6fab35826830fe8b748b3869790e7720c2c4a74" +dependencies = [ + "html5ever", + "maplit", + "once_cell", + "tendril", + "url", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[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 = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.7.3", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" + +[[package]] +name = "headers" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha-1 0.10.0", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[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.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand", + "safemem", + "tempfile", + "twoway", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[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 = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "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 = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "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-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1 0.9.8", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-normalization" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dee68f85cab8cf68dec42158baf3a79a1cdc065a8b103025965d6ccb7f6cbd" +dependencies = [ + "tinyvec", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multipart", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-tungstenite", + "tokio-util 0.6.10", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "web-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +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 = "websweeper" +version = "1.1.0" +dependencies = [ + "ammonia", + "flate2", + "futures", + "rand", + "serde", + "serde_json", + "tokio", + "warp", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3c68a1b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "websweeper" +version = "1.1.0" +authors = ["stale "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1", features = ["full"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1.0" +flate2 = "1.0" +warp = { version = "0.3", features = ["tls", "websocket"] } +rand = "0.8" +futures = "0.3" +ammonia = "3" diff --git a/assets/VT323-Regular.ttf b/assets/VT323-Regular.ttf new file mode 100644 index 0000000..6aec599 Binary files /dev/null and b/assets/VT323-Regular.ttf differ diff --git a/assets/client.js b/assets/client.js new file mode 100644 index 0000000..174628f --- /dev/null +++ b/assets/client.js @@ -0,0 +1,267 @@ +window.player = { uid: NaN }; +window.info_elem = document.getElementById("miscinfo"); +window.identform = document.getElementById("identform"); +window.statusline = document.getElementsByClassName("statusline")[0]; +window.bcont_elem = document.getElementById("board-container"); +window.board_elem = document.getElementById("board"); +window.cursor_frame = document.getElementById("cursor-frame"); +window.queued_pos = undefined; + +window.room = { + name: undefined, + bconf: { w: NaN, h: NaN, tile_w: NaN, tile_h: NaN, mine_ratio: undefined }, + board: {}, + cbounds: {}, + socket: undefined, + last_packet: undefined, + identity: JSON.parse(localStorage.getItem("identity")), + cursors: new Map(), +}; + + +if (room.identity == null) { + statusline.style.display = "none"; + identform.style.display = "initial"; +} else { + join(); +} + +function join() { + if (room.identity == null) { + room.identity = {}; + room.identity.name = document.getElementById("name-in").value; + room.identity.clr = document.getElementById("clr-in").value; + localStorage.setItem("identity", JSON.stringify(room.identity)); + } + identform.style.display = "none"; + room.socket = connect(); + statusline.style.display = "flex"; +} +function clear_ident() { + localStorage.removeItem("identity"); + document.location.reload(); +} + +function connect() { + let wsproto = (window.location.protocol == "https:")? "wss:": "ws:"; + let s = new WebSocket(`${wsproto}//${location.hostname}:${location.port}${location.pathname}/ws`); + s.onopen = function() { + s.send(`register ${room.identity.name} ${room.identity.clr}`); + } + s.onmessage = function(e) { + room.last_packet = e; + let d = e.data; + if (typeof d == "object") { + d.arrayBuffer().then(acceptBoard); + info_elem.onclick = undefined; + info_elem.innerHTML = `${room.name} (${room.bconf.w}x${room.bconf.h}) >> Running, ${room.bconf.mine_ratio} tiles are mines`; + } else if (typeof e.data == "string") { + let fields = d.split(" "); + switch (fields[0]) { + case "pos": { + let posdata = JSON.parse(fields[1]); + posdata.forEach(pdat => { + let oid = Number(pdat[0]); + let x = pdat[1][0]; + let y = pdat[1][1]; + let curs = room.cursors.get(oid); + if (curs != undefined) { + movCursor(curs, x, y); + } else { + console.log("livepos sys incoherent"); + } + }); + } break; + case "players": { + let pdata = JSON.parse(fields[1]); + console.log(pdata); + pdata.forEach(p => { + let oid = Number(p[0]); + let name = p[1]; + let clr = p[2]; + console.log(oid, name, clr); + if (!room.cursors.has(oid)) { + createCursor(oid, name, clr); + } + }); + } break; + case "regack": { + room.name = fields[1]; + name = fields[2]; + player.uid = Number(fields[3]); + let dims = fields[4].split("x"); + room.bconf.w = Number(dims[0]); + room.bconf.h = Number(dims[1]); + room.bconf.mine_ratio = fields[5]; + createCursor(player.uid, name, room.identity.clr); + } break; + case "win": { + info_elem.innerHTML = "You win! Click here to play again."; + info_elem.onclick = e => { s.send("reset") }; + } break; + case "lose": { + let badone = fields[1]; + info_elem.innerHTML = `You lost, ${badone} was blown up. Click here to retry.`; + info_elem.onclick = e => { s.send("reset") }; + } break; + case "logoff": { + let oid = Number(fields[1]); + room.cursors.get(oid).elem.remove(); + room.cursors.get(oid).selwin.remove(); + room.cursors.delete(oid); + } break; + } + } + } + s.onerror = function(e) { info_elem.innerHTML += `
Connection error: ${e}`; } + s.onclose = function(e) { info_elem.innerHTML = "Connection closed"; } + return s; +} + +function acceptBoard(data) { + let dataarr = new Uint8Array(data); + let vals = fflate.inflateSync(dataarr); + room.board = vals.reduce((s,c) => { + let v = String.fromCodePoint(c); + if (v == ' ') { + s = s + " "; + } else { + s = s + v; + } + return s; + }, ""); + let last = room.board[0]; + let last_idx = 0; + let split_board = []; + for (let i = 1; i < room.board.length+1; i++) { + let cur = room.board[i]; + let gamechars = /^[CFO# 1-8]+$/; + if ((cur != last && gamechars.test(cur)) || cur == undefined) { + let txt = room.board.substr(last_idx, i-last_idx); + switch(txt[0]) { + case 'O': + txt = `${txt}`; + break; + case 'C': + txt = `${txt}`; + break; + case 'F': + txt = `${txt}`; + break; + + case '1': txt = `${txt}`; break; + case '2': txt = `${txt}`; break; + case '3': txt = `${txt}`; break; + case '4': txt = `${txt}`; break; + case '5': txt = `${txt}`; break; + case '6': txt = `${txt}`; break; + + default: txt = `${txt}`; break; + } + split_board.push(txt); + last_idx = i; + } + last = room.board[i]; + } + board_elem.innerHTML = split_board.join(""); + room.cbounds = getBoardBounds(); +} + +function createCursor(id, name, clr) { + // shit doesn't line up + let cursor = document.createElement("div"); + cursor.style.position = "absolute"; + let nametag = document.createElement("p"); + nametag.innerHTML = name; + nametag.classList.add('cursor-name'); + let selection_window = document.createElement("div"); + selection_window.style.backgroundColor = clr + "a0"; + selection_window.style.position = "absolute"; + selection_window.classList.add('cursor'); + cursor.appendChild(nametag); + cursor.classList.add('cursor'); + cursor.style.color = clr; + document.getElementById('cursor-frame').append(cursor); + document.getElementById('cursor-frame').append(selection_window); + let c = { name: name, elem: cursor, selwin: selection_window }; + if (id == window.player.uid) { + document.addEventListener('mousemove', e => { + let bcoords = pageToBoardPx(e.pageX, e.pageY); + movCursor(c, bcoords[0], bcoords[1]); + window.queued_pos = bcoords; + }, + false); + } + room.cursors.set(id, {name: name, elem: cursor, selwin: selection_window}); + return cursor; +} + +function pageToBoardPx(x,y) { + return [Math.floor(x - room.cbounds.ox), Math.floor(y - room.cbounds.oy)]; +} + +function movCursor(c, bx, by) { + c.elem.style.left = (room.cbounds.ox + bx) + 'px'; + c.elem.style.top = (room.cbounds.oy + by) + 'px'; + movSelWin(c.selwin, bx, by); +} +function movSelWin(win, bx, by) { + let tpos = tilepos(bx,by); + console.log(tpos); + if (tpos.x > (room.bconf.w - 1) || tpos.x < 0 || tpos.y > (room.bconf.h - 1) || tpos.y < 0) { + win.style.display = "none"; + } else { + win.style.display = ""; + } + win.style.left = (tpos.x * room.bconf.tile_w) + 'px'; + win.style.top = (tpos.y * room.bconf.tile_h) + 'px'; + win.style.width = room.bconf.tile_w + 'px'; + win.style.height = room.bconf.tile_h + 'px'; +} +function getBoardBounds() { + let a = bcont_elem.getBoundingClientRect(); + let b = board_elem.getBoundingClientRect(); + room.bconf.tile_w = b.width / room.bconf.w; + room.bconf.tile_h = 48; + return { + ox: b.x + window.scrollX, + oy: a.y + window.scrollY, + w: b.width, + h: a.height + }; +} +window.onresize = () => { + room.cbounds = getBoardBounds(); +} + +bcont_elem.onclick = function(e) { + let bcoords = pageToBoardPx(e.pageX, e.pageY); + let tpos = tilepos(bcoords[0], bcoords[1]); + let cmd = `reveal ${tpos.x} ${tpos.y}`; + room.socket.send(cmd); +} +bcont_elem.oncontextmenu = function(e) { + let bcoords = pageToBoardPx(e.pageX, e.pageY); + let tpos = tilepos(bcoords[0], bcoords[1]); + let cmd = `flag ${tpos.x} ${tpos.y}`; + room.socket.send(cmd); + return false; +} +// these are board-px coords +function tilepos(bx,by) { + let b = room.cbounds; // we can assume it is already computed by earlier aux calls + let tilex = Math.floor(room.bconf.w * bx/b.w); + let tiley = Math.floor(room.bconf.h * by/b.h); + return { x: tilex, y: tiley }; +} + +(function sendPos() { + let qp = window.queued_pos; + if (qp) { + room.socket.send(`pos ${qp[0]} ${qp[1]}`); + window.queued_pos = undefined; + } + setTimeout(function() { + sendPos(); + }, 16); +})(); diff --git a/assets/index.html b/assets/index.html new file mode 100644 index 0000000..cce9248 --- /dev/null +++ b/assets/index.html @@ -0,0 +1,132 @@ + + + + + websweeper + + + + +
+
+ +
+
+
+ -={ Create a new room }=- +
+
+
+
+
+ +
+
+ + + + diff --git a/assets/room.html b/assets/room.html new file mode 100644 index 0000000..4dadc6b --- /dev/null +++ b/assets/room.html @@ -0,0 +1,30 @@ + + + + + websweeper + + + + +
+
+ +
+
+ + +
+ + + + diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..49e832e --- /dev/null +++ b/assets/style.css @@ -0,0 +1,86 @@ +@font-face { + font-family: vt323; + src: url("./f.ttf"); +} +#board-container { + font-size: 80px; + line-height: 48px; + margin: 2vw; + position: relative; + top: 0px; + left 0px; +} +#cursor-frame { + position: absolute; + top: 0px; + left: 0px; +} + +body { + font-family: vt323, monospace; + font-size: 20pt; + background-color: black; + color: white; + margin: 0; +} +.unsel { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.cursor { + font-size: 20pt; + padding: 0; + pointer-events: none; + z-index: 1; +} +.cursor * { + margin: 0 0; +} +.cursor-name { + background-color: #000000c0; + padding: 0 0.1em; + line-height: initial; +} + +a { + text-decoration: none; + color: #8b8be8; +} + +#miscinfo { + flex-grow: 1; +} + +.statusline { + display: flex; + position: sticky; + background-color: black; + width: 96vw; + padding: 0 2vw; + bottom: 0px; + left: 0px; +} +.statusline * { + margin: 0.5em 2em 0 0; +} + +/* i was today years old when i learned that css selector declaration order matters */ +/* cent should come after statusline, so it overwrites statusline's props */ +.cent { + width: 80vw; + margin: 0 auto; +} + +span, h1, h4 { + margin: 0 auto; +} + +h4 { margin-left: 1em; } + +a :visited { + color: inherit; +} diff --git a/conf.json.sample b/conf.json.sample new file mode 100644 index 0000000..7ca5bec --- /dev/null +++ b/conf.json.sample @@ -0,0 +1,4 @@ +{ + "area_limit": 22500, + "room_limit": 16 +} diff --git a/src/conn.rs b/src/conn.rs new file mode 100644 index 0000000..addf3c5 --- /dev/null +++ b/src/conn.rs @@ -0,0 +1,191 @@ +use crate::types::*; +use std::{ + sync::Arc, + net::SocketAddr, +}; +use tokio::sync::RwLock; +use tokio::sync::mpsc as tokio_mpsc; +use futures::{SinkExt, StreamExt, TryStreamExt, stream::SplitStream}; +use warp::ws::{ WebSocket, Message }; +use crate::livepos; + +const MAX_IN: usize = 2048; + +pub async fn lobby(socket: WebSocket, addr: SocketAddr, rinfo: (RoomId,Arc>)) { + let (room_id, room) = rinfo; + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + + // server <-> client comms + let (mut outgoing, incoming) = socket.split(); + + println!("{room_id} I: Incoming TCP connection from: {}", addr); + + let full = { + let rl = room.read().await; + let pcap = rl.conf.player_cap; + let pl = rl.players.read().await; + pl.len() >= pcap + }; + if full { return } + let drive_game = handle_room((incoming,tx), addr, (room_id.clone(),room.clone())); + let send_to_client = { + let room_id = room_id.clone(); + async move { + while let Some(m) = rx.recv().await { + if let Err(e) = outgoing.send(m).await { + println!("{room_id} E: something went bad lol: {e}"); + } + } + } + }; + + tokio::select! { + _ = drive_game => (), + _ = send_to_client => { println!("{room_id} E: anomalous close for {addr}"); } + }; + + let room_lock = room.read().await; + let mut players = room_lock.players.write().await; + if let Some(disconn_p) = players.remove(&addr) { + if let Err(e) = room_lock.pos_stream.send(livepos::Req { id: disconn_p.uid, data: livepos::ReqData::Quit }) { + println!("{room_id} E: couldn't send removal request for {disconn_p} from the live position system: {e}"); + } + for p in players.values() { + if let Err(e) = p.conn.tx.send(Message::text(format!("logoff {}", disconn_p.uid))) { + println!("{room_id} E: couldn't deliver logoff info to {}: {}", p, e); + } + } + println!("{room_id} I: {disconn_p} disconnected"); + } else { + println!("{room_id} I: {addr} disconnected"); + } +} + +type RoomStreams = (SplitStream,tokio_mpsc::UnboundedSender); + +pub async fn handle_room(streams: RoomStreams, addr: SocketAddr, rinfo: (RoomId, Arc>)) { + let (mut incoming, tx) = streams; + let (room_id, room) = rinfo; + let (players, cmd_tx, pos_tx, room_conf) = { + let room = room.read().await; + (room.players.clone(), room.cmd_stream.clone(), room.pos_stream.clone(), room.conf.clone()) + }; + while let Ok(cmd) = incoming.try_next().await { + if let Some(cmd) = cmd { + // if it ain't text we can't handle it + let cmd = match cmd.to_str() { + Ok(cmd) => { if cmd.len() > MAX_IN { + println!("{room_id} E: string too big: {cmd}"); + return + } else { cmd.to_owned() } }, + Err(_) => return + }; + + let mut fields = cmd.split(" "); + let parse_pos = |mut fields: std::str::Split<&str>| -> Option<(usize, usize)> { + let x = fields.next().and_then(|xstr| xstr.parse::().ok()); + let y = fields.next().and_then(|ystr| ystr.parse::().ok()); + x.zip(y) + }; + if let Some(cmd_name) = fields.next() { + use crate::minesweeper::{Move,MoveType}; + let mut players_lock = players.write().await; + match players_lock.get_mut(&addr) { + Some(me) => match cmd_name { + "pos" => { + if let Some(pos) = parse_pos(fields) { + if let Err(e) = pos_tx.send(livepos::Req { id: me.uid, data: livepos::ReqData::Pos(pos) }) { + println!("{room_id} E: couldn't process {me}'s position update: {e}"); + }; + } + }, + "reveal" => { + match parse_pos(fields) { + Some(pos) => { + if let Err(e) = cmd_tx.send(MetaMove::Move(Move { t: MoveType::Reveal, pos }, addr)) { + println!("{room_id} E: couldn't process {me}'s reveal command: {e}"); + }; + }, + None => { + println!("{room_id} E: bad reveal from {me}"); + } + } + }, + "flag" => { + match parse_pos(fields) { + Some(pos) => { + if let Err(e) = cmd_tx.send(MetaMove::Move(Move { t: MoveType::ToggleFlag, pos }, addr)) { + println!("{room_id} E: couldn't process {me}'s flag command: {e}"); + }; + }, + None => { + println!("{room_id} E: bad flag from {me}"); + } + } + }, + "reset" => { + if let Err(e) = cmd_tx.send(MetaMove::Reset) { + println!("{room_id} E: couldn't request game dump in behalf of {me}: {e}"); + } + }, + e => println!("{room_id} E: unknown command {e:?} from {me}: \"{cmd}\""), + }, + None => { + if cmd_name == "register" { + let mut all_fields = fields.collect::>(); + let clr = all_fields.pop().expect("register without color").chars().filter(|c| c.is_digit(16) || *c == '#').collect::(); + let name = { + let def = "anon".to_string(); + if all_fields.is_empty() { def } + else { + let n = ammonia::clean(&all_fields.join(" ")); + if n.is_empty() { def } else { n } + } + }; + println!("{room_id} I: registered \"{name}@{addr}\""); + drop(players_lock); + let uid = { + // new scope cuz paranoid bout deadlocks + let conn = Conn { addr, tx: tx.clone() }; + room.write().await.players.insert_conn(conn, name.clone(), clr).await + }; + let players_lock = players.read().await; + let me = players_lock.get(&addr).unwrap(); + tx.send(Message::text(format!("regack {} {} {} {}", + room_conf.name.replace(' ', " "), name.replace(' ', " "), uid, room_conf.board_conf)) + ).expect("couldn't send register ack"); + + { + let msg = Message::text(format!("players {}", + jsonenc_players(players_lock.values()) + .expect("couldn't JSONify players"))); + for p in players_lock.values() { + if let Err(e) = p.conn.tx.send(msg.clone()) { + println!("{room_id} E: couldn't dump players for {me}: {e}"); + } + } + } + if let Err(e) = pos_tx.send(livepos::Req { id: uid, data: livepos::ReqData::StateDump }) { + println!("{room_id} E: couldn't request position dump for {me}: {e}"); + } + if let Err(e) = cmd_tx.send(MetaMove::Dump) { + println!("{room_id} E: couldn't request game dump for {me}: {e}"); + } + } + } + } + } + } else { + println!("{room_id} E: reached end of stream for {addr}"); + break; + } + } +} + +fn jsonenc_players<'a, I: IntoIterator>(players: I) -> Result { + let mut pairs = Vec::new(); + for player in players { + pairs.push((player.uid, player.name.replace(' ', " "), player.clr.clone())); + } + serde_json::to_string(&pairs) +} diff --git a/src/livepos.rs b/src/livepos.rs new file mode 100644 index 0000000..9112755 --- /dev/null +++ b/src/livepos.rs @@ -0,0 +1,78 @@ +use crate::types::*; +use tokio::sync::mpsc as tokio_mpsc; +use tokio::sync::Mutex; +use std::collections::{HashMap,HashSet}; +use tokio::time::{self, Duration}; +use warp::ws::Message; + +pub enum ReqData { + Pos((usize,usize)), + StateDump, + Quit, +} + +pub struct Req { + pub id: usize, + pub data: ReqData, +} + +pub async fn livepos(players: PlayerMapData, mut recv: tokio_mpsc::UnboundedReceiver) { + let positions = Mutex::new(HashMap::new()); + let dirty = Mutex::new(HashSet::new()); + let process_upds = async { + while let Some(update) = recv.recv().await { + let mut dirty = dirty.lock().await; + let mut positions = positions.lock().await; + match update.data { + ReqData::Pos(p) => { + let old = positions.get(&update.id).unwrap_or(&(0,0)); + if p != *old { + dirty.insert(update.id); + } + positions.insert(update.id, p); + }, + ReqData::StateDump => { + dirty.clear(); + dirty.extend(positions.keys().copied()); + }, + ReqData::Quit => { + positions.remove(&update.id); + dirty.retain(|x| *x != update.id); + } + } + } + }; + let periodic_send = async { + let mut interv = tokio::time::interval(Duration::from_millis(16)); + interv.set_missed_tick_behavior(time::MissedTickBehavior::Skip); + loop { + interv.tick().await; + let mut dirty = dirty.lock().await; + if dirty.len() > 0 { + let mut positions = positions.lock().await; + let msg = jsonenc_ids(&mut positions, &*dirty).expect("couldn't JSONify player positions"); + dirty.clear(); + let plock = players.read().await; + for player in plock.values() { + if let Err(e) = player.conn.tx.send(Message::text(format!("pos {}", msg))) { + println!("E: couldn't send livepos update to {}: {}", player, e); + } + } + } + } + }; + + tokio::select!( + _ = process_upds => (), + _ = periodic_send => () + ); +} + +fn jsonenc_ids<'a, I: IntoIterator>(positions: &mut HashMap, ids: I) -> Result { + let mut pairs = Vec::new(); + for id in ids { + pairs.push((id, positions[id])); + }; + serde_json::to_string(&pairs) +} + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..07ba00e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,328 @@ +use std::{ + error::Error, + net::SocketAddr, + sync::Arc, + collections::HashMap, + num::NonZeroUsize, +}; +use futures::stream::StreamExt; + +mod types; +mod livepos; +mod conn; +mod minesweeper; +use types::*; + +use tokio::sync::RwLock; + +const FONT_FILE: &[u8] = include_bytes!("../assets/VT323-Regular.ttf"); +const CONF_FILE: &str = "./conf.json"; + +fn main() -> Result<(), Box> { + let conf = Config { + cert: "./cert.pem".to_owned(), + pkey: "./cert.rsa".to_owned(), + index_pg: "./assets/index.html".to_owned(), + room_pg: "./assets/room.html".to_owned(), + client_code: "./assets/client.js".to_owned(), + stylesheet: "./assets/style.css".to_owned(), + socket_addr: ([0,0,0,0],31235).into(), + }; + + tokio_main(conf) +} + +#[tokio::main] +async fn tokio_main(conf: Config) -> Result<(), Box> { + let conf_file: serde_json::Value = serde_json::from_str(&tokio::fs::read_to_string(CONF_FILE).await?)?; + let area_limit: usize = conf_file.get("area_limit") + .expect("no area_limit field in the conf.json file") + .as_u64().expect("area_limit not a number") as usize; + let room_limit: usize = conf_file.get("room_limit") + .expect("no room_limit field in the conf.json file") + .as_u64().expect("room_limit not a number") as usize; + let rooms: RoomMap = Arc::new(RwLock::new(HashMap::new())); + let public_rooms = Arc::new(RwLock::new(HashMap::new())); + use warp::*; + + let index = path::end().and(fs::file(conf.index_pg.clone())); + let style = path!("s.css").and(fs::file(conf.stylesheet.clone())); + let code = path!("c.js").and(fs::file(conf.client_code.clone())); + let font = path!("f.ttf").map(|| FONT_FILE); + let listing = { + let rooms = rooms.clone(); + let pubs = public_rooms.clone(); + path!("rlist").and_then(move || { + let rooms = rooms.clone(); + let pubs = pubs.clone(); + async move { + let roomsl = rooms.read().await; + let pubsl = pubs.read().await; + let rooms_pcount = futures::stream::iter(pubsl.iter()) + .then(|(id, _):(&RoomId,_)| { + let roomsl = roomsl.clone(); + async move { + let room = roomsl.get(id).unwrap().read().await; + let pcount = room.players.read().await.len(); + (id.clone(), (pcount, room.conf.player_cap)) + } + }) + .collect::>().await; + let resp = (&*pubsl, rooms_pcount); + Ok::<_,std::convert::Infallible>( + reply::json(&resp) + ) + } + }) + }; + let roomspace = { + let rooms = rooms.clone(); + + path!("rspace").and_then(move || { + let r = rooms.clone(); + async move { + let empty_len = empty_rooms(r.clone()).await.len(); + let space = room_limit - r.read().await.len() + empty_len; + Ok::<_,std::convert::Infallible>( + hyper::Response::builder() + .status(hyper::StatusCode::OK) + .body(hyper::Body::from(space.to_string())) + .unwrap() + ) + } + }) + }; + let rform_recv = { + let rooms = rooms.clone(); + let pubs = public_rooms.clone(); + post().and(path("r")).and(body::content_length_limit(4096)).and(body::form()) + .and_then(move |rinfo: HashMap| { + println!("{:?}", rinfo); + let rooms = rooms.clone(); + let pubs = pubs.clone(); + async move { + let slots_available = room_limit - rooms.read().await.len(); + let empty = empty_rooms(rooms.clone()).await; + if slots_available < 1 { + if slots_available + empty.len() > 0 { + remove_room(rooms.clone(), pubs.clone(), empty[0].clone()).await; + } else { + return Err(reject::custom(NoRoomSlots)); + } + } + + if let (Some(w),Some(h),Some(num),Some(denom),access,asfm,limit) = ( + rinfo.get("rwidth").and_then(|wt| wt.parse::().ok()), + rinfo.get("rheight").and_then(|ht| ht.parse::().ok()), + rinfo.get("rration").and_then(|nt| nt.parse::().ok()), + rinfo.get("rratiod").and_then(|dt| dt.parse::().ok()), + rinfo.get("raccess"), + rinfo.get("ralwayssafe1move"), + rinfo.get("rlimit").and_then(|l| l.parse::().ok()), + ) { + if w.get()*h.get() > area_limit { + return Err(reject::custom(BoardTooBig)) + } + let board_conf = minesweeper::BoardConf { w, h, mine_ratio: (num,denom), always_safe_first_move: asfm.is_some() }; + let mut rooms = rooms.write().await; + let uid = types::RoomId::new_in(&rooms); + let name = { + let n = rinfo.get("rname").unwrap().to_owned(); + if n.is_empty() { uid.to_string() } else { n } + }; + + let players = PlayerMap::default(); + + let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel(); + let game_handle = tokio::spawn(gameloop(cmd_rx, players.clone(), board_conf)); + + let (pos_tx, pos_rx) = tokio::sync::mpsc::unbounded_channel(); + let livepos_handle = tokio::spawn(livepos::livepos(players.clone(), pos_rx)); + + let room_conf = RoomConf { + name, + player_cap: match limit { Some(i) => i, None => usize::MAX }, + public: access.is_some(), + board_conf, + }; + let new_room = Room { + conf: room_conf, + players, + game_driver: game_handle, + cmd_stream: cmd_tx, + livepos_driver: livepos_handle, + pos_stream: pos_tx, + }; + if access.is_some() { + pubs.write().await.insert(uid.clone(), serde_json::to_string(&new_room.conf).unwrap()); + } + rooms.insert(uid.clone(), Arc::new(RwLock::new(new_room))); + + Ok( + hyper::Response::builder() + .status(hyper::StatusCode::SEE_OTHER) + .header(hyper::header::LOCATION, format!("./room/{uid}")) + .body(hyper::Body::empty()) + .unwrap() + ) + } else { Err(reject::custom(BadFormData)) } + } + }) + }; + let room = { + let rooms_ws = rooms.clone(); + let rooms_lobby = rooms.clone(); + let prefix = get().and(path!("room" / String / ..)); + + // Fixme: better errors + prefix.and(path!("ws")) + .and(ws()) + .and(addr::remote()) + .and_then(move |id: String, websocket: warp::ws::Ws, saddr: Option| { + let rooms = rooms_ws.clone(); + async move { + let id = RoomId(id); + match rooms.read().await.get(&id).cloned() { + Some(r) => { + println!("{id} I: conn from {saddr:?}"); + Ok(websocket.on_upgrade(move |socket| { + conn::lobby(socket, saddr.expect("socket without address"), (id,r)) + })) + }, + None => { + println!("I: conn from {saddr:?} into inexistent room {id}"); + Err(reject()) + } + } + } + }) + .or(prefix.and(path::end()) + .and(fs::file(conf.room_pg.clone())) + .then(move |id: String, f: fs::File| { + let rooms = rooms_lobby.clone(); + async move { + if rooms.read().await.contains_key(&RoomId(id)) { + f.into_response() + } else { + reply::with_status("No such room", http::StatusCode::BAD_REQUEST).into_response() + } + } + }) + ) + }; + + + let route = get() + .and(index) + .or(style) + .or(code) + .or(font) + .or(listing) + .or(roomspace) + .or(rform_recv) + .or(room) + .recover(error_handler); + + let server = warp::serve(route) + .tls() + .cert_path(conf.cert) + .key_path(conf.pkey) + .run(conf.socket_addr); + println!("Serving on {}", conf.socket_addr); + server.await; + Ok(()) +} + +// If a move is made, broadcast new board, else just send current board +async fn gameloop(mut move_rx: tokio::sync::mpsc::UnboundedReceiver, players: PlayerMapData, bconf: minesweeper::BoardConf) { + use minesweeper::*; + use flate2::{ Compression, write::DeflateEncoder }; + use std::io::Write; + let mut game = Game::new(bconf); + let mut latest_player_name = None; + while let Some(req) = move_rx.recv().await { + let done = game.phase == Phase::Die || game.phase == Phase::Win; + match req { + MetaMove::Move(m, o) => if !done { + game = game.act(m); + if game.phase == Phase::Win || game.phase == Phase::Die { + game.board = game.board.grade(); + } + latest_player_name = players.read().await.get(&o).map(|p| p.name.clone()); + }, + MetaMove::Dump => (), + MetaMove::Reset => { game = Game::new(bconf); }, + } + use warp::ws::Message; + let mut board_encoder = DeflateEncoder::new(Vec::new(), Compression::default()); + board_encoder.write_all(&game.board.render()).unwrap(); + let compressed_board = board_encoder.finish().unwrap(); + let mut reply = vec![Message::binary(compressed_board)]; + let lpname = latest_player_name.as_deref().unwrap_or("unknown player").replace(' ', " "); + match game.phase { + Phase::Win => { reply.push(Message::text(format!("win {lpname}"))); }, + Phase::Die => { reply.push(Message::text(format!("lose {lpname}"))); }, + _ => (), + } + { + let peers = players.read().await; + for (addr, p) in peers.iter() { + for r in reply.iter() { + if let Err(e) = p.conn.tx.send(r.clone()) { + println!("couldn't send game update {r:?} to {addr}: {e}"); + } + } + } + } + } +} + +use warp::{ reject::{ Reject, Rejection }, reply::{ self, Reply }, http::StatusCode }; +#[derive(Debug)] +struct BadFormData; +impl Reject for BadFormData {} + +#[derive(Debug)] +struct BoardTooBig; +impl Reject for BoardTooBig {} + +#[derive(Debug)] +struct NoRoomSlots; +impl Reject for NoRoomSlots {} + +async fn error_handler(err: Rejection) -> Result { + if err.is_not_found() { Ok(reply::with_status("No such file", StatusCode::NOT_FOUND)) } + else if let Some(_e) = err.find::() { + Ok(reply::with_status("Bad form data", StatusCode::BAD_REQUEST)) + } else if let Some(_e) = err.find::() { + Ok(reply::with_status("Board too big", StatusCode::BAD_REQUEST)) + } else if let Some(_e) = err.find::() { + Ok(reply::with_status("No more rooms slots", StatusCode::BAD_REQUEST)) + } else { + println!("unhandled rejection: {err:?}"); + Ok(reply::with_status("Server error", StatusCode::INTERNAL_SERVER_ERROR)) + } +} + +async fn empty_rooms(rooms: RoomMap) -> Vec { + let rl = rooms.read().await; + futures::stream::iter(rl.iter()) + .filter_map(|(id,roomarc)| async move { + let rrl = roomarc.read().await; + let rrrl = rrl.players.read().await; + if rrrl.len() == 0 { Some(id.clone()) } else { None } + }) + .collect::>().await +} + +async fn remove_room(rooms: RoomMap, pubs: Arc>>, id: RoomId) { + { + let mut rwl = rooms.write().await; + rwl.remove(&id); + } + { + let mut pwl = pubs.write().await; + pwl.remove(&id); + } +} + diff --git a/src/minesweeper.rs b/src/minesweeper.rs new file mode 100644 index 0000000..9e362dc --- /dev/null +++ b/src/minesweeper.rs @@ -0,0 +1,286 @@ +use std::{ + convert::TryInto, + num::NonZeroUsize, +}; +use rand::{ thread_rng, Rng, distributions::Uniform }; +use serde::Serialize; + +const HIDDEN_BIT: u8 = 1 << 7; +pub const FLAGGED_BIT: u8 = 1 << 6; +const CORRECT_BIT: u8 = 1 << 5; // grading for a rightly flagged mine +// all the bits that aren't flags +const TILE_NUMBITS: u8 = !(HIDDEN_BIT | FLAGGED_BIT | CORRECT_BIT); +const MINED: u8 = HIDDEN_BIT | TILE_NUMBITS; +const NEIGH_OFFS: &[(isize,isize)] = &[ + (-1,-1),(0,-1),(1,-1), + (-1, 0), (1, 0), + (-1, 1),(0, 1),(1, 1), +]; +#[derive(PartialEq)] +pub enum Phase { + SafeFirstMove, + FirstMoveFail, + Run, + Die, + Win, +// Leave, +} +pub struct Game { + pub phase: Phase, + pub board: Board, + pub board_conf: BoardConf, +} + +#[derive(Debug, Clone, Copy, Serialize)] +pub struct BoardConf { + pub w: NonZeroUsize, + pub h: NonZeroUsize, + /// mines/tiles, expressed as (numerator, denominator) + pub mine_ratio: (usize,NonZeroUsize), + pub always_safe_first_move: bool, +} + +impl std::fmt::Display for BoardConf { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}x{} {}/{}", self.w, self.h, self.mine_ratio.0, self.mine_ratio.1) + } +} + +pub struct Board { + pub data: Vec, + pub width: NonZeroUsize, + pub height: NonZeroUsize, + pub hidden_tiles: usize, + pub mine_count: usize, +} +#[derive(Debug)] +pub enum MoveType { + Reveal, + ToggleFlag, +} +#[derive(Debug)] +pub struct Move { + pub t: MoveType, + pub pos: (usize,usize), +} + +pub struct MoveResult(pub Board, pub bool); +impl Game { + pub fn new(conf: BoardConf) -> Self { + let board = Board::new(conf); + Game { + phase: if conf.always_safe_first_move { Phase::SafeFirstMove } else { Phase::Run }, + board, + board_conf: conf + } + } + pub fn act(mut self, m: Move) -> Self { + let lost_phase = | phase | { + match phase { + Phase::SafeFirstMove => Phase::FirstMoveFail, + Phase::Run => Phase::Die, + _ => unreachable!(), + } + }; + + match m.t { + MoveType::Reveal => { + let kaboom: bool; + self.board = { + let mr = self.board.reveal(m.pos); + kaboom = mr.1; + mr.0 + }; + if kaboom { self.phase = lost_phase(self.phase) } + if self.phase == Phase::SafeFirstMove { self.phase = Phase::Run } + }, + MoveType::ToggleFlag => self.board = self.board.flag(m.pos).0, + }; + + if self.phase == Phase::FirstMoveFail { + let winnable = self.board.mine_count < (self.board.width.get() * self.board.height.get()); + if winnable { + self.board.move_mine_elsewhere(m.pos); + self.phase = Phase::Run; + self = self.act(m); + } else { + self.phase = Phase::Die; + } + } else if self.phase != Phase::Die && self.board.hidden_tiles == self.board.mine_count { + self.phase = Phase::Win; + } + self + } +} +impl Board { + pub fn new(conf: BoardConf) -> Self { + let (w,h) = (conf.w,conf.h); + let area = w.get()*h.get(); + let mine_count = ((conf.mine_ratio.0 * area) / conf.mine_ratio.1.get()).clamp(0, area); + let b = Board { + data: [HIDDEN_BIT].repeat(area), + width: w, + height: h, + hidden_tiles: area, + mine_count, + }; + b.spread_mines(mine_count) + } + pub fn spread_mines(mut self, mut count: usize) -> Self { + let mut rng = thread_rng(); + let w = self.width.get(); + let h = self.height.get(); + while count > 0 { + let randpos: (usize, usize) = (rng.sample(Uniform::new(0,w)), rng.sample(Uniform::new(0,h))); + let o = self.pos_to_off_unchecked(randpos); + if self.data[o] == MINED { continue } + else { + self.data[o] = MINED; + count -= 1; + let minepos = pos_u2i(randpos).unwrap(); + self.map_neighs(minepos, |neigh| { + if neigh != MINED { + neigh + 1 + } else { neigh } + }); + } + } + self + } + + fn neighs(&self, pos: (T,T)) -> Option> + where T: TryInto + { + if let (Ok(ox),Ok(oy)) = (pos.0.try_into(),pos.1.try_into()) { + Some(NEIGH_OFFS + .iter() + .map(|(x,y)| (*x + ox, *y + oy)).filter_map(|p| self.bounded(p)) + .collect()) + } else { + None + } + } + fn map_neighs(&mut self, pos: (T,T), mut f: impl FnMut(u8) -> u8) where T: TryInto { + if let Some(neighs) = self.neighs(pos) { + let npos = neighs.iter().filter_map(|pos| self.pos_to_off(*pos)).collect::>(); + npos.iter().for_each(|o| { + self.data[*o] = f(self.data[*o]); + }); + } + } + + pub fn pos_to_off(&self, pos: (usize,usize)) -> Option + { + self.bounded(pos).map(|x| self.pos_to_off_unchecked(x)) + } + pub fn pos_to_off_unchecked(&self, pos: (usize, usize)) -> usize { + pos.0 + pos.1 * self.width.get() + } + pub fn bounded(&self, pos: (T,T)) -> Option<(usize, usize)> + where T: TryInto + { + if let (Ok(x),Ok(y)) = ( + pos.0.try_into(), + pos.1.try_into(), + ) { + (x < self.width.get() && y < self.height.get()).then(|| (x,y)) + } else { None } + } + pub fn flood_reveal(&mut self, pos: (usize,usize)) { + let mut queue = vec![pos]; + while let Some(pos) = queue.pop() { + if let Some(off) = self.pos_to_off(pos) { + let c = &mut self.data[off]; + if *c & HIDDEN_BIT > 0 { + *c &= !(HIDDEN_BIT | FLAGGED_BIT); + self.hidden_tiles -= 1; + if *c > 0 { continue; } + if let Some(mut adj) = self.neighs(pos) { + queue.append(&mut adj); + } + } + } + } + } + pub fn reveal(mut self, pos: (usize,usize)) -> MoveResult { + if let Some(off) = self.pos_to_off(pos) { + self.flood_reveal(pos); + let c = self.data[off]; + MoveResult(self, (c & !(FLAGGED_BIT | CORRECT_BIT)) == TILE_NUMBITS) + } else { + MoveResult(self, false) + } + } + pub fn grade(mut self) -> Board { + for i in &mut self.data { + if *i == TILE_NUMBITS | FLAGGED_BIT | HIDDEN_BIT { + *i |= CORRECT_BIT; + } + } + self + } + pub fn flag(mut self, pos: (usize,usize)) -> MoveResult { + if let Some(off) = self.pos_to_off(pos) { + self.data[off] ^= FLAGGED_BIT; + } + MoveResult(self, false) + } + + pub fn render(&self) -> Vec { + let mut ret = vec![]; + for y in 0..self.height.get() { + for x in 0..self.width.get() { + let c = &self.data[self.pos_to_off_unchecked((x,y))]; + match *c { + 0 => ret.push(b' '), + _ if *c <= 8 => ret.push(b'0' + c), + _ if (*c & CORRECT_BIT) > 0 => ret.push(b'C'), + _ if (*c & FLAGGED_BIT) > 0 => ret.push(b'F'), + _ if (*c & HIDDEN_BIT) > 0 => ret.push(b'#'), + _ if *c == TILE_NUMBITS => ret.push(b'O'), + _ => ret.push(b'?'), + } + } + ret.extend_from_slice(b"
"); + } + ret + } + + pub fn move_mine_elsewhere(&mut self, pos: (usize, usize)) { + let mut surround_count = 0; + self.map_neighs(pos, |val| { + if (val & !FLAGGED_BIT) == MINED { + surround_count += 1; + val + } else { + val - 1 + }}); + let off = self.pos_to_off(pos).unwrap(); + let vacant_pos = { + let v = self.data.iter() + .enumerate() + .filter(|(_,val)| (*val & TILE_NUMBITS) != TILE_NUMBITS) + .map(|(p,_)| p) + .next() + .unwrap(); // there must be at least one + (v%self.width.get(), v/self.width.get()) + }; + let voff = self.pos_to_off_unchecked(vacant_pos); + debug_assert!(voff != off, "swapped mine to the same position in a FirstMoveFail/grace'd first move (???)"); + + { // swap 'em (keep these together, pls kthnx (bugs were had)) + self.data[voff] |= MINED; + self.data[off] = surround_count; + } + + self.map_neighs(vacant_pos, |val| { + if (val & !FLAGGED_BIT) == MINED { val } else { val + 1 } + }); + } +} + +fn pos_u2i(pos: (usize, usize)) -> Option<(isize, isize)> { + if let (Ok(x),Ok(y)) = (pos.0.try_into(), pos.1.try_into()) + { Some((x,y)) } else { None } +} + diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..f9f166a --- /dev/null +++ b/src/types.rs @@ -0,0 +1,135 @@ +use std::{ + collections::HashMap, + net::SocketAddr, + sync::{ + Arc, + atomic::{ AtomicUsize, Ordering }, + }, + fmt::Display, + ops::{ Deref, DerefMut }, +}; +use warp::ws::Message; +use tokio::sync::RwLock; +use serde::Serialize; +use crate::minesweeper; +use crate::livepos; + +#[derive(Debug, Clone)] +pub struct Config { + pub cert: String, + pub pkey: String, + pub index_pg: String, + pub room_pg: String, + pub client_code: String, + pub stylesheet: String, + pub socket_addr: SocketAddr, +} + +#[derive(Debug, Serialize, Clone)] +pub struct RoomConf { + pub name: String, + pub player_cap: usize, + pub public: bool, + pub board_conf: minesweeper::BoardConf, +} + +pub struct Room { + pub conf: RoomConf, + pub players: PlayerMap, + pub game_driver: tokio::task::JoinHandle<()>, + pub cmd_stream: CmdTx, + pub livepos_driver: tokio::task::JoinHandle<()>, + pub pos_stream: tokio::sync::mpsc::UnboundedSender, +} + +#[derive(Debug)] +pub enum MetaMove { + Move(minesweeper::Move,SocketAddr), + Dump, + Reset, +} + +#[derive(Debug)] +pub struct Conn { + pub tx: tokio::sync::mpsc::UnboundedSender, + pub addr: SocketAddr, +} + +#[derive(Debug)] +pub struct Player { + pub conn: Conn, + pub uid: usize, + pub name: String, + pub clr: String, +} + +impl Display for Player { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "\"{}\"@{}", self.name, self.conn.addr) + } +} + +#[derive(Eq, PartialEq, Hash, Debug, Clone, serde::Serialize)] +pub struct RoomId(pub String); +impl Display for RoomId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} +impl std::borrow::Borrow for RoomId { + fn borrow(&self) -> &str { + self.0.borrow() + } +} + +impl RoomId { + pub fn new_in(map: &HashMap) -> Self { + use rand::{ thread_rng, Rng, distributions::Alphanumeric }; + let id = RoomId(thread_rng() + .sample_iter(&Alphanumeric) + .take(16) + .map(char::from) + .collect::()); + if map.contains_key(&id) { RoomId::new_in(map) } + else { id } + } +} + +pub type CmdTx = tokio::sync::mpsc::UnboundedSender; +pub type RoomMap = Arc>>>>; +pub type PlayerMapData = Arc>>; +#[derive(Debug)] +pub struct PlayerMap { + inner: PlayerMapData, + uid_counter: AtomicUsize, +} + +impl Deref for PlayerMap { + type Target = Arc>>; + fn deref(&self) -> &Self::Target { + &self.inner + } +} +impl DerefMut for PlayerMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} +impl Default for PlayerMap { + fn default() -> Self { + Self { inner: Arc::new(RwLock::new(HashMap::new())), uid_counter: 0.into() } + } +} + +impl PlayerMap { + pub async fn insert_conn(&mut self, conn: Conn, name: String, clr: String) -> usize { + let mut map = self.write().await; + let uid = self.uid_counter.fetch_add(1, Ordering::Relaxed); + map.insert( + conn.addr, + Player { conn, uid, name, clr }, + ); + uid + } +} + -- cgit v1.2.3