From 4c0871e26c760b24018cf2a42ce9f26528b80f9d Mon Sep 17 00:00:00 2001 From: shiroyashik Date: Mon, 28 Oct 2024 03:19:33 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89WebSocket=20refactored!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 323 ++++++++---------- Cargo.toml | 52 +-- Dockerfile | 2 +- README.md | 12 +- README.ru.md | 18 +- src/api/figura/assets.rs | 20 +- src/api/figura/mod.rs | 2 +- src/api/figura/profile.rs | 20 +- src/api/figura/types/mod.rs | 9 +- src/api/figura/websocket.rs | 234 ------------- src/api/figura/websocket/handler.rs | 212 ++++++++++++ src/api/figura/websocket/mod.rs | 8 + src/api/figura/websocket/processor.rs | 32 ++ src/api/figura/{ => websocket}/types/c2s.rs | 36 +- .../figura/{ => websocket}/types/errors.rs | 31 ++ src/api/figura/websocket/types/mod.rs | 9 + src/api/figura/{ => websocket}/types/s2c.rs | 43 +-- src/api/figura/websocket/types/session.rs | 15 + src/api/v1/http2ws.rs | 6 +- src/api/v1/users.rs | 3 +- src/auth/auth.rs | 8 +- src/auth/types.rs | 6 +- src/consts.rs | 24 +- src/main.rs | 39 +-- src/state/config.rs | 8 +- src/state/mod.rs | 3 +- src/state/state.rs | 23 ++ src/utils/{utils.rs => auxiliary.rs} | 21 +- src/utils/check_updates.rs | 14 +- src/utils/mod.rs | 4 +- 30 files changed, 650 insertions(+), 587 deletions(-) delete mode 100644 src/api/figura/websocket.rs create mode 100644 src/api/figura/websocket/handler.rs create mode 100644 src/api/figura/websocket/mod.rs create mode 100644 src/api/figura/websocket/processor.rs rename src/api/figura/{ => websocket}/types/c2s.rs (80%) rename src/api/figura/{ => websocket}/types/errors.rs (60%) create mode 100644 src/api/figura/websocket/types/mod.rs rename src/api/figura/{ => websocket}/types/s2c.rs (77%) create mode 100644 src/api/figura/websocket/types/session.rs rename src/utils/{utils.rs => auxiliary.rs} (84%) diff --git a/Cargo.lock b/Cargo.lock index 112b8c6..1b7a0c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,9 +60,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "arbitrary" @@ -75,13 +75,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -103,9 +103,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" @@ -139,7 +139,7 @@ dependencies = [ "sync_wrapper 1.0.1", "tokio", "tokio-tungstenite", - "tower 0.5.1", + "tower", "tower-layer", "tower-service", "tracing", @@ -174,7 +174,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -233,9 +233,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bzip2" @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.19" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "jobserver", "libc", @@ -529,7 +529,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -571,7 +571,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -594,9 +594,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -635,9 +635,9 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -675,9 +675,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -685,33 +685,33 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", @@ -762,7 +762,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.5.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -781,6 +781,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.4.1" @@ -862,9 +868,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -874,9 +880,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -928,9 +934,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -941,7 +947,6 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower 0.4.13", "tower-service", "tracing", ] @@ -997,12 +1002,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "serde", ] @@ -1017,9 +1022,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is_ci" @@ -1044,9 +1049,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1059,9 +1064,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libredox" @@ -1244,15 +1249,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -1271,7 +1276,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1282,9 +1287,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -1352,31 +1357,11 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1386,9 +1371,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "powerfmt" @@ -1431,9 +1416,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -1479,9 +1464,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -1499,14 +1484,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1520,13 +1505,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1537,15 +1522,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64", "bytes", @@ -1617,9 +1602,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno", @@ -1630,9 +1615,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" dependencies = [ "once_cell", "rustls-pki-types", @@ -1643,19 +1628,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -1670,9 +1654,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -1691,9 +1675,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -1716,7 +1700,7 @@ dependencies = [ "dashmap", "dotenvy", "hex", - "indexmap 2.5.0", + "indexmap 2.6.0", "lazy_static", "rand", "reqwest", @@ -1752,9 +1736,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -1768,22 +1752,22 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1797,9 +1781,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -1819,9 +1803,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -1957,9 +1941,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -2004,9 +1988,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -2032,22 +2016,22 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -2108,9 +2092,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -2132,7 +2116,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -2213,32 +2197,17 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.21" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - [[package]] name = "tower" version = "0.5.1" @@ -2315,7 +2284,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -2400,9 +2369,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -2412,9 +2381,9 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -2444,9 +2413,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "serde", ] @@ -2496,9 +2465,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -2507,24 +2476,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -2534,9 +2503,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2544,28 +2513,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -2737,9 +2706,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -2762,7 +2731,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -2782,7 +2751,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -2801,7 +2770,7 @@ dependencies = [ "displaydoc", "flate2", "hmac", - "indexmap 2.5.0", + "indexmap 2.6.0", "lzma-rs", "memchr", "pbkdf2", diff --git a/Cargo.toml b/Cargo.toml index 5004059..5a8c242 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,40 +7,40 @@ publish = false [dependencies] # Logging -tracing-subscriber = { version = "0.3.18", features = ["env-filter", "chrono"] } -tracing-appender = "0.2.3" -tracing-panic = "0.1.2" -tracing = "0.1.40" +tracing-subscriber = { version = "0.3", features = ["env-filter", "chrono"] } +tracing-appender = "0.2" +tracing-panic = "0.1" +tracing = "0.1" # Errors handelers -anyhow = "1.0.83" -thiserror = "1.0.64" -chrono = { version = "0.4.38", features = ["now", "serde"] } -serde = { version = "1.0.201", features = ["derive"] } -serde_json = "1.0.117" -toml = "0.8.19" +anyhow = "1.0" +thiserror = "1.0" +chrono = { version = "0.4", features = ["now", "serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +toml = "0.8" # Other -dashmap = { version = "6.0.1", features = ["serde"] } -hex = "0.4.3" -uuid = { version = "1.8.0", features = ["serde"] } -base64 = "0.22.1" -reqwest = { version = "0.12.7", features = ["blocking", "json"] } -dotenvy = "0.15.7" -semver = "1.0.23" -walkdir = "2.5.0" +dashmap = { version = "6.0", features = ["serde"] } +hex = "0.4" +uuid = { version = "1.11", features = ["serde"] } +base64 = "0.22" +reqwest = { version = "0.12", features = ["blocking", "json"] } +dotenvy = "0.15" +semver = "1.0" +walkdir = "2.5" # Crypto -ring = "0.17.8" -rand = "0.8.5" +ring = "0.17" +rand = "0.8" # Web framework -axum = { version = "0.7.7", features = ["ws", "macros", "http2"] } -tower-http = { version = "0.6.1", features = ["trace"] } -tokio = { version = "1.37.0", features = ["full"] } -indexmap = { version = "2.5.0", features = ["serde"] } -zip = "2.2.0" -lazy_static = "1.5.0" +axum = { version = "0.7", features = ["ws", "macros", "http2"] } +tower-http = { version = "0.6", features = ["trace"] } +tokio = { version = "1.37", features = ["full"] } +indexmap = { version = "2.6", features = ["serde"] } +zip = "2.2" +lazy_static = "1.5" [dev-dependencies] cross = "0.2.5" diff --git a/Dockerfile b/Dockerfile index 425a7a9..dfbbf6e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ## Chef # FROM clux/muslrust:stable AS chef -FROM rust:1.81.0-alpine3.20 AS chef +FROM rust:1.82.0-alpine3.20 AS chef USER root RUN apk add --no-cache musl-dev libressl-dev RUN cargo install cargo-chef diff --git a/README.md b/README.md index 55391bc..c1bec79 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ # The Sculptor [![Push Dev](https://github.com/shiroyashik/sculptor/actions/workflows/dev-release.yml/badge.svg?branch=dev)](https://github.com/shiroyashik/sculptor/actions/workflows/dev-release.yml) +![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg) Unofficial backend for the Minecraft mod [Figura](https://github.com/FiguraMC/Figura). @@ -19,11 +20,11 @@ I'm keeping the public server running at the moment! You can use it if running your own Sculptor instance is difficult for you. -To connect, simply change **IP Server** in Figura settings to the address below: +To connect, simply change **Figura Cloud IP** in Figura settings to the address below: > figura.shsr.ru -Authentication is enabled on the server via: Mojang and [Ely.By](https://ely.by/) +Authentication is enabled on the server via: Mojang(Microsoft) and [Ely.By](https://ely.by/) For reasons beyond my control, the server is not available in some countries. @@ -73,10 +74,15 @@ cargo run --release ``` ## Contributing +![Ask me anything!](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg) +on +[![Telegram](https://badgen.net/static/icon/telegram?icon=telegram&color=cyan&label)](https://t.me/shiroyashik) +or +![Discord](https://badgen.net/badge/icon/discord?icon=discord&label) If you have ideas for new features, have found a bug, or want to suggest improvements, please create an [issue](https://github.com/shiroyashik/sculptor/issues) -or contact me directly via Discord/Telegram (@shiroyashik). +or contact me directly via Discord/Telegram (**@shiroyashik**). If you are a Rust developer, you can modify the code yourself and request a Pull Request: diff --git a/README.ru.md b/README.ru.md index f14b00e..49209b9 100644 --- a/README.ru.md +++ b/README.ru.md @@ -3,12 +3,13 @@ # The Sculptor [![Push Dev](https://github.com/shiroyashik/sculptor/actions/workflows/dev-release.yml/badge.svg?branch=dev)](https://github.com/shiroyashik/sculptor/actions/workflows/dev-release.yml) +![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg) Неофициальный бэкенд для Minecraft мода [Figura](https://github.com/FiguraMC/Figura). Это полноценная замена официальной версии. Реализован весь функционал который вы можете использовать во время игры. -А также отличительной особенностью является возможность игры с сторонними провайдерерами аутентификации (таких как [Ely.By](https://ely.by/)) +А также отличительной особенностью является возможность игры с сторонними провайдерерами аутентификации (такими как [Ely.By](https://ely.by/)) ## Публичный сервер @@ -18,13 +19,13 @@ Вы можете использовать его если запуск собственного сервера затруднителен для вас. -Для подключения достаточно сменить **Сервер IP** в настройках Figura на адрес ниже: +Для подключения достаточно сменить **IP сервера Figura** в настройках Figura на адрес ниже: > figura.shsr.ru -На сервере включена аутентификация через: Mojang и [Ely.By](https://ely.by/) +На сервере включена аутентификация через: Mojang(Microsoft) и [Ely.By](https://ely.by/) -По неконтролируемым мною причинам, сервер не доступен в некоторых странах +По неконтролируемым мною причинам, сервер не доступен в некоторых странах. ## Запуск @@ -71,15 +72,20 @@ cargo run --release ``` ## Вклад в развитие +![Спроси меня о чём угодно!](https://img.shields.io/badge/Ask%20me-anything-1abc9c.svg) +в +[![Telegram](https://badgen.net/static/icon/telegram?icon=telegram&color=cyan&label)](https://t.me/shiroyashik) +или +![Discord](https://badgen.net/badge/icon/discord?icon=discord&label) Если у вас есть идем, нашли баг или хотите предложить улучшения создавайте [issue](https://github.com/shiroyashik/sculptor/issues) -или свяжитесь со мной напрямую через Discord/Telegram (@shiroyashik). +или свяжитесь со мной напрямую через Discord/Telegram (**@shiroyashik**). Если вы Rust разработчик, буду рад вашим Pull Request'ам: 1. Форкните репу -2. Создайте новую репу для вашего гения +2. Создайте новую ветку 3. Создайте PR! Буду рад любой вашей помощи! ❤ diff --git a/src/api/figura/assets.rs b/src/api/figura/assets.rs index 436a151..dda757f 100644 --- a/src/api/figura/assets.rs +++ b/src/api/figura/assets.rs @@ -21,10 +21,10 @@ async fn versions() -> ApiResult> { let mut directories = Vec::new(); - let mut entries = fs::read_dir(dir_path).await.map_err(|err| internal_and_log(err))?; + let mut entries = fs::read_dir(dir_path).await.map_err(internal_and_log)?; - while let Some(entry) = entries.next_entry().await.map_err(|err| internal_and_log(err))? { - if entry.metadata().await.map_err(|err| internal_and_log(err))?.is_dir() { + while let Some(entry) = entries.next_entry().await.map_err(internal_and_log)? { + if entry.metadata().await.map_err(internal_and_log)?.is_dir() { if let Some(name) = entry.file_name().to_str() { let name = name.to_string(); if !name.starts_with('.') { @@ -38,7 +38,7 @@ async fn versions() -> ApiResult> { } async fn hashes(Path(version): Path) -> ApiResult>> { - let map = index_assets(&version).await.map_err(|err| internal_and_log(err))?; + let map = index_assets(&version).await.map_err(internal_and_log)?; Ok(Json(map)) } @@ -49,7 +49,7 @@ async fn download(Path((version, path)): Path<(String, String)>) -> ApiResult anyhow::Result> Err(_) => continue }; - let path: String; - - if cfg!(windows) { - path = entry.path().strip_prefix(version_path.clone())?.to_string_lossy().to_string().replace("\\", "/"); + let path: String = if cfg!(windows) { + entry.path().strip_prefix(version_path.clone())?.to_string_lossy().to_string().replace("\\", "/") } else { - path = entry.path().strip_prefix(version_path.clone())?.to_string_lossy().to_string(); - } + entry.path().strip_prefix(version_path.clone())?.to_string_lossy().to_string() + }; map.insert(path, Value::from(hex::encode(digest(&SHA256, &data).as_ref()))); } diff --git a/src/api/figura/mod.rs b/src/api/figura/mod.rs index d7986a2..527526f 100644 --- a/src/api/figura/mod.rs +++ b/src/api/figura/mod.rs @@ -5,4 +5,4 @@ pub mod profile; pub mod info; pub mod assets; -pub use websocket::handler as ws; \ No newline at end of file +pub use websocket::{initial as ws, SessionMessage}; \ No newline at end of file diff --git a/src/api/figura/profile.rs b/src/api/figura/profile.rs index 3511c37..1825496 100644 --- a/src/api/figura/profile.rs +++ b/src/api/figura/profile.rs @@ -14,7 +14,7 @@ use crate::{ auth::Token, utils::{calculate_file_sha256, format_uuid}, ApiError, ApiResult, AppState, AVATARS_VAR }; -use super::types::S2CMessage; +use super::websocket::S2CMessage; pub async fn user_info( Path(uuid): Path, @@ -85,7 +85,7 @@ pub async fn download_avatar(Path(uuid): Path) -> ApiResult> { return Err(ApiError::NotFound) }; let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).await.map_err(|err| internal_and_log(err))?; + file.read_to_end(&mut buffer).await.map_err(internal_and_log)?; Ok(buffer) } @@ -103,15 +103,15 @@ pub async fn upload_avatar( user_info.username ); let avatar_file = format!("{}/{}.moon", *AVATARS_VAR, user_info.uuid); - let mut file = BufWriter::new(fs::File::create(&avatar_file).await.map_err(|err| internal_and_log(err))?); - io::copy(&mut request_data.as_ref(), &mut file).await.map_err(|err| internal_and_log(err))?; + let mut file = BufWriter::new(fs::File::create(&avatar_file).await.map_err(internal_and_log)?); + io::copy(&mut request_data.as_ref(), &mut file).await.map_err(internal_and_log)?; } Ok("ok".to_string()) } pub async fn equip_avatar(Token(token): Token, State(state): State) -> ApiResult<&'static str> { debug!("[API] S2C : Equip"); - let uuid = state.user_manager.get(&token).ok_or_else(|| ApiError::Unauthorized)?.uuid; + let uuid = state.user_manager.get(&token).ok_or(ApiError::Unauthorized)?.uuid; send_event(&state, &uuid).await; Ok("ok") } @@ -124,7 +124,7 @@ pub async fn delete_avatar(Token(token): Token, State(state): State) - user_info.username ); let avatar_file = format!("{}/{}.moon", *AVATARS_VAR, user_info.uuid); - fs::remove_file(avatar_file).await.map_err(|err| internal_and_log(err))?; + fs::remove_file(avatar_file).await.map_err(internal_and_log)?; send_event(&state, &user_info.uuid).await; } Ok("ok".to_string()) @@ -132,16 +132,16 @@ pub async fn delete_avatar(Token(token): Token, State(state): State) - pub async fn send_event(state: &AppState, uuid: &Uuid) { // To user subscribers - if let Some(broadcast) = state.broadcasts.get(&uuid) { - if broadcast.send(S2CMessage::Event(*uuid).to_vec()).is_err() { + if let Some(broadcast) = state.subscribes.get(uuid) { + if broadcast.send(S2CMessage::Event(*uuid).into()).is_err() { debug!("[WebSocket] Failed to send Event! There is no one to send. UUID: {uuid}") }; } else { debug!("[WebSocket] Failed to send Event! Can't find UUID: {uuid}") }; // To user - if let Some(session) = state.session.get(&uuid) { - if session.send(S2CMessage::Event(*uuid).to_vec()).await.is_err() { + if let Some(session) = state.session.get(uuid) { + if session.send(super::SessionMessage::Ping(S2CMessage::Event(*uuid).into())).await.is_err() { debug!("[WebSocket] Failed to send Event! WS doesn't connected? UUID: {uuid}") }; } else { diff --git a/src/api/figura/types/mod.rs b/src/api/figura/types/mod.rs index db4eb5c..5696e21 100644 --- a/src/api/figura/types/mod.rs +++ b/src/api/figura/types/mod.rs @@ -1,8 +1 @@ -mod c2s; -mod errors; -mod s2c; -pub mod auth; - -pub use c2s::C2SMessage; -pub use errors::MessageLoadError; -pub use s2c::S2CMessage; \ No newline at end of file +pub mod auth; \ No newline at end of file diff --git a/src/api/figura/websocket.rs b/src/api/figura/websocket.rs deleted file mode 100644 index 1d26bb5..0000000 --- a/src/api/figura/websocket.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::sync::Arc; - -use axum::{ - extract::{ - ws::{Message, WebSocket}, - State, WebSocketUpgrade, - }, - response::Response, -}; -use dashmap::DashMap; -use tracing::{debug, error, info, trace, warn}; -use tokio::sync::{ - broadcast::{self, Receiver}, - mpsc, Notify, -}; -use uuid::Uuid; - -use crate::AppState; -use super::types::{C2SMessage, S2CMessage}; - -pub async fn handler(ws: WebSocketUpgrade, State(state): State) -> Response { - ws.on_upgrade(|socket| handle_socket(socket, state)) -} - -#[derive(Debug, Clone)] -struct WSUser { - username: String, - uuid: Uuid, -} - -trait ExtWSUser { - fn name(&self) -> String; -} - -impl ExtWSUser for Option { - fn name(&self) -> String { - if let Some(user) = self { - format!(" ({})", user.username) - } else { - String::new() - } - } -} - -async fn handle_socket(mut socket: WebSocket, state: AppState) { - debug!("[WebSocket] New unknown connection!"); - let mut owner: Option = None; // Information about user - let cutoff: DashMap> = DashMap::new(); // Отключение подписки - let (mtx, mut mrx) = mpsc::channel(64); // multiple tx and single receive - let mut bctx: Option>> = None; // broadcast tx send - loop { - tokio::select! { - // Main loop what receving messages from WebSocket - Some(msg) = socket.recv() => { - trace!("[WebSocket{}] Raw: {msg:?}", owner.name()); - let mut msg = if let Ok(msg) = msg { - if let Message::Close(_) = msg { - info!("[WebSocket{}] Connection successfully closed!", owner.name()); - break; - } - msg - } else { - debug!("[WebSocket{}] Receive error! Connection terminated!", owner.name()); - break; - }; - // Checking ban list - if let Some(ref user) = owner { - if state.user_manager.is_banned(&user.uuid) { - warn!("[WebSocket] Detected banned user with active WebSocket! Sending close with Banned code."); - let _ = socket.send(Message::Binary(S2CMessage::Toast(2, "You're banned!", None).to_vec())).await; // option слищком жирный Some("Reason: Lorum Ipsum interсно сколько влезет~~~ 0w0.") - tokio::time::sleep(std::time::Duration::from_secs(6)).await; - debug!("{:?}", socket.send(Message::Close(Some(axum::extract::ws::CloseFrame { code: 4001, reason: "You're banned!".into() }))).await); - continue; - } - } - // Next is the code for processing msg - let msg_vec = msg.clone().into_data(); - let msg_array = msg_vec.as_slice(); - - if msg_array.len() == 0 { tracing::debug!("[WebSocket{}] Deprecated len 0 msg", owner.name()); continue; }; - - let newmsg = match C2SMessage::try_from(msg_array) { - Ok(data) => data, - Err(e) => { - error!("[WebSocket{}] This message is not from Figura! {}", owner.name(), e.to_string()); - debug!("[WebSocket{}] Broken data: {}", owner.name(), hex::encode(msg_vec)); - continue; - // break; - }, - }; - - debug!("[WebSocket{}] MSG: {:?}, HEX: {}", owner.name(), newmsg, hex::encode(newmsg.to_vec())); - - match newmsg { - C2SMessage::Token(token) => { - trace!("[WebSocket{}] C2S : Token", owner.name()); - let token = String::from_utf8(token.to_vec()).unwrap(); - match state.user_manager.get(&token) { // The principle is simple: if there is no token in authenticated, then it's "dirty hacker" :D - Some(t) => { - //username = t.username.clone(); - owner = Some(WSUser { username: t.username.clone(), uuid: t.uuid }); - state.session.insert(t.uuid, mtx.clone()); - msg = Message::Binary(S2CMessage::Auth.to_vec()); - match state.broadcasts.get(&t.uuid) { - Some(tx) => { - bctx = Some(tx.to_owned()); - }, - None => { - let (tx, _rx) = broadcast::channel(64); - state.broadcasts.insert(t.uuid, tx.clone()); - bctx = Some(tx.to_owned()); - }, - }; - }, - None => { - warn!("[WebSocket] Authentication error! Sending close with Re-auth code."); - debug!("[WebSocket] Tried to log in with {token}"); // Tried to log in with token: {token} - debug!("{:?}", socket.send(Message::Close(Some(axum::extract::ws::CloseFrame { code: 4000, reason: "Re-auth".into() }))).await); - continue; - }, - }; - }, - C2SMessage::Ping(_, _, _) => { - trace!("[WebSocket{}] C2S : Ping", owner.name()); - let data = into_s2c_ping(msg_vec, owner.clone().unwrap().uuid); - match bctx.clone().unwrap().send(data) { - Ok(_) => (), - Err(_) => debug!("[WebSocket{}] Failed to send Ping! Maybe there's no one to send", owner.name()), - }; - continue; - }, - // Subscribing - C2SMessage::Sub(uuid) => { // TODO: Eliminate the possibility of using SUB without authentication - trace!("[WebSocket{}] C2S : Sub", owner.name()); - // Ignoring self Sub - if uuid == owner.clone().unwrap().uuid { - continue; - }; - - let rx = match state.broadcasts.get(&uuid) { // Get sender - Some(rx) => rx.to_owned().subscribe(), // Subscribe on sender to get receiver - None => { - warn!("[WebSocket{}] Attention! The required UUID for subscription was not found!", owner.name()); - let (tx, rx) = broadcast::channel(64); // Pre creating broadcast for future - state.broadcasts.insert(uuid, tx); // Inserting into dashmap - rx - }, - }; - - let shutdown = Arc::new(Notify::new()); // Creating new shutdown - tokio::spawn(subscribe(mtx.clone(), rx, shutdown.clone())); // - cutoff.insert(uuid, shutdown); - continue; - }, - // Unsubscribing - C2SMessage::Unsub(uuid) => { - trace!("[WebSocket{}] C2S : Unsub", owner.name()); - // Ignoring self Unsub - if uuid == owner.clone().unwrap().uuid { - continue; - }; - - let shutdown = cutoff.remove(&uuid).unwrap().1; // Getting from list // FIXME: UNWRAP PANIC! NONE VALUE - shutdown.notify_one(); // Shutdown function - continue; - }, - } - - // Sending message - debug!("[WebSocket{}] Answering: {msg:?}", owner.name()); - if socket.send(msg).await.is_err() { - warn!("[WebSocket{}] Send error! Connection terminated!", owner.name()); - break; - } - } - msg = mrx.recv() => { - match socket.send(Message::Binary(msg.clone().unwrap())).await { - Ok(_) => { - debug!("[WebSocketSubscribe{}] Answering: {}", owner.name(), hex::encode(msg.unwrap())); - } - Err(_) => { - warn!("[WebSocketSubscriber{}] Send error! Connection terminated!", owner.name()); - break; - } - } - } - } - } - // Closing connection - if let Some(u) = owner { - debug!("[WebSocket ({})] Removing session data", u.username); - state.session.remove(&u.uuid); // FIXME: Temporary solution - // state.broadcasts.remove(&u.uuid); // NOTE: Create broadcasts manager ?? - state.user_manager.remove(&u.uuid); - } else { - debug!("[WebSocket] Nothing to remove"); - } -} - -async fn subscribe( - socket: mpsc::Sender>, - mut rx: Receiver>, - shutdown: Arc, -) { - loop { - tokio::select! { - _ = shutdown.notified() => { - debug!("SUB successfully closed!"); - return; - } - msg = rx.recv() => { - let msg = msg.ok(); - - if let Some(msg) = msg { - if socket.send(msg.clone()).await.is_err() { - debug!("Forced shutdown SUB! Client died?"); - return; - }; - } else { - debug!("Forced shutdown SUB! Source died?"); - return; - } - } - } - } -} - -fn into_s2c_ping(buf: Vec, uuid: Uuid) -> Vec { - use std::iter::once; - once(1) - .chain(uuid.into_bytes().iter().copied()) - .chain(buf.as_slice()[1..].iter().copied()) - .collect() -} diff --git a/src/api/figura/websocket/handler.rs b/src/api/figura/websocket/handler.rs new file mode 100644 index 0000000..0feaf6c --- /dev/null +++ b/src/api/figura/websocket/handler.rs @@ -0,0 +1,212 @@ +use anyhow::bail; +use axum::extract::{ws::{Message, WebSocket}, State}; +use dashmap::DashMap; +use tokio::sync::{broadcast, mpsc}; + +use crate::{auth::Userinfo, AppState}; + +use super::{processor::*, AuthModeError, S2CMessage, C2SMessage, WSSession, SessionMessage, RADError}; + +pub async fn initial( + ws: axum::extract::WebSocketUpgrade, + State(state): State +) -> axum::response::Response { + ws.on_upgrade(|socket| handle_socket(socket, state)) +} + +async fn handle_socket(mut ws: WebSocket, state: AppState) { + // Trying authenticate & get user data or dropping connection + match authenticate(&mut ws, &state).await { + Ok(user) => { + + // Creating session & creating/getting channels + let mut session = { + let sub_workers_aborthandles = DashMap::new(); + + // Channel for receiving messages from internal functions. + let (own_tx, own_rx) = mpsc::channel(32); + state.session.insert(user.uuid, own_tx.clone()); + + // Channel for sending messages to subscribers + let subs_tx = match state.subscribes.get(&user.uuid) { + Some(tx) => tx.clone(), + None => { + tracing::debug!("[Subscribes] Can't find own subs channel for {}, creating new...", user.uuid); + let (subs_tx, _) = broadcast::channel(32); + state.subscribes.insert(user.uuid, subs_tx.clone()); + subs_tx + }, + }; + + WSSession { user: user.clone(), own_tx, own_rx, subs_tx, sub_workers_aborthandles } + }; + + // Starting main worker + match main_worker(&mut session, &mut ws, &state).await { + Ok(_) => (), + Err(kind) => tracing::error!("[WebSocket] Main worker halted due to {}.", kind), + } + + for (_, handle) in session.sub_workers_aborthandles { + handle.abort(); + } + + // Removing session data + state.session.remove(&user.uuid); + state.user_manager.remove(&user.uuid); + }, + Err(kind) => { + tracing::info!("[WebSocket] Can't authenticate: {}", kind); + } + } + + // Closing connection + if let Err(kind) = ws.close().await { tracing::trace!("[WebSocket] Closing fault: {}", kind) } +} + +async fn main_worker(session: &mut WSSession, ws: &mut WebSocket, state: &AppState) -> anyhow::Result<()> { + tracing::debug!("WebSocket control for {} is transferred to the main worker", session.user.username); + loop { + tokio::select! { + external_msg = ws.recv_and_decode() => { + + // Getting a value or halt the worker without an error + let external_msg = match external_msg { + Ok(m) => m, + Err(kind) => { + match kind { + RADError::Close(_) => return Ok(()), + RADError::StreamClosed => return Ok(()), + _ => return Err(kind.into()) + } + }, + }; + + // Processing message + match external_msg { + C2SMessage::Token(_) => bail!("authentication passed, but the client sent the Token again"), + C2SMessage::Ping(func_id, echo, data) => { + let s2c_ping: Vec = S2CMessage::Ping(session.user.uuid, func_id, echo, data).into(); + + // Echo check + if echo { + ws.send(Message::Binary(s2c_ping.clone())).await? + } + // Sending to others + let _ = session.subs_tx.send(s2c_ping); + }, + C2SMessage::Sub(uuid) => { + tracing::debug!("[WebSocket] {} subscribes to {}", session.user.username, uuid); + + // Doesn't allow to subscribe to yourself + if session.user.uuid != uuid { + // Creates a channel to send pings to a subscriber if it can't find an existing one + let rx = match state.subscribes.get(&uuid) { + Some(tx) => tx.subscribe(), + None => { + let (tx, rx) = broadcast::channel(32); + state.subscribes.insert(uuid, tx); + rx + }, + }; + let handle = tokio::spawn(sub_worker(session.own_tx.clone(), rx)).abort_handle(); + session.sub_workers_aborthandles.insert(uuid, handle); + } + }, + C2SMessage::Unsub(uuid) => { + tracing::debug!("[WebSocket] {} unsubscribes from {}", session.user.username, uuid); + + match session.sub_workers_aborthandles.get(&uuid) { + Some(handle) => handle.abort(), + None => tracing::warn!("[WebSocket] {} was not subscribed.", session.user.username), + }; + }, + } + }, + internal_msg = session.own_rx.recv() => { + let internal_msg = internal_msg.ok_or(anyhow::anyhow!("Unexpected error! Session channel broken!"))?; + match internal_msg { + SessionMessage::Ping(msg) => { + ws.send(Message::Binary(msg)).await? + }, + SessionMessage::Banned => { + let _ = ban_action(ws).await + .inspect_err( + |kind| tracing::warn!("[WebSocket] Didn't get the ban message due to {}", kind) + ); + bail!("{} banned!", session.user.username) + }, + } + } + } + } +} + +async fn sub_worker(tx_main: mpsc::Sender, mut rx: broadcast::Receiver>) { + loop { + let msg = match rx.recv().await { + Ok(m) => m, + Err(kind) => { + tracing::error!("[Subscribes_Worker] Broadcast error! {}", kind); + return; + }, + }; + match tx_main.send(SessionMessage::Ping(msg)).await { + Ok(_) => (), + Err(kind) => { + tracing::error!("[Subscribes_Worker] Session error! {}", kind); + return; + }, + } + } +} + +async fn authenticate(socket: &mut WebSocket, state: &AppState) -> Result { + match socket.recv_and_decode().await { + Ok(msg) => { + match msg { + C2SMessage::Token(token) => { + let token = String::from_utf8(token.to_vec()).map_err(|_| AuthModeError::ConvertError)?; + match state.user_manager.get(&token) { + Some(user) => { + if socket.send(Message::Binary(S2CMessage::Auth.into())).await.is_err() { + Err(AuthModeError::SendError) + } else if !user.banned { + Ok(user.clone()) + } else { + let _ = ban_action(socket).await + .inspect_err( + |kind| tracing::warn!("[WebSocket] Didn't get the ban message due to {}", kind) + ); + Err(AuthModeError::Banned(user.username.clone())) + } + }, + None => { + if socket.send( + Message::Close(Some(axum::extract::ws::CloseFrame { code: 4000, reason: "Re-auth".into() })) + ).await.is_err() { + Err(AuthModeError::SendError) + } else { + Err(AuthModeError::AuthenticationFailure) + } + }, + } + }, + _ => { + Err(AuthModeError::UnauthorizedAction) + } + } + }, + Err(err) => { + Err(AuthModeError::RecvError(err)) + }, + } +} + +async fn ban_action(ws: &mut WebSocket) -> anyhow::Result<()> { + ws.send(Message::Binary(S2CMessage::Toast(2, "You're banned!".to_string(), None).into())).await?; + tokio::time::sleep(std::time::Duration::from_secs(6)).await; + ws.send(Message::Close(Some(axum::extract::ws::CloseFrame { code: 4001, reason: "You're banned!".into() }))).await?; + + Ok(()) +} \ No newline at end of file diff --git a/src/api/figura/websocket/mod.rs b/src/api/figura/websocket/mod.rs new file mode 100644 index 0000000..d4de481 --- /dev/null +++ b/src/api/figura/websocket/mod.rs @@ -0,0 +1,8 @@ +// mod websocket; +mod handler; +mod processor; +mod types; + +// pub use websocket::*; +pub use handler::initial; +pub use types::*; \ No newline at end of file diff --git a/src/api/figura/websocket/processor.rs b/src/api/figura/websocket/processor.rs new file mode 100644 index 0000000..0b4c9da --- /dev/null +++ b/src/api/figura/websocket/processor.rs @@ -0,0 +1,32 @@ +use axum::extract::ws::{Message, WebSocket}; + +use super::{C2SMessage, RADError}; + +pub trait RecvAndDecode { + async fn recv_and_decode(&mut self) -> Result; +} + +impl RecvAndDecode for WebSocket { + async fn recv_and_decode(&mut self) -> Result { + if let Some(msg) = self.recv().await { + match msg { + Ok(msg) => { + match msg { + Message::Close(frame) => Err(RADError::Close(frame.map(|f| format!("code: {}, reason: {}", f.code, f.reason)))), + _ => { + match C2SMessage::try_from(msg.clone().into_data().as_slice()) { + Ok(decoded) => Ok(decoded), + Err(e) => { + Err(RADError::DecodeError(e, hex::encode(msg.into_data()))) + }, + } + } + } + }, + Err(e) => Err(RADError::WebSocketError(e)), + } + } else { + Err(RADError::StreamClosed) + } + } +} \ No newline at end of file diff --git a/src/api/figura/types/c2s.rs b/src/api/figura/websocket/types/c2s.rs similarity index 80% rename from src/api/figura/types/c2s.rs rename to src/api/figura/websocket/types/c2s.rs index db2d4e7..945c228 100644 --- a/src/api/figura/types/c2s.rs +++ b/src/api/figura/websocket/types/c2s.rs @@ -5,27 +5,27 @@ use std::convert::{TryFrom, TryInto}; #[repr(u8)] #[derive(Debug, Clone, PartialEq, Eq)] -pub enum C2SMessage<'a> { - Token(&'a [u8]) = 0, - Ping(u32, bool, &'a [u8]) = 1, +pub enum C2SMessage { + Token(Vec) = 0, + Ping(u32, bool, Vec) = 1, Sub(Uuid) = 2, // owo Unsub(Uuid) = 3, } // 6 - 6 -impl<'a> TryFrom<&'a [u8]> for C2SMessage<'a> { +impl TryFrom<&[u8]> for C2SMessage { type Error = MessageLoadError; - fn try_from(buf: &'a [u8]) -> Result>::Error> { + fn try_from(buf: &[u8]) -> Result { if buf.is_empty() { Err(MessageLoadError::BadLength("C2SMessage", 1, false, 0)) } else { match buf[0] { - 0 => Ok(C2SMessage::Token(&buf[1..])), + 0 => Ok(C2SMessage::Token(buf[1..].to_vec())), 1 => { if buf.len() >= 6 { Ok(C2SMessage::Ping( u32::from_be_bytes((&buf[1..5]).try_into().unwrap()), buf[5] != 0, - &buf[6..], + buf[6..].to_vec(), )) } else { Err(MessageLoadError::BadLength( @@ -73,10 +73,10 @@ impl<'a> TryFrom<&'a [u8]> for C2SMessage<'a> { } } } -impl<'a> From> for Box<[u8]> { - fn from(val: C2SMessage<'a>) -> Self { +impl From for Vec { + fn from(val: C2SMessage) -> Self { use std::iter; - let a: Box<[u8]> = match val { + let a: Vec = match val { C2SMessage::Token(t) => iter::once(0).chain(t.iter().copied()).collect(), C2SMessage::Ping(p, s, d) => iter::once(1) .chain(p.to_be_bytes()) @@ -90,11 +90,11 @@ impl<'a> From> for Box<[u8]> { } } -impl<'a> C2SMessage<'a> { - pub fn to_array(&self) -> Box<[u8]> { - >>::into(self.clone()) - } - pub fn to_vec(&self) -> Vec { - self.to_array().to_vec() - } -} \ No newline at end of file +// impl<'a> C2SMessage<'a> { +// pub fn to_array(&self) -> Box<[u8]> { +// >>::into(self.clone()) +// } +// pub fn to_vec(&self) -> Vec { +// self.to_array().to_vec() +// } +// } \ No newline at end of file diff --git a/src/api/figura/types/errors.rs b/src/api/figura/websocket/types/errors.rs similarity index 60% rename from src/api/figura/types/errors.rs rename to src/api/figura/websocket/types/errors.rs index ed8aa94..b6e7b2d 100644 --- a/src/api/figura/types/errors.rs +++ b/src/api/figura/websocket/types/errors.rs @@ -1,6 +1,8 @@ use std::fmt::*; use std::ops::RangeInclusive; +use thiserror::Error; + #[derive(Debug)] pub enum MessageLoadError { BadEnum(&'static str, RangeInclusive, usize), @@ -23,6 +25,35 @@ impl Display for MessageLoadError { } } } + +#[derive(Error, Debug)] +pub enum RADError { + #[error("message decode error due: {0}, invalid data: {1}")] + DecodeError(MessageLoadError, String), + #[error("close, frame: {0:?}")] + Close(Option), + #[error(transparent)] + WebSocketError(#[from] axum::Error), + #[error("stream closed")] + StreamClosed, +} + +#[derive(Error, Debug)] +pub enum AuthModeError { + #[error("token recieve error due {0}")] + RecvError(RADError), + #[error("action attempt without authentication")] + UnauthorizedAction, + #[error("convert error, bytes into string")] + ConvertError, + #[error("can't send, websocket broken")] + SendError, + #[error("authentication failure, sending re-auth...")] + AuthenticationFailure, + #[error("{0} banned")] + Banned(String), +} + #[cfg(test)] #[test] fn message_load_error_display() { diff --git a/src/api/figura/websocket/types/mod.rs b/src/api/figura/websocket/types/mod.rs new file mode 100644 index 0000000..d449427 --- /dev/null +++ b/src/api/figura/websocket/types/mod.rs @@ -0,0 +1,9 @@ +mod c2s; +mod s2c; +mod errors; +mod session; + +pub use session::*; +pub use errors::*; +pub use c2s::*; +pub use s2c::*; \ No newline at end of file diff --git a/src/api/figura/types/s2c.rs b/src/api/figura/websocket/types/s2c.rs similarity index 77% rename from src/api/figura/types/s2c.rs rename to src/api/figura/websocket/types/s2c.rs index d62cdc3..b17aeaa 100644 --- a/src/api/figura/types/s2c.rs +++ b/src/api/figura/websocket/types/s2c.rs @@ -5,17 +5,19 @@ use uuid::Uuid; #[repr(u8)] #[derive(Debug, Clone, PartialEq, Eq)] -pub enum S2CMessage<'a> { +pub enum S2CMessage { Auth = 0, - Ping(Uuid, u32, bool, &'a [u8]) = 1, + Ping(Uuid, u32, bool, Vec) = 1, Event(Uuid) = 2, // Updates avatar for other players - Toast(u8, &'a str, Option<&'a str>) = 3, - Chat(&'a str) = 4, + Toast(u8, String, Option) = 3, + Chat(String) = 4, Notice(u8) = 5, } -impl<'a> TryFrom<&'a [u8]> for S2CMessage<'a> { +impl TryFrom<&[u8]> for S2CMessage { + type Error = MessageLoadError; - fn try_from(buf: &'a [u8]) -> Result>::Error> { + + fn try_from(buf: &[u8]) -> Result { if buf.is_empty() { Err(MessageLoadError::BadLength("S2CMessage", 1, false, 0)) } else { @@ -35,7 +37,7 @@ impl<'a> TryFrom<&'a [u8]> for S2CMessage<'a> { Uuid::from_bytes((&buf[1..17]).try_into().unwrap()), u32::from_be_bytes((&buf[17..21]).try_into().unwrap()), buf[21] != 0, - &buf[22..], + buf[22..].to_vec(), )) } else { Err(BadLength("S2CMessage::Ping", 22, false, buf.len())) @@ -56,12 +58,13 @@ impl<'a> TryFrom<&'a [u8]> for S2CMessage<'a> { } } } -impl<'a> From> for Box<[u8]> { - fn from(val: S2CMessage<'a>) -> Self { + +impl From for Vec { + fn from(val: S2CMessage) -> Self { use std::iter::once; use S2CMessage::*; match val { - Auth => Box::new([0]), + Auth => vec![0], Ping(u, i, s, d) => once(1) .chain(u.into_bytes().iter().copied()) .chain(i.to_be_bytes().iter().copied()) @@ -74,20 +77,20 @@ impl<'a> From> for Box<[u8]> { .chain(h.as_bytes().iter().copied()) .chain( d.into_iter() - .flat_map(|s| once(0).chain(s.as_bytes().iter().copied())), + .flat_map(|s| once(0).chain(s.as_bytes().iter().copied()).collect::>()), // FIXME: Try find other solution ) .collect(), Chat(c) => once(4).chain(c.as_bytes().iter().copied()).collect(), - Notice(t) => Box::new([5, t]), + Notice(t) => vec![5, t], } } } -impl<'a> S2CMessage<'a> { - pub fn to_array(&self) -> Box<[u8]> { - >>::into(self.clone()) - } - pub fn to_vec(&self) -> Vec { - self.to_array().to_vec() - } -} +// impl<'a> S2CMessage<'a> { +// pub fn to_array(&self) -> Box<[u8]> { +// >>::into(self.clone()) +// } +// pub fn to_vec(&self) -> Vec { +// self.to_array().to_vec() +// } +// } diff --git a/src/api/figura/websocket/types/session.rs b/src/api/figura/websocket/types/session.rs new file mode 100644 index 0000000..c8e1e99 --- /dev/null +++ b/src/api/figura/websocket/types/session.rs @@ -0,0 +1,15 @@ +use dashmap::DashMap; +use tokio::{sync::{broadcast, mpsc}, task::AbortHandle}; + +pub struct WSSession { + pub user: crate::auth::Userinfo, + pub own_tx: mpsc::Sender, + pub own_rx: mpsc::Receiver, + pub subs_tx: broadcast::Sender>, + pub sub_workers_aborthandles: DashMap, +} + +pub enum SessionMessage { + Ping(Vec), + Banned, +} \ No newline at end of file diff --git a/src/api/v1/http2ws.rs b/src/api/v1/http2ws.rs index c5a1af0..323b03e 100644 --- a/src/api/v1/http2ws.rs +++ b/src/api/v1/http2ws.rs @@ -28,7 +28,7 @@ pub(super) async fn raw( Some(uuid) => { // for only one let tx = state.session.get(&uuid).ok_or_else(|| { warn!("unknown uuid"); crate::ApiError::NotFound })?; - tx.value().send(payload).await.map_err(|err| internal_and_log(err))?; + tx.value().send(crate::api::figura::SessionMessage::Ping(payload)).await.map_err(internal_and_log)?; Ok("ok") }, None => { @@ -53,8 +53,8 @@ pub(super) async fn sub_raw( match query.uuid { Some(uuid) => { // for only one - let tx = state.broadcasts.get(&uuid).ok_or_else(|| { warn!("unknown uuid"); crate::ApiError::NotFound })?; - tx.value().send(payload).map_err(|err| internal_and_log(err))?; + let tx = state.subscribes.get(&uuid).ok_or_else(|| { warn!("unknown uuid"); crate::ApiError::NotFound })?; + tx.value().send(payload).map_err(internal_and_log)?; Ok("ok") }, None => { diff --git a/src/api/v1/users.rs b/src/api/v1/users.rs index 73745bf..5b39881 100644 --- a/src/api/v1/users.rs +++ b/src/api/v1/users.rs @@ -29,7 +29,8 @@ pub(super) async fn ban( info!("Trying ban user: {uuid}"); - state.user_manager.ban(&Userinfo { uuid: uuid, banned: true, ..Default::default() }); + if let Some(tx) = state.session.get(&uuid) {let _ = tx.send(crate::api::figura::SessionMessage::Banned).await;} + state.user_manager.ban(&Userinfo { uuid, banned: true, ..Default::default() }); Ok("ok") } diff --git a/src/auth/auth.rs b/src/auth/auth.rs index 62cfeb7..1b4d5d7 100644 --- a/src/auth/auth.rs +++ b/src/auth/auth.rs @@ -83,8 +83,8 @@ async fn fetch_json( trace!("{res:?}"); match res.status().as_u16() { 200 => { - let json = serde_json::from_str::(&res.text().await?).with_context(|| format!("Cant deserialize"))?; - let uuid = get_id_json(&json).with_context(|| format!("Cant get UUID"))?; + let json = serde_json::from_str::(&res.text().await?).with_context(|| "Cant deserialize".to_string())?; + let uuid = get_id_json(&json).with_context(|| "Cant get UUID".to_string())?; Ok((uuid, auth_provider.clone())) } _ => Err(FetchError::WrongResponse(res.status().as_u16(), res.text().await)), @@ -131,7 +131,7 @@ pub async fn has_joined( // Choosing what error return // Returns if some internals errors occured - if errors.len() != 0 { + if !errors.is_empty() { error!("Something wrong with your authentification providers!\nMisses: {misses:?}\nErrors: {errors:?}"); Err(anyhow::anyhow!("{:?}", errors)) @@ -203,7 +203,7 @@ impl UManager { pub fn insert_user(&self, uuid: Uuid, userinfo: Userinfo) { // self.registered.insert(uuid, userinfo) let usercopy = userinfo.clone(); - self.registered.entry(uuid.clone()) + self.registered.entry(uuid) .and_modify(|exist| { if !userinfo.username.is_empty() { exist.username = userinfo.username }; if !userinfo.auth_provider.is_empty() { exist.auth_provider = userinfo.auth_provider }; diff --git a/src/auth/types.rs b/src/auth/types.rs index 7fb3c96..f5eae03 100644 --- a/src/auth/types.rs +++ b/src/auth/types.rs @@ -51,11 +51,7 @@ impl Default for AuthProvider { impl AuthProvider { pub fn is_empty(&self) -> bool { - if self.name == "Unknown".to_string() { - true - } else { - false - } + self.name == "Unknown" } } diff --git a/src/consts.rs b/src/consts.rs index 05295ac..01d5481 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,22 +1,22 @@ // Environment -pub const LOGGER_ENV: &'static str = "RUST_LOG"; -pub const CONFIG_ENV: &'static str = "RUST_CONFIG"; -pub const LOGS_ENV: &'static str = "LOGS_FOLDER"; -pub const ASSETS_ENV: &'static str = "ASSETS_FOLDER"; -pub const AVATARS_ENV: &'static str = "AVATARS_FOLDER"; +pub const LOGGER_ENV: &str = "RUST_LOG"; +pub const CONFIG_ENV: &str = "RUST_CONFIG"; +pub const LOGS_ENV: &str = "LOGS_FOLDER"; +pub const ASSETS_ENV: &str = "ASSETS_FOLDER"; +pub const AVATARS_ENV: &str = "AVATARS_FOLDER"; // Instance info -pub const SCULPTOR_VERSION: &'static str = env!("CARGO_PKG_VERSION"); -pub const REPOSITORY: &'static str = "shiroyashik/sculptor"; +pub const SCULPTOR_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const REPOSITORY: &str = "shiroyashik/sculptor"; // reqwest parameters -pub const USER_AGENT: &'static str = "reqwest"; +pub const USER_AGENT: &str = "reqwest"; pub const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); // Figura update checker -pub const FIGURA_RELEASES_URL: &'static str = "https://api.github.com/repos/figuramc/figura/releases"; -pub const FIGURA_DEFAULT_VERSION: &'static str = "0.1.4"; +pub const FIGURA_RELEASES_URL: &str = "https://api.github.com/repos/figuramc/figura/releases"; +pub const FIGURA_DEFAULT_VERSION: &str = "0.1.4"; // Figura Assets -pub const FIGURA_ASSETS_ZIP_URL: &'static str = "https://github.com/FiguraMC/Assets/archive/refs/heads/main.zip"; -pub const FIGURA_ASSETS_COMMIT_URL: &'static str = "https://api.github.com/repos/FiguraMC/Assets/commits/main"; \ No newline at end of file +pub const FIGURA_ASSETS_ZIP_URL: &str = "https://github.com/FiguraMC/Assets/archive/refs/heads/main.zip"; +pub const FIGURA_ASSETS_COMMIT_URL: &str = "https://api.github.com/repos/FiguraMC/Assets/commits/main"; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index cdbe789..379cfe3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![allow(clippy::module_inception)] use anyhow::Result; use axum::{ extract::DefaultBodyLimit, routing::{delete, get, post, put}, Router @@ -6,9 +7,8 @@ use dashmap::DashMap; use tracing_panic::panic_hook; use tracing_subscriber::{fmt::{self, time::ChronoLocal}, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; use std::{path::PathBuf, sync::Arc, env::var}; -use tokio::{fs, sync::{broadcast, mpsc, RwLock}, time::Instant}; +use tokio::{fs, sync::RwLock, time::Instant}; use tower_http::trace::TraceLayer; -use uuid::Uuid; use lazy_static::lazy_static; // Consts @@ -31,28 +31,12 @@ use auth::{UManager, check_auth}; // Config mod state; -use state::Config; +use state::{Config, AppState}; // Utils mod utils; use utils::*; -#[derive(Debug, Clone)] -pub struct AppState { - /// Uptime - uptime: Instant, - /// User manager - user_manager: Arc, - /// Send into WebSocket - session: Arc>>>, - /// Ping broadcasts for WebSocket connections - broadcasts: Arc>>>, - /// Current configuration - config: Arc>, - /// Caching Figura Versions - figura_versions: Arc>>, -} - lazy_static! { pub static ref LOGGER_VAR: String = { var(LOGGER_ENV).unwrap_or(String::from("info")) @@ -77,7 +61,7 @@ async fn main() -> Result<()> { let _ = dotenvy::dotenv(); // 2. Set up logging - let file_appender = tracing_appender::rolling::never(&*LOGS_VAR, get_log_file(&*LOGS_VAR)); + let file_appender = tracing_appender::rolling::never(&*LOGS_VAR, get_log_file(&LOGS_VAR)); let timer = ChronoLocal::new(String::from("%Y-%m-%dT%H:%M:%S%.3f%:z")); let file_layer = fmt::layer() @@ -147,7 +131,7 @@ async fn app() -> Result { // Config let config = Arc::new(RwLock::new(Config::parse(CONFIG_VAR.clone().into()))); let listen = config.read().await.listen.clone(); - let limit = get_limit_as_bytes(config.read().await.limitations.max_avatar_size.clone() as usize); + let limit = get_limit_as_bytes(config.read().await.limitations.max_avatar_size as usize); if config.read().await.assets_updater_enabled { // Force update assets if folder or hash file doesn't exists. @@ -179,15 +163,17 @@ async fn app() -> Result { uptime: Instant::now(), user_manager: Arc::new(UManager::new()), session: Arc::new(DashMap::new()), - broadcasts: Arc::new(DashMap::new()), + subscribes: Arc::new(DashMap::new()), figura_versions: Arc::new(RwLock::new(None)), config, }; + // FIXME: FIXME: FIXME: ПЕРЕДЕЛАЙ ЭТО! НЕМЕДЛЕННО! ЕБУЧИЙ ПОЗОР :< // Automatic update of configuration while the server is running let config_update = Arc::clone(&state.config); - let user_manager = Arc::clone(&state.user_manager); - update_advanced_users(&config_update.read().await.advanced_users.clone(), &user_manager); + let umanager = Arc::clone(&state.user_manager); + let session = Arc::clone(&state.session); + update_advanced_users(&config_update.read().await.advanced_users.clone(), &umanager, &session).await; tokio::spawn(async move { loop { tokio::time::sleep(std::time::Duration::from_secs(10)).await; @@ -197,14 +183,15 @@ async fn app() -> Result { if new_config != *config { tracing::info!("Server configuration modification detected!"); *config = new_config; - update_advanced_users(&config.advanced_users.clone(), &user_manager); + update_advanced_users(&config.advanced_users.clone(), &umanager, &session).await; } } }); if state.config.read().await.mc_folder.exists() { tokio::spawn(update_bans_from_minecraft( state.config.read().await.mc_folder.clone(), - Arc::clone(&state.user_manager) + Arc::clone(&state.user_manager), + Arc::clone(&state.session) )); } diff --git a/src/state/config.rs b/src/state/config.rs index 24984c5..3637509 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -62,11 +62,11 @@ pub struct BannedPlayer { pub name: String, } -impl Into for BannedPlayer { - fn into(self) -> Userinfo { +impl From for Userinfo { + fn from(val: BannedPlayer) -> Self { Userinfo { - uuid: self.uuid, - username: self.name, + uuid: val.uuid, + username: val.name, banned: true, ..Default::default() } diff --git a/src/state/mod.rs b/src/state/mod.rs index 31bcd43..ef7692e 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -1,4 +1,5 @@ mod config; mod state; -pub use config::*; \ No newline at end of file +pub use config::*; +pub use state::*; \ No newline at end of file diff --git a/src/state/state.rs b/src/state/state.rs index e69de29..70601e6 100644 --- a/src/state/state.rs +++ b/src/state/state.rs @@ -0,0 +1,23 @@ +use std::sync::Arc; + +use dashmap::DashMap; +use tokio::{sync::*, time::Instant}; +use uuid::Uuid; + +use crate::{api::figura::SessionMessage, auth::UManager, FiguraVersions}; + +#[derive(Debug, Clone)] +pub struct AppState { + /// Uptime + pub uptime: Instant, + /// User manager + pub user_manager: Arc, + /// Send into WebSocket + pub session: Arc>>, + /// Send messages for subscribers + pub subscribes: Arc>>>, + /// Current configuration + pub config: Arc>, + /// Caching Figura Versions + pub figura_versions: Arc>>, +} \ No newline at end of file diff --git a/src/utils/utils.rs b/src/utils/auxiliary.rs similarity index 84% rename from src/utils/utils.rs rename to src/utils/auxiliary.rs index f6ebe66..a6b6197 100644 --- a/src/utils/utils.rs +++ b/src/utils/auxiliary.rs @@ -30,14 +30,18 @@ pub fn _generate_hex_string(length: usize) -> String { hex::encode(random_bytes) } -pub fn update_advanced_users(value: &std::collections::HashMap, umanager: &UManager) { +pub async fn update_advanced_users( + value: &std::collections::HashMap, + umanager: &UManager, + sessions: &dashmap::DashMap> +) { let users: Vec<(Uuid, Userinfo)> = value .iter() .map( |(uuid, userdata)| { ( - uuid.clone(), + *uuid, Userinfo { - uuid: uuid.clone(), + uuid: *uuid, username: userdata.username.clone(), banned: userdata.banned, ..Default::default() @@ -48,12 +52,17 @@ pub fn update_advanced_users(value: &std::collections::HashMap) { +pub async fn update_bans_from_minecraft( + folder: PathBuf, + umanager: std::sync::Arc, + sessions: std::sync::Arc>> +) { let path = folder.join("banned-players.json"); let mut file = tokio::fs::File::open(path.clone()).await.expect("Access denied or banned-players.json doesn't exists!"); let mut data = String::new(); @@ -70,6 +79,7 @@ pub async fn update_bans_from_minecraft(folder: PathBuf, umanager: std::sync::Ar for player in &old_bans { umanager.ban(&player.clone().into()); + if let Some(tx) = sessions.get(&player.uuid) {let _ = tx.send(crate::api::figura::SessionMessage::Banned).await;} } // old_bans @@ -97,6 +107,7 @@ pub async fn update_bans_from_minecraft(folder: PathBuf, umanager: std::sync::Ar if !ban.is_empty() { for player in ban { umanager.ban(&player.clone().into()); + if let Some(tx) = sessions.get(&player.uuid) {let _ = tx.send(crate::api::figura::SessionMessage::Banned).await;} } } else { ban_names = String::from("-")}; info!("List of changes:\n Banned: {ban_names}\n Unbanned: {unban_names}"); diff --git a/src/utils/check_updates.rs b/src/utils/check_updates.rs index 26494d2..f3b8112 100644 --- a/src/utils/check_updates.rs +++ b/src/utils/check_updates.rs @@ -65,10 +65,8 @@ pub async fn get_figura_versions() -> anyhow::Result { if tag_ver > prerelease_ver { prerelease_ver = tag_ver } - } else { - if tag_ver > release_ver { + } else if tag_ver > release_ver { release_ver = tag_ver - } } } if release_ver > prerelease_ver { @@ -115,13 +113,11 @@ pub async fn is_assets_outdated(last_sha: &str) -> anyhow::Result { if contents.lines().count() != 1 { // Lines count in file abnormal Ok(true) + } else if contents == last_sha { + Ok(false) } else { - if contents == last_sha { - Ok(false) - } else { - // SHA in file mismatches with provided SHA - Ok(true) - } + // SHA in file mismatches with provided SHA + Ok(true) } }, Err(err) => if err.kind() == tokio::io::ErrorKind::NotFound { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 171ee7e..f0f1d50 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,7 @@ -mod utils; +mod auxiliary; mod check_updates; mod motd; -pub use utils::*; +pub use auxiliary::*; pub use motd::*; pub use check_updates::*; \ No newline at end of file