Merge pull request #1 from selimaj-dev/fully-functional
Fully functional
This commit is contained in:
@@ -19,3 +19,8 @@ target
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
||||
Generated
+648
@@ -0,0 +1,648 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"rand_core",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.182"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "session-rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.116"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.49.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "session-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
rand = "0.10.0"
|
||||
serde = { version = "1.0.228", features = ["serde_derive"] }
|
||||
serde_json = "1.0.149"
|
||||
sha1 = "0.10.6"
|
||||
tokio = { version = "1.49.0", features = ["io-util", "macros", "net", "rt", "sync", "time"] }
|
||||
@@ -0,0 +1,38 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use session_rs::{Method, session::Session};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Data;
|
||||
|
||||
impl Method for Data {
|
||||
const NAME: &'static str = "data";
|
||||
type Request = String;
|
||||
type Response = String;
|
||||
type Error = String;
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> session_rs::Result<()> {
|
||||
let session = Session::connect("127.0.0.1:8080", "/").await?;
|
||||
|
||||
session.start_receiver();
|
||||
|
||||
println!(
|
||||
"Hi: {:?}",
|
||||
session
|
||||
.request::<Data>("Hello from client".to_string())
|
||||
.await?
|
||||
);
|
||||
|
||||
println!(
|
||||
"Invalid data response: {:?}",
|
||||
session
|
||||
.request::<Data>("invalid_data".to_string())
|
||||
.await?
|
||||
);
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
|
||||
|
||||
session.close().await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use session_rs::{Method, session::Session, ws::WebSocket};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Data;
|
||||
|
||||
impl Method for Data {
|
||||
const NAME: &'static str = "data";
|
||||
type Request = String;
|
||||
type Response = String;
|
||||
type Error = String;
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> session_rs::Result<()> {
|
||||
let listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
println!("Server listening on ws://127.0.0.1:8080");
|
||||
|
||||
loop {
|
||||
let (stream, addr) = listener.accept().await?;
|
||||
println!("New connection: {}", addr);
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Wrap session in Arc so tasks can share it
|
||||
let session = Session::from_ws(
|
||||
WebSocket::handshake(stream)
|
||||
.await
|
||||
.expect("Failed to initialize websocket"),
|
||||
);
|
||||
|
||||
session.start_receiver();
|
||||
|
||||
session
|
||||
.on::<Data, _>(async |_, req| {
|
||||
println!("Msg from client: {req}");
|
||||
|
||||
if req == "invalid_data" {
|
||||
return Err("Invalid data".to_string());
|
||||
}
|
||||
|
||||
Ok("Hello from server".to_string())
|
||||
})
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod server;
|
||||
pub mod session;
|
||||
pub mod ws;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type BoxFuture<'a> = Pin<Box<dyn Future<Output = Option<(bool, serde_json::Value)>> + Send + 'a>>;
|
||||
pub type MethodHandler = Box<dyn Fn(u32, serde_json::Value) -> BoxFuture<'static> + Send + Sync>;
|
||||
|
||||
pub trait Method {
|
||||
const NAME: &'static str;
|
||||
type Request: Serialize + for<'de> Deserialize<'de> + Send + Sync;
|
||||
type Response: Serialize + for<'de> Deserialize<'de>;
|
||||
type Error: Serialize + for<'de> Deserialize<'de>;
|
||||
}
|
||||
|
||||
pub struct GenericMethod;
|
||||
|
||||
impl Method for GenericMethod {
|
||||
const NAME: &'static str = "generic_do_not_use";
|
||||
type Request = serde_json::Value;
|
||||
type Response = serde_json::Value;
|
||||
type Error = serde_json::Value;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
WebSocket(ws::Error),
|
||||
Json(serde_json::Error),
|
||||
Io(std::io::Error),
|
||||
RecvError(tokio::sync::broadcast::error::RecvError),
|
||||
}
|
||||
|
||||
impl From<ws::Error> for Error {
|
||||
fn from(value: ws::Error) -> Self {
|
||||
Self::WebSocket(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::Io(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
Self::Json(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tokio::sync::broadcast::error::RecvError> for Error {
|
||||
fn from(value: tokio::sync::broadcast::error::RecvError) -> Self {
|
||||
Self::RecvError(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub struct SessionServer();
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::{GenericMethod, Method, MethodHandler, ws::WebSocket};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase", tag = "type")]
|
||||
pub enum Message<M: Method> {
|
||||
Request {
|
||||
id: u32,
|
||||
method: String,
|
||||
data: M::Request,
|
||||
},
|
||||
Response {
|
||||
id: u32,
|
||||
result: M::Response,
|
||||
},
|
||||
ErrorResponse {
|
||||
id: u32,
|
||||
error: M::Error,
|
||||
},
|
||||
Notification {
|
||||
method: String,
|
||||
data: M::Request,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
pub ws: WebSocket,
|
||||
id: Arc<Mutex<u32>>,
|
||||
methods: Arc<Mutex<HashMap<String, MethodHandler>>>,
|
||||
tx: broadcast::Sender<(u32, bool, serde_json::Value)>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn clone(&self) -> Self {
|
||||
Self {
|
||||
ws: self.ws.clone(),
|
||||
id: self.id.clone(),
|
||||
methods: self.methods.clone(),
|
||||
tx: self.tx.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn from_ws(ws: WebSocket) -> Self {
|
||||
Self {
|
||||
ws,
|
||||
id: Arc::new(Mutex::new(0)),
|
||||
methods: Arc::new(Mutex::new(HashMap::new())),
|
||||
tx: broadcast::channel(8192).0,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect(addr: &str, path: &str) -> crate::Result<Self> {
|
||||
Ok(Self::from_ws(WebSocket::connect(addr, path).await?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn start_receiver(&self) {
|
||||
let s = self.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match s.ws.read().await {
|
||||
Ok(crate::ws::Frame::Text(text)) => {
|
||||
let Ok(msg) = serde_json::from_str::<Message<GenericMethod>>(&text) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match msg {
|
||||
Message::Request { id, method, data } => {
|
||||
if let Some(m) = s.methods.lock().await.get(&method) {
|
||||
if let Some((err, res)) = (m)(id, data).await {
|
||||
if err {
|
||||
s.respond_error(id, res)
|
||||
.await
|
||||
.expect("Failed to respond");
|
||||
} else {
|
||||
s.respond(id, res).await.expect("Failed to respond");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Response { id, result } => {
|
||||
s.tx.send((id, false, result)).unwrap();
|
||||
}
|
||||
Message::ErrorResponse { id, error } => {
|
||||
s.tx.send((id, true, error)).unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn on<
|
||||
M: Method,
|
||||
Fut: Future<Output = Result<M::Response, M::Error>> + Send + 'static,
|
||||
>(
|
||||
&self,
|
||||
handler: impl Fn(u32, M::Request) -> Fut + Send + Sync + 'static,
|
||||
) {
|
||||
let handler = Arc::new(handler);
|
||||
|
||||
self.methods.lock().await.insert(
|
||||
M::NAME.to_string(),
|
||||
Box::new(move |id, value| {
|
||||
let handler = Arc::clone(&handler);
|
||||
|
||||
Box::pin(async move {
|
||||
Some(
|
||||
match handler(id, serde_json::from_value(value).ok()?).await {
|
||||
Ok(v) => (false, serde_json::to_value(v).ok()?),
|
||||
Err(v) => (true, serde_json::to_value(v).ok()?),
|
||||
},
|
||||
)
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub async fn send<M: Method>(&self, data: &Message<M>) -> crate::Result<()> {
|
||||
self.ws
|
||||
.send_text_payload(&serde_json::to_vec(&data)?)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn use_id(&self) -> u32 {
|
||||
let mut id = self.id.lock().await;
|
||||
*id += 1;
|
||||
*id
|
||||
}
|
||||
|
||||
pub async fn request<M: Method>(
|
||||
&self,
|
||||
req: M::Request,
|
||||
) -> crate::Result<std::result::Result<M::Response, M::Error>> {
|
||||
let id = self.use_id().await;
|
||||
|
||||
self.send::<M>(&Message::Request {
|
||||
id,
|
||||
method: M::NAME.to_string(),
|
||||
data: req,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mut rx = self.tx.subscribe();
|
||||
|
||||
loop {
|
||||
let r = rx.recv().await?;
|
||||
|
||||
if r.0 == id {
|
||||
break Ok(if r.1 {
|
||||
Err(serde_json::from_value(r.2)?)
|
||||
} else {
|
||||
Ok(serde_json::from_value(r.2)?)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn respond(&self, to: u32, val: serde_json::Value) -> crate::Result<()> {
|
||||
self.send::<GenericMethod>(&Message::Response {
|
||||
id: to,
|
||||
result: val,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn respond_error(&self, to: u32, val: serde_json::Value) -> crate::Result<()> {
|
||||
self.send::<GenericMethod>(&Message::ErrorResponse { id: to, error: val })
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn notify<M: Method>(&self, data: M::Request) -> crate::Result<()> {
|
||||
self.send::<M>(&Message::Notification {
|
||||
method: M::NAME.to_string(),
|
||||
data,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn close(&self) -> crate::Result<()> {
|
||||
Ok(self.ws.close().await?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(std::io::Error),
|
||||
InvalidFrame(String),
|
||||
HandshakeFailed(String),
|
||||
Utf8(FromUtf8Error),
|
||||
ConnectionClosed,
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::Io(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for Error {
|
||||
fn from(value: FromUtf8Error) -> Self {
|
||||
Self::Utf8(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
use base64::Engine;
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::sync::Arc;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
net::TcpStream,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use super::WebSocket;
|
||||
|
||||
pub async fn handle_websocket_handshake(stream: &mut TcpStream) -> std::io::Result<()> {
|
||||
let (read_half, mut write_half) = stream.split();
|
||||
let mut reader = BufReader::new(read_half);
|
||||
|
||||
let mut request_line = String::new();
|
||||
reader.read_line(&mut request_line).await?;
|
||||
let request_line = request_line.trim_end();
|
||||
|
||||
if request_line.starts_with("HEAD") {
|
||||
write_half
|
||||
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !request_line.starts_with("GET") {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Invalid HTTP method",
|
||||
));
|
||||
}
|
||||
|
||||
use std::collections::HashMap;
|
||||
let mut headers = HashMap::new();
|
||||
let mut line = String::new();
|
||||
|
||||
loop {
|
||||
line.clear();
|
||||
reader.read_line(&mut line).await?;
|
||||
if line == "\r\n" {
|
||||
break;
|
||||
}
|
||||
if let Some((k, v)) = line.split_once(':') {
|
||||
headers.insert(k.trim().to_lowercase(), v.trim().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if headers
|
||||
.get("upgrade")
|
||||
.map(|v| !v.eq_ignore_ascii_case("websocket"))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
write_half
|
||||
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let key = headers
|
||||
.get("sec-websocket-key")
|
||||
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Missing key"))?;
|
||||
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD as Base64;
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(key.as_bytes());
|
||||
hasher.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
let accept = Base64.encode(hasher.finalize());
|
||||
|
||||
let response = format!(
|
||||
"HTTP/1.1 101 Switching Protocols\r\n\
|
||||
Upgrade: websocket\r\n\
|
||||
Connection: Upgrade\r\n\
|
||||
Sec-WebSocket-Accept: {}\r\n\r\n",
|
||||
accept
|
||||
);
|
||||
|
||||
write_half.write_all(response.as_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl WebSocket {
|
||||
pub async fn handshake(mut stream: TcpStream) -> super::Result<Self> {
|
||||
handle_websocket_handshake(&mut stream).await?;
|
||||
|
||||
let (read, write) = stream.into_split();
|
||||
|
||||
Ok(Self {
|
||||
id: rand::random(),
|
||||
reader: Arc::new(Mutex::new(read)),
|
||||
writer: Arc::new(Mutex::new(write)),
|
||||
is_server: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Connect to a WebSocket server and perform the handshake
|
||||
pub async fn connect(addr: &str, path: &str) -> super::Result<Self> {
|
||||
// 1. TCP connect
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
|
||||
// 2. Generate Sec-WebSocket-Key
|
||||
let key_bytes: [u8; 16] = rand::random();
|
||||
let key = base64::prelude::BASE64_STANDARD.encode(&key_bytes);
|
||||
|
||||
// 3. Send HTTP Upgrade request
|
||||
let request = format!(
|
||||
"GET {} HTTP/1.1\r\n\
|
||||
Host: {}\r\n\
|
||||
Upgrade: websocket\r\n\
|
||||
Connection: Upgrade\r\n\
|
||||
Sec-WebSocket-Key: {}\r\n\
|
||||
Sec-WebSocket-Version: 13\r\n\
|
||||
\r\n",
|
||||
path, addr, key
|
||||
);
|
||||
stream.write_all(request.as_bytes()).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
// 4. Read HTTP response
|
||||
let mut reader = BufReader::new(&mut stream);
|
||||
let mut status_line = String::new();
|
||||
reader.read_line(&mut status_line).await?;
|
||||
if !status_line.starts_with("HTTP/1.1 101") {
|
||||
return Err(super::Error::HandshakeFailed(format!(
|
||||
"Expected 101 Switching Protocols, got: {}",
|
||||
status_line.trim_end()
|
||||
)));
|
||||
}
|
||||
|
||||
// Read headers
|
||||
let mut sec_accept = None;
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line).await?;
|
||||
let line = line.trim_end();
|
||||
if line.is_empty() {
|
||||
break; // end of headers
|
||||
}
|
||||
if let Some((k, v)) = line.split_once(':') {
|
||||
if k.eq_ignore_ascii_case("sec-websocket-accept") {
|
||||
sec_accept = Some(v.trim().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Verify Sec-WebSocket-Accept
|
||||
let expected = {
|
||||
let mut sha1 = Sha1::new();
|
||||
sha1.update(key.as_bytes());
|
||||
sha1.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
base64::prelude::BASE64_STANDARD.encode(sha1.finalize())
|
||||
};
|
||||
if sec_accept.as_deref() != Some(expected.as_str()) {
|
||||
return Err(super::Error::HandshakeFailed(
|
||||
"Sec-WebSocket-Accept mismatch".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// 6. Upgrade succeeded, split stream
|
||||
let (read, write) = stream.into_split();
|
||||
|
||||
Ok(Self {
|
||||
id: rand::random(),
|
||||
reader: Arc::new(Mutex::new(read)),
|
||||
writer: Arc::new(Mutex::new(write)),
|
||||
is_server: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
pub mod error;
|
||||
pub mod handshake;
|
||||
pub use error::{Error, Result};
|
||||
|
||||
use std::{
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Frame {
|
||||
Text(String),
|
||||
Binary(Vec<u8>),
|
||||
Ping,
|
||||
Pong,
|
||||
Close,
|
||||
}
|
||||
|
||||
pub struct WebSocket {
|
||||
pub(crate) reader: Arc<Mutex<tokio::net::tcp::OwnedReadHalf>>,
|
||||
pub(crate) writer: Arc<Mutex<tokio::net::tcp::OwnedWriteHalf>>,
|
||||
pub(crate) id: u64,
|
||||
pub(crate) is_server: bool,
|
||||
}
|
||||
|
||||
impl Clone for WebSocket {
|
||||
fn clone(&self) -> Self {
|
||||
WebSocket {
|
||||
reader: self.reader.clone(),
|
||||
writer: self.writer.clone(),
|
||||
is_server: self.is_server.clone(),
|
||||
id: self.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for WebSocket {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for WebSocket {}
|
||||
|
||||
impl Hash for WebSocket {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl WebSocket {
|
||||
async fn send_frame(&self, opcode: u8, payload: &[u8]) -> Result<()> {
|
||||
let mut writer = self.writer.lock().await;
|
||||
|
||||
let mut header = Vec::with_capacity(10);
|
||||
let mask_bit = if self.is_server { 0x80 } else { 0x00 };
|
||||
header.push(0x80 | opcode); // FIN + opcode
|
||||
|
||||
let len = payload.len();
|
||||
if len < 126 {
|
||||
header.push((len as u8) | mask_bit);
|
||||
} else if len <= 0xFFFF {
|
||||
header.push(126 | mask_bit);
|
||||
header.extend_from_slice(&(len as u16).to_be_bytes());
|
||||
} else {
|
||||
header.push(127 | mask_bit);
|
||||
header.extend_from_slice(&(len as u64).to_be_bytes());
|
||||
}
|
||||
|
||||
if self.is_server {
|
||||
// Generate 4-byte mask key
|
||||
let mask_key: [u8; 4] = rand::random();
|
||||
header.extend_from_slice(&mask_key);
|
||||
|
||||
// Mask the payload
|
||||
let mut masked_payload = payload.to_vec();
|
||||
for i in 0..masked_payload.len() {
|
||||
masked_payload[i] ^= mask_key[i % 4];
|
||||
}
|
||||
|
||||
writer.write_all(&header).await?;
|
||||
writer.write_all(&masked_payload).await?;
|
||||
} else {
|
||||
writer.write_all(&header).await?;
|
||||
writer.write_all(payload).await?;
|
||||
}
|
||||
|
||||
writer.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl WebSocket {
|
||||
pub async fn send(&self, msg: &str) -> Result<()> {
|
||||
self.send_frame(0x1, msg.as_bytes()).await
|
||||
}
|
||||
|
||||
pub async fn send_text_payload(&self, payload: &[u8]) -> Result<()> {
|
||||
self.send_frame(0x1, payload).await
|
||||
}
|
||||
|
||||
pub async fn send_bin(&self, payload: &[u8]) -> Result<()> {
|
||||
self.send_frame(0x2, payload).await
|
||||
}
|
||||
|
||||
pub async fn send_ping(&self) -> Result<()> {
|
||||
self.send_frame(0x9, &[]).await
|
||||
}
|
||||
|
||||
pub async fn send_pong(&self) -> Result<()> {
|
||||
self.send_frame(0xA, &[]).await
|
||||
}
|
||||
|
||||
pub async fn close(&self) -> Result<()> {
|
||||
self.send_frame(0x8, &[]).await
|
||||
}
|
||||
|
||||
pub fn start_ping_loop(&self) {
|
||||
let s = self.clone();
|
||||
tokio::task::spawn(async move {
|
||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(15));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
if s.send_ping().await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl WebSocket {
|
||||
/// Read a full WebSocket frame (handling masking and control frames)
|
||||
/// Returns (opcode, payload)
|
||||
pub async fn read_frame(&self) -> Result<(bool, u8, Vec<u8>)> {
|
||||
let mut reader = self.reader.lock().await;
|
||||
|
||||
// --- 1. Read first 2-byte header ---
|
||||
let mut header = [0u8; 2];
|
||||
reader.read_exact(&mut header).await?;
|
||||
|
||||
let fin = header[0] & 0x80 != 0;
|
||||
let opcode = header[0] & 0x0F;
|
||||
let masked = header[1] & 0x80 != 0;
|
||||
let mut payload_len = (header[1] & 0x7F) as u64;
|
||||
|
||||
// --- 2. Read extended payload length if necessary ---
|
||||
if payload_len == 126 {
|
||||
let mut buf = [0u8; 2];
|
||||
reader.read_exact(&mut buf).await?;
|
||||
payload_len = u16::from_be_bytes(buf) as u64;
|
||||
} else if payload_len == 127 {
|
||||
let mut buf = [0u8; 8];
|
||||
reader.read_exact(&mut buf).await?;
|
||||
payload_len = u64::from_be_bytes(buf);
|
||||
}
|
||||
|
||||
let payload = if masked {
|
||||
// --- 3. Read mask key ---
|
||||
let mut mask = [0u8; 4];
|
||||
reader.read_exact(&mut mask).await?;
|
||||
let mut payload = vec![0u8; payload_len as usize];
|
||||
if payload_len > 0 {
|
||||
reader.read_exact(&mut payload).await?;
|
||||
for i in 0..payload.len() {
|
||||
payload[i] ^= mask[i % 4];
|
||||
}
|
||||
}
|
||||
payload
|
||||
} else {
|
||||
// Per spec, client-to-server frames MUST be masked
|
||||
if !self.is_server {
|
||||
self.close().await.ok();
|
||||
return Err(Error::InvalidFrame(
|
||||
"Received unmasked frame from client".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut payload = vec![0u8; payload_len as usize];
|
||||
if payload_len > 0 {
|
||||
reader.read_exact(&mut payload).await?;
|
||||
}
|
||||
payload
|
||||
};
|
||||
|
||||
// --- 6. Return opcode + payload ---
|
||||
Ok((fin, opcode, payload))
|
||||
}
|
||||
|
||||
pub async fn read(&self) -> Result<Frame> {
|
||||
let (fin, opcode, mut payload) = self.read_frame().await?;
|
||||
|
||||
if !fin {
|
||||
// Continuation loop
|
||||
while let (fin, o, mut p) = self.read_frame().await?
|
||||
&& !fin
|
||||
{
|
||||
match o {
|
||||
// Continuation
|
||||
0x0 => payload.append(&mut p),
|
||||
// Close
|
||||
0x8 => {
|
||||
self.close().await.ok();
|
||||
}
|
||||
// Ping
|
||||
0x9 => {
|
||||
self.send_pong().await.ok();
|
||||
}
|
||||
// Pong
|
||||
0xA => {}
|
||||
_ => {
|
||||
self.close().await.ok();
|
||||
return Err(Error::InvalidFrame(format!("Unknown opcode: {opcode}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match opcode {
|
||||
// Close
|
||||
0x8 => {
|
||||
self.close().await.ok();
|
||||
Ok(Frame::Close)
|
||||
}
|
||||
|
||||
// Ping
|
||||
0x9 => {
|
||||
self.send_pong().await.ok();
|
||||
Ok(Frame::Ping)
|
||||
}
|
||||
|
||||
// Pong
|
||||
0xA => Ok(Frame::Pong),
|
||||
|
||||
// Text
|
||||
0x1 => Ok(Frame::Text(String::from_utf8(payload)?)),
|
||||
|
||||
// Binary
|
||||
0x2 => Ok(Frame::Binary(payload)),
|
||||
|
||||
_ => {
|
||||
self.close().await.ok();
|
||||
Err(Error::InvalidFrame(format!("Unknown opcode: {opcode}")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user