[WIP!] feat: ts6 server support
this is really unfinished and *WILL* be rebased! pushing just to let the test run on github's servers (my laptop is too sh*tty)
This commit is contained in:
parent
86b9c8d3a7
commit
a3a19c610d
34 changed files with 4423 additions and 395 deletions
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
|
@ -19,4 +19,4 @@ jobs:
|
|||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
run: cargo test --verbose -- --no-capture
|
||||
|
|
|
|||
344
Cargo.lock
generated
344
Cargo.lock
generated
|
|
@ -2,15 +2,6 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
|
|
@ -82,28 +73,6 @@ version = "1.0.100"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
|
|
@ -129,11 +98,10 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.9"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
|
|
@ -146,49 +114,31 @@ dependencies = [
|
|||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"sync_wrapper",
|
||||
"tower 0.5.2",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
|
|
@ -227,9 +177,9 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.48"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -237,9 +187,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.48"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -249,9 +199,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.47"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
|
@ -273,22 +223,23 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
|||
|
||||
[[package]]
|
||||
name = "console-api"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857"
|
||||
checksum = "e8599749b6667e2f0c910c1d0dff6901163ff698a52d5a39720f61b5be4b20d3"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"tonic",
|
||||
"tonic-prost",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console-subscriber"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01"
|
||||
checksum = "fb4915b7d8dd960457a1b6c380114c2944f728e7c65294ab247ae6b6f1f37592"
|
||||
dependencies = [
|
||||
"console-api",
|
||||
"crossbeam-channel",
|
||||
|
|
@ -401,23 +352,6 @@ dependencies = [
|
|||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
|
|
@ -430,19 +364,13 @@ dependencies = [
|
|||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.11.4",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
|
|
@ -571,22 +499,12 @@ dependencies = [
|
|||
"hyper",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.0",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.4"
|
||||
|
|
@ -594,18 +512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -685,9 +592,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
|
|
@ -755,15 +662,6 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
|
|
@ -837,15 +735,6 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
|
|
@ -857,9 +746,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.13.5"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
|
||||
checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
|
|
@ -867,9 +756,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.13.5"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
||||
checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
|
|
@ -880,9 +769,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.13.5"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
|
||||
checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72"
|
||||
dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
|
@ -896,36 +785,6 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.17"
|
||||
|
|
@ -952,18 +811,6 @@ version = "0.8.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
|
|
@ -1021,9 +868,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.2"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee"
|
||||
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
|
@ -1058,16 +905,6 @@ version = "1.15.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.0"
|
||||
|
|
@ -1132,30 +969,27 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.47.1"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2 0.6.0",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1188,11 +1022,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.7"
|
||||
version = "0.9.10+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0"
|
||||
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
|
||||
dependencies = [
|
||||
"indexmap 2.11.4",
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
|
|
@ -1203,35 +1037,34 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.2"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.3"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
|
||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.3"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.12.3"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
|
||||
checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
|
|
@ -1245,34 +1078,25 @@ dependencies = [
|
|||
"hyper-util",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"socket2 0.5.10",
|
||||
"socket2",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower 0.4.13",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
name = "tonic-prost"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap 1.9.3",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"bytes",
|
||||
"prost",
|
||||
"tonic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1283,10 +1107,15 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
|||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1303,9 +1132,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
|||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
|
|
@ -1314,9 +1143,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.30"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1325,9 +1154,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.34"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
|
|
@ -1346,9 +1175,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.20"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
|
|
@ -1403,9 +1232,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
|||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
|
|
@ -1434,6 +1263,15 @@ dependencies = [
|
|||
"windows-targets 0.53.4",
|
||||
]
|
||||
|
||||
[[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.52.6"
|
||||
|
|
@ -1568,23 +1406,3 @@ name = "winnow"
|
|||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
|
|
|||
14
Cargo.toml
14
Cargo.toml
|
|
@ -5,16 +5,16 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
async-trait = "0.1.89"
|
||||
clap = { version = "4.5.48", features = ["derive"] }
|
||||
once_cell = "1.21.3"
|
||||
tokio = { version = "1.47.1", features = ["full"] }
|
||||
console-subscriber = { version = "0.4.1", optional = true }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.20"
|
||||
clap = { version = "4.5.53", features = ["derive"] }
|
||||
tokio = { version = "1.48.0", features = ["full"] }
|
||||
console-subscriber = { version = "0.5.0", optional = true }
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = "0.3.22"
|
||||
thiserror = "2.0.17"
|
||||
anyhow = "1.0.100"
|
||||
toml = "0.9.7"
|
||||
toml = "0.9.10"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
once_cell = "1.21.3"
|
||||
|
||||
[features]
|
||||
tokio-console = ["tokio/tracing", "console-subscriber"]
|
||||
|
|
|
|||
172
docs/technical/client_cap.txt
Normal file
172
docs/technical/client_cap.txt
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
Client Capab Proposal (v5)
|
||||
Authors: fl and Beeth
|
||||
--------------------------
|
||||
|
||||
|
||||
This document aims to define a protocol for capabilities between a server
|
||||
and a client. These capabs help servers introduce changes to the protocol
|
||||
that could cause problems with clients that do not support the changes.
|
||||
|
||||
This specification defines the "CAP" command, and the available subcommands.
|
||||
This is sent in the form:
|
||||
CAP [subcommand [parameters]]
|
||||
|
||||
CAP
|
||||
---
|
||||
|
||||
A client that supports this specification must send one of three things at
|
||||
the earliest stage of registration. To enter capab negotiation it can either
|
||||
send the command "CAP" without parameters to request a list of capabs, or it
|
||||
can send the subcommand "REQ" to request capabs without first seeing the
|
||||
list of available capabs. A client which does not wish to enter capab
|
||||
negotiation but supports this spec must send the subcommand "END" to notify
|
||||
support of the specification.
|
||||
|
||||
The "CAP" command may be used at any time during the connection to request a
|
||||
list of capabs. If the user enters capab negotiation the server must
|
||||
postpone registration until the client issues the subcommand "END".
|
||||
|
||||
A server receiving the parameterless "CAP" command from a client must reply
|
||||
to this command with a list of capabs supported by the server using the "LS"
|
||||
and "LSL" subcommands. The "LS" subcommand is used when the capab list must
|
||||
be spread across multiple lines and is used for all except the last line.
|
||||
The subcommand "LSL" is used to finish the list of capabs.
|
||||
|
||||
The subcommands "LS" and "LSL" are sent in the form:
|
||||
CAP <subcommand> :<capab> [capab]*
|
||||
|
||||
CAP: REQ
|
||||
--------
|
||||
|
||||
The "REQ" subcommand is used by a client to give a list of capabs it
|
||||
wishes to use.
|
||||
|
||||
The subcommand "REQ" is sent in the form:
|
||||
CAP REQ :<capab> [capab]*
|
||||
|
||||
If any of the requested capabs are prefixed with '-' then the client is
|
||||
notifying the server that it wishes the specified capab to be disabled for
|
||||
the client.
|
||||
|
||||
The responsibility is placed on the client to request a valid list of capabs.
|
||||
The server must either accept the request as issued or reject it. A server
|
||||
must not partially accept parameters of the subcommand "REQ".
|
||||
|
||||
CAP: ACK
|
||||
--------
|
||||
|
||||
The "ACK" subcommand is used by a server to acknowledge that the
|
||||
given capabilities are now being used. The "ACK" subcommand is used by a
|
||||
client to acknowledge the given capabilities are now being used.
|
||||
|
||||
The server places the capabilities it is acknowledging as parameters. The
|
||||
parameters of the subcommand "ACK" will be identical to the parameters issued
|
||||
in the subcommand "REQ" from the client if the request is valid. If any of
|
||||
the requested capabs modify the protocol stream, after sending the subcommand
|
||||
"ACK" the server must then transmit any following messages in the modified
|
||||
protocol stream form.
|
||||
|
||||
The client must issue the subcommand "ACK" for any capabilities that modify
|
||||
the protocol stream, using the capabs that modify the protocol stream as the
|
||||
subcommand "ACK" parameters. A client must know which capabs modify the
|
||||
protocol stream and send the subcommand "ACK" before transmitting any
|
||||
messages in the modified protocol stream. A client must not issue the
|
||||
subcommand "ACK" for a capab without first receiving the subcommand "ACK"
|
||||
containing that capab from the server.
|
||||
|
||||
A client must not issue the subcommand "ACK" for capabs that do not modify
|
||||
the protocol stream.
|
||||
|
||||
The subcommand "ACK" is sent in the following form:
|
||||
CAP ACK :<capab> [capab]*
|
||||
|
||||
CAP: NAK
|
||||
--------
|
||||
|
||||
The "NAK" subcommand is used by a server to notify a client that the
|
||||
requested list of capabs are invalid.
|
||||
|
||||
A client receiving the subcommand "NAK" must request again the capabs it
|
||||
wishes to use. The parameters of the subcommand "NAK" will be identical
|
||||
to the parameters issued in the subcommand "REQ" from the client if the
|
||||
request is invalid.
|
||||
|
||||
The subcommand "NAK" is sent in the following form:
|
||||
CAP NAK :<capab> [capab]*
|
||||
|
||||
CAP: CLEAR
|
||||
----------
|
||||
|
||||
The "CLEAR" subcommand is used by a server to notify a client that all
|
||||
capabs have been cleared. The "CLEAR" subcommand is used by a client to
|
||||
request that a server clears its current capab list.
|
||||
|
||||
A server receiving the subcommand "CLEAR" must issue back the subcommand
|
||||
"CLEAR" using the current protocol stream. It must then clear the stored
|
||||
list of capabs and revert to receiving/sending a 'normal' protocol stream
|
||||
from/to the client.
|
||||
|
||||
A client receiving the subcommand "CLEAR" must then revert to expecting to
|
||||
receive a 'normal' protocol stream.
|
||||
|
||||
The subcommand "CLEAR" is sent in the form:
|
||||
CAP CLEAR
|
||||
|
||||
CAP: END
|
||||
--------
|
||||
|
||||
The "END" subcommand is used by a client to notify the server it has
|
||||
finished capab negotiation and the server can proceed with registration.
|
||||
|
||||
The subcommand "END" is sent in the form:
|
||||
CAP END
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
CLIENT: indicates what client sends
|
||||
SERVER: indicates what server sends
|
||||
|
||||
This example shows a client which doesnt support this specification:
|
||||
|
||||
CLIENT: NICK FOO
|
||||
CLIENT: USER FOO FOO FOO FOO
|
||||
SERVER: :SERVERNAME 001 ...
|
||||
|
||||
This example shows a client supporting this specification but not
|
||||
wishing to use capabs for the connection:
|
||||
|
||||
CLIENT: CAP END
|
||||
CLIENT: PASS FOO
|
||||
CLIENT: NICK FOO
|
||||
CLIENT: USER FOO FOO FOO FOO
|
||||
SERVER: :SERVERNAME 001 ....
|
||||
|
||||
This example shows a client requesting a list of capabs then successfully
|
||||
requesting the capabs.
|
||||
|
||||
CLIENT: CAP
|
||||
CLIENT: NICK FOO
|
||||
CLIENT: USER FOO FOO FOO FOO
|
||||
SERVER: CAP LS :A B C D E F G H
|
||||
SERVER: CAP LSL :I J
|
||||
CLIENT: CAP REQ :A B C D E F
|
||||
SERVER: CAP ACK :A B C D E F
|
||||
CLIENT: CAP END
|
||||
SERVER: :SERVERNAME 001 ...
|
||||
|
||||
This example shows a client requesting an invalid list of capabs.
|
||||
|
||||
CLIENT: CAP
|
||||
CLIENT: NICK FOO
|
||||
CLIENT: USER FOO FOO FOO FOO
|
||||
SERVER: CAP LSL :A B C D E F G H
|
||||
CLIENT: CAP REQ :A B
|
||||
SERVER: CAP NAK :A B
|
||||
CLIENT: CAP REQ :A
|
||||
SERVER: CAP ACK :A
|
||||
CLIENT: CAP REQ :C
|
||||
SERVER: CAP ACK :C
|
||||
CLIENT: CAP END
|
||||
SERVER: :SERVERNAME 001 ...
|
||||
32
docs/technical/encap.txt
Normal file
32
docs/technical/encap.txt
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
ENCAP DEFINITION
|
||||
----------------
|
||||
|
||||
Preamble
|
||||
--------
|
||||
|
||||
This document defines the specification for the ENCAP command.
|
||||
|
||||
ENCAP is designed to help fix the situation where new commands do
|
||||
not propagate over hub servers running older code.
|
||||
|
||||
Definition
|
||||
----------
|
||||
|
||||
Support for the ENCAP command is given by the CAPAB token "ENCAP".
|
||||
|
||||
The format of ENCAP is:
|
||||
:<source> ENCAP <destination> <subcommand> <parameters>
|
||||
|
||||
<source> - The entity generating the command.
|
||||
|
||||
<destination> - The entity the command is destined for. This may
|
||||
include wildcards for servers, but not clients.
|
||||
|
||||
If the wildcard does not match the current server, the
|
||||
command should be propagated and ignored.
|
||||
|
||||
<subcommand> - The subcommand we're propagating over ENCAP. If the
|
||||
subcommand is not recognised by the current server, the
|
||||
command should be propagated and ignored.
|
||||
|
||||
<parameters> - The parameters that the subcommand have.
|
||||
1171
docs/technical/irc-server-protocol-hybrid.txt
Normal file
1171
docs/technical/irc-server-protocol-hybrid.txt
Normal file
File diff suppressed because it is too large
Load diff
1255
docs/technical/ts6-charybdis.txt
Normal file
1255
docs/technical/ts6-charybdis.txt
Normal file
File diff suppressed because it is too large
Load diff
296
docs/technical/ts6-hybrid.txt
Normal file
296
docs/technical/ts6-hybrid.txt
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
TS6 Proposal (v8)
|
||||
Written by Lee H <lee@leeh.co.uk>
|
||||
Ideas borrowed heavily from ircnet (Beeth, jv, Q)
|
||||
|
||||
- Changes between v7 and v8 -
|
||||
-----------------------------
|
||||
|
||||
In the v7 specification, the JOIN command included the channel modes of a
|
||||
channel, and acted on them following TS rules. In the v8 specification,
|
||||
JOIN will never send modes.
|
||||
|
||||
Desyncs can occur both when they are sent and when they are not. If they
|
||||
are sent, then you can have a situation where a user on one side of the
|
||||
network issues "MODE #channel -l", and a user on another side of the network
|
||||
issues "JOIN #channel" whilst the +l still exists. As the JOIN string sent
|
||||
server<->server includes the full modes at the time of the user joining,
|
||||
this will propagate the +l, but there is a -l crossing in the other
|
||||
direction. Desync will occur beyond where they intersect.
|
||||
|
||||
If the modes are not sent, then a lower TS JOIN command, or a JOIN command
|
||||
that creates a channel will cause a desync.
|
||||
|
||||
It is judged that the desync with sending the modes is worse than the desync
|
||||
by not sending them, as such the v8 specification dictates modes are not
|
||||
sent with a JOIN command server<->server.
|
||||
|
||||
The v8 specification also clarifies that servers may issue TMODE.
|
||||
|
||||
- Introduction -
|
||||
----------------
|
||||
|
||||
This document aims to fix some of the flaws that are still present in the
|
||||
current TS system.
|
||||
|
||||
Whilst only one person may use a nickname at any one time, they are not
|
||||
a reliable method of directing commands between servers. Clients can change
|
||||
their nicknames, which can create desyncs. A reliable method of directing
|
||||
messages between servers is required so that a message will always reach the
|
||||
intended destination, even if the client changes nicks in between.
|
||||
|
||||
UID solves this problem by ensuring that a client has a unique ID for the
|
||||
duration of his connection.
|
||||
|
||||
This document also aims to solve the lack of TS rules to channel 'bans' on
|
||||
a netburst. Bans from both sides of a TS war (losing/winning) are kept.
|
||||
Bursting the bans with a TS solves this problem.
|
||||
|
||||
There is also a race condition in the current TS system, where a user can
|
||||
issue a mode during a netburst and the mode will be set on the server
|
||||
we are bursting to.
|
||||
|
||||
|
||||
- Definitions -
|
||||
---------------
|
||||
|
||||
Throughout this document, the following terms are used:
|
||||
|
||||
SID - A servers unique ID. This is three characters long and must be in
|
||||
the form [0-9][A-Z0-9][A-Z0-9]
|
||||
ID - A clients unique ID. This is six characters long and must be in
|
||||
the form [A-Z][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]. The
|
||||
numbers [0-9] at the beginning of an ID are legal characters, but
|
||||
reserved for future use.
|
||||
UID - An ID concateneted to a SID. This forms the clients UID.
|
||||
TS6 - The TS version 6.
|
||||
|
||||
|
||||
- Support -
|
||||
-----------
|
||||
|
||||
Support for this document is given by the TS version 6.
|
||||
|
||||
Wherever a destination parameter or source parameter is used, it must use
|
||||
the SID or UID if the server/client has one. A TS6 capable server must
|
||||
translate any SIDs/UIDs back into the server/clients name when communicating
|
||||
with a server that does not support TS6.
|
||||
|
||||
A TS6 server must also support the QS (quitstorm) system, and the encap
|
||||
specification found here:
|
||||
http://www.leeh.co.uk/ircd/encap.txt
|
||||
|
||||
The TS6 protocol does not supports masked entities.
|
||||
|
||||
|
||||
- Nick TS rules -
|
||||
-----------------
|
||||
|
||||
A server receiving a command that requires nick TS rules must check for a
|
||||
collision between an existing user, and the nick in the received message.
|
||||
(the "new user"). The collisions must obey the rules specified in Nick TS
|
||||
collisions.
|
||||
|
||||
If the TS received is lower than the TS of the existing user the server will
|
||||
collide the existing user if the clients user@host are different, if the
|
||||
clients user@hosts are identical it will collide the new user.
|
||||
|
||||
If the TS received is equal to the TS of the existing user both clients are
|
||||
collided.
|
||||
|
||||
If the TS received is higher than the TS of the existing user, the server
|
||||
will collide the existing user if the user@hosts are identical, if the
|
||||
clients user@host are different it will collide the new user and drop the
|
||||
message.
|
||||
|
||||
|
||||
- Nick TS collisions -
|
||||
----------------------
|
||||
|
||||
If both users are to be collided, we must issue a KILL for the existing
|
||||
user to all servers. If the new user has a UID then we must also issue a
|
||||
KILL for that UID back to the server sending us data causing the collision.
|
||||
|
||||
If only the existing user is being collided, we must issue a KILL for the
|
||||
existing user to all servers except the server sending us data. If the
|
||||
existing user has a UID and the server sending us data supports TS6 then
|
||||
we must also issue a KILL for the existing users UID to the server sending
|
||||
us data.
|
||||
|
||||
If only the new user is being collided, we must issue a KILL for the new user
|
||||
back to the server sending us data if the new user has a UID.
|
||||
|
||||
|
||||
- Channel TS rules -
|
||||
--------------------
|
||||
|
||||
A server receiving a command that requires normal channel TS rules must
|
||||
apply the following rules to the command.
|
||||
|
||||
If the TS received is lower than our TS of the channel a TS6 server must
|
||||
remove status modes (+ov etc) and channel modes (+nt etc). If the
|
||||
originating server is TS6 capable (ie, it has a SID), the server must
|
||||
also remove any ban modes (+b etc). The new modes and statuses are then
|
||||
accepted.
|
||||
|
||||
If any bans are removed, the server must send to non-TS6, directly connected
|
||||
servers mode changes removing the bans after the command is propagated.
|
||||
This prevents desync with banlists, and has to be sent after as clients are
|
||||
still able to send mode changes before the triggering command arrives.
|
||||
|
||||
If the TS received is equal to our TS of the channel the server should keep
|
||||
its current modes and accept the received modes and statuses.
|
||||
|
||||
If the TS received is higher than our TS of the channel the server should keep
|
||||
its current modes and ignore the received modes and statuses. Any statuses
|
||||
given in the received message will be removed. A server must mark clients
|
||||
losing their op (+o) status who do not have a UID as 'deopped'. A server must
|
||||
ignore any "MODE" commands from a user marked as 'deopped'.
|
||||
|
||||
|
||||
- Simple channel TS rules -
|
||||
---------------------------
|
||||
|
||||
A server receiving a command that requires simple channel TS rules must
|
||||
apply the following rules to the command.
|
||||
|
||||
If the TS received is lower, or equal to our TS of the channel the modes are
|
||||
accepted. If the TS received is higher than our TS of the channel the modes
|
||||
are ignored and dropped.
|
||||
|
||||
Simple channel TS rules do not affect current modes in the channel except
|
||||
for the modes we are accepting.
|
||||
|
||||
|
||||
- The following commands are defined here as the TS6 protocol -
|
||||
---------------------------------------------------------------
|
||||
|
||||
- PASS -
|
||||
PASS <PASSWORD> TS <TS_CURRENT> :<SID>
|
||||
|
||||
This command is used for password verification with the server we are
|
||||
connecting to.
|
||||
|
||||
Due to the burst being sent on verification of the "SERVER" command, and
|
||||
"SVINFO" being sent after "SERVER", we need to be aware of the TS version
|
||||
earlier to decide whether to send a TS6 burst or not.
|
||||
|
||||
The <PASSWORD> field is the password we have stored for this server,
|
||||
<TS_CURRENT> is our current TS version. If this field is not present then
|
||||
the server does not support TS6. <SID> is the SID of the server.
|
||||
|
||||
- UID -
|
||||
:<SID> UID <NICK> <HOPS> <TS> +<UMODE> <USERNAME> <HOSTNAME> <IP> <UID> :<GECOS>
|
||||
|
||||
This command is used for introducing clients to the network.
|
||||
|
||||
The <SID> field is the SID of the server the client is connected to.
|
||||
The <NICK> field is the nick of the client being introduced. The <HOPS>
|
||||
field is the amount of server hops between the server being burst to and
|
||||
the server the client is on. The <TS> field is the TS of the client, either
|
||||
the time they connected or the time they last changed nick. The <UMODE>
|
||||
field contains the clients usermodes that need to be transmitted between
|
||||
servers. The <USERNAME> field contains the clients username/ident. The
|
||||
<HOSTNAME> field contains the clients host.
|
||||
|
||||
The <IP> field contains the clients IP. If the IP is not to be sent
|
||||
(due to a spoof etc), the field must be sent as "0". The <UID> field is the
|
||||
clients UID. The <GECOS> field is the clients gecos.
|
||||
|
||||
A server receiving a UID command must apply nick TS rules to the nick.
|
||||
|
||||
- SID -
|
||||
:<SID> SID <SERVERNAME> <HOPS> <SID> :<GECOS>
|
||||
|
||||
This command is used for introducing servers to the network.
|
||||
|
||||
The first <SID> field is the SID of the new servers uplink. The
|
||||
<SERVERNAME> field is the new servers name. The <HOPS> field is the hops
|
||||
between the server being introduced nd the server being burst to.
|
||||
|
||||
The second <SID> field is the SID of the new server. The <GECOS> field i
|
||||
is the new servers gecos.
|
||||
|
||||
Upon receiving the SID command servers must check for a SID collision.
|
||||
Two servers must not be allowed to link to the network with the same SID.
|
||||
If a server detects a SID collision it must drop the link to the directly
|
||||
connected server through which the command was received.
|
||||
|
||||
Client and servers which do not have a UID/SID must be introduced by old
|
||||
methods.
|
||||
|
||||
- SJOIN -
|
||||
:<SID> SJOIN <TS> <CHANNAME> +<CHANMODES> :<UIDS>
|
||||
|
||||
This command is used for introducing users to channels.
|
||||
|
||||
The <SID> field is the SID of the server introducing users to the channel.
|
||||
The <TS> field is the channels current TS, <CHANNAME> is the channels
|
||||
current name, <CHANMODES> are the channels current modes. <UIDS> is a
|
||||
space delimited list of clients UIDs to join to the channel. Each clients
|
||||
UID is prefixed with their status on the channel, ie "@UID" for an opped
|
||||
user. Multiple prefixes are allowed, "peons" (clients without a status) are
|
||||
not prefixed.
|
||||
|
||||
A server receiving an SJOIN must apply normal channel TS rules to the SJOIN.
|
||||
|
||||
A TS6 server must not use the SJOIN command outside of a netburst
|
||||
to introduce a single user to an existing channel. It must instead
|
||||
use the "JOIN" command defined in this specification. A TS6 server must
|
||||
still use SJOIN for creating channels.
|
||||
|
||||
- JOIN -
|
||||
:<UID> JOIN <TS> <CHANNAME> +
|
||||
|
||||
This command is used for introducing one user unopped to an existing channel.
|
||||
|
||||
The <UID> field is the UID of the client joining the channel. The
|
||||
<TS> field is the channels current TS, <CHANNAME> is the channels
|
||||
current name.
|
||||
|
||||
A server receiving a JOIN must apply normal channel TS rules to the JOIN.
|
||||
|
||||
No channel modes are sent with the JOIN command. In previous versions of
|
||||
this specification, the "+" parameter contained the channels current modes.
|
||||
A server following this version of the specification must not interpret this
|
||||
argument and must not propagate any value other than "+" for this parameter.
|
||||
|
||||
It should be noted that whilst JOIN would not normally create a
|
||||
channel or lower the timestamp, during specific conditions it can. This
|
||||
can create a desync that this specification does not rectify.
|
||||
|
||||
- BMASK -
|
||||
:<SID> BMASK <TS> <CHANNAME> <TYPE> :<MASKS>
|
||||
|
||||
This command is used for bursting channel bans to a network.
|
||||
|
||||
The <SID> field is the SID of the server bursting the bans. The
|
||||
<TS> field is the channels current TS, <CHANNAME> is the channels
|
||||
name. <TYPE> is a single character identifying the mode type (ie,
|
||||
for a ban 'b'). <MASKS> is a space delimited list of masks of the
|
||||
given mode,limited only in length to the size of the buffer as defined
|
||||
by RFC1459.
|
||||
|
||||
A server receiving a BMASK must apply simple channel TS rules to the BMASK.
|
||||
|
||||
A TS6 server must translate BMASKs into raw modes for non-TS6
|
||||
capable servers. This command must be used only after SJOIN has
|
||||
been sent for the given channel.
|
||||
|
||||
It should be noted however, that a BMASK with a lower TS should
|
||||
not be possible without a desync, due to it being sent after
|
||||
SJOIN.
|
||||
|
||||
- TMODE -
|
||||
:<SID|UID> TMODE <TS> <CHANNAME> <MODESTRING>
|
||||
|
||||
This command is used for clients issuing modes on a channel.
|
||||
|
||||
<SID|UID> is either the UID of the client setting the mode, or the SID of
|
||||
the server setting the mode. <TS> is the current TS of the channel,
|
||||
<CHANNAME> is the channels name. <MODESTRING> is the raw mode the client is
|
||||
setting.
|
||||
|
||||
A server receiving a TMODE must apply simple channel TS rules to the TMODE.
|
||||
|
||||
A TS6 server must translate MODEs issued by a local client, or received from
|
||||
a server into TMODE to send to other TS6 capable servers.
|
||||
|
|
@ -3,3 +3,5 @@ port = 6667
|
|||
server_hostname = "irc.foo.bar"
|
||||
network_name = "MyCoolFooNet" # this SHOULDN'T HAVE SPACES!
|
||||
operators = []
|
||||
server_incoming_passwords = ["unimpl"]
|
||||
server_outgoing_password = "root"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ impl IrcHandler for Cap {
|
|||
_arguments: Vec<String>,
|
||||
_authenticated: bool,
|
||||
_user_state: &mut User,
|
||||
) -> super::IrcAction {
|
||||
IrcAction::DoNothing
|
||||
_server_outgoing_password: String,
|
||||
_server_incoming_passwords: Vec<String>,
|
||||
_user_passwords: Vec<String>,
|
||||
) -> Vec<super::IrcAction> {
|
||||
vec![IrcAction::DoNothing]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ impl IrcHandler for Join {
|
|||
arguments: Vec<String>,
|
||||
authenticated: bool,
|
||||
user_state: &mut User,
|
||||
) -> super::IrcAction {
|
||||
_server_outgoing_password: String,
|
||||
_server_incoming_passwords: Vec<String>,
|
||||
_user_passwords: Vec<String>,
|
||||
) -> Vec<super::IrcAction> {
|
||||
let mut joined_channels = JOINED_CHANNELS.lock().await;
|
||||
let mut channels = Vec::new();
|
||||
|
||||
|
|
@ -28,7 +31,7 @@ impl IrcHandler for Join {
|
|||
}
|
||||
|
||||
if !authenticated {
|
||||
return IrcAction::ErrorAuthenticateFirst;
|
||||
return vec![IrcAction::ErrorAuthenticateFirst];
|
||||
}
|
||||
|
||||
for existing_channel in joined_channels.clone() {
|
||||
|
|
@ -53,6 +56,6 @@ impl IrcHandler for Join {
|
|||
}
|
||||
}
|
||||
|
||||
IrcAction::JoinChannels(channels)
|
||||
vec![IrcAction::JoinChannels(channels)]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ use crate::{
|
|||
SENDER,
|
||||
channels::Channel,
|
||||
commands::{
|
||||
cap::Cap, join::Join, nick::Nick, ping::Ping, privmsg::PrivMsg, user::User as UserHandler,
|
||||
who::Who,
|
||||
cap::Cap, join::Join, nick::Nick, pass::Pass, ping::Ping, privmsg::PrivMsg,
|
||||
user::User as UserHandler, who::Who,
|
||||
},
|
||||
config::ServerInfo,
|
||||
error_structs::CommandExecError,
|
||||
messages::{JoinMessage, Message},
|
||||
messages::{ChanJoinMessage, Message},
|
||||
sender::IrcResponse,
|
||||
user::User,
|
||||
};
|
||||
|
|
@ -20,6 +21,7 @@ use crate::{
|
|||
mod cap;
|
||||
mod join;
|
||||
mod nick;
|
||||
mod pass;
|
||||
mod ping;
|
||||
mod privmsg;
|
||||
mod user;
|
||||
|
|
@ -40,10 +42,17 @@ pub enum IrcAction {
|
|||
SendText(IrcResponse),
|
||||
SendMessage(Message),
|
||||
JoinChannels(Vec<Channel>),
|
||||
UpgradeToServerConn,
|
||||
ErrorAuthenticateFirst,
|
||||
DoNothing,
|
||||
}
|
||||
|
||||
pub enum ReturnAction {
|
||||
Nothing,
|
||||
ServerConn,
|
||||
CloseConn,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait IrcHandler: Send + Sync {
|
||||
async fn handle(
|
||||
|
|
@ -51,17 +60,25 @@ pub trait IrcHandler: Send + Sync {
|
|||
command: Vec<String>,
|
||||
authenticated: bool,
|
||||
user_state: &mut User,
|
||||
) -> IrcAction;
|
||||
server_outgoing_password: String,
|
||||
server_incoming_passwords: Vec<String>,
|
||||
user_passwords: Vec<String>,
|
||||
) -> Vec<IrcAction>;
|
||||
}
|
||||
|
||||
pub struct SendMessage(Option<String>);
|
||||
|
||||
impl IrcCommand {
|
||||
pub fn new(command_with_arguments: String) -> Self {
|
||||
let split_command: Vec<&str> = command_with_arguments
|
||||
pub async fn new(command_with_arguments: String) -> Self {
|
||||
let mut split_command: Vec<&str> = command_with_arguments
|
||||
.split_whitespace()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
if split_command[0].starts_with(":") {
|
||||
split_command.remove(0);
|
||||
}
|
||||
|
||||
let command = split_command[0].to_owned();
|
||||
let mut arguments = Vec::new();
|
||||
let mut buffer: Option<String> = None;
|
||||
|
|
@ -94,7 +111,8 @@ impl IrcCommand {
|
|||
writer: &mut BufWriter<TcpStream>,
|
||||
hostname: &str,
|
||||
user_state: &mut User,
|
||||
) -> Result<(), CommandExecError> {
|
||||
config: &ServerInfo,
|
||||
) -> Result<Vec<ReturnAction>, CommandExecError> {
|
||||
let mut command_map: HashMap<String, &dyn IrcHandler> = HashMap::new();
|
||||
let broadcast_sender = SENDER.lock().await.clone().unwrap();
|
||||
|
||||
|
|
@ -106,6 +124,7 @@ impl IrcCommand {
|
|||
command_map.insert("PING".to_owned(), &Ping);
|
||||
command_map.insert("JOIN".to_owned(), &Join);
|
||||
command_map.insert("WHO".to_owned(), &Who);
|
||||
command_map.insert("PASS".to_owned(), &Pass);
|
||||
|
||||
println!("{self:#?}");
|
||||
|
||||
|
|
@ -114,18 +133,28 @@ impl IrcCommand {
|
|||
.map(|v| *v)
|
||||
.ok_or(CommandExecError::NonexistantCommand)?;
|
||||
|
||||
let action = command_to_execute
|
||||
let actions = command_to_execute
|
||||
.handle(
|
||||
self.arguments.clone(),
|
||||
user_state.is_populated(),
|
||||
user_state,
|
||||
config.server_outgoing_password.clone(),
|
||||
config.server_incoming_passwords.clone(),
|
||||
vec![], // TODO
|
||||
)
|
||||
.await;
|
||||
action
|
||||
.execute(writer, hostname, &user_state, broadcast_sender)
|
||||
|
||||
let mut return_actions = Vec::new();
|
||||
|
||||
for action in actions {
|
||||
let return_action = action
|
||||
.execute(writer, hostname, &user_state, broadcast_sender.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
return_actions.push(return_action);
|
||||
}
|
||||
|
||||
Ok(return_actions)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +165,7 @@ impl IrcAction {
|
|||
hostname: &str,
|
||||
user_state: &User,
|
||||
sender: Sender<Message>,
|
||||
) {
|
||||
) -> ReturnAction {
|
||||
match self {
|
||||
IrcAction::SendText(msg) => {
|
||||
msg.send(hostname, writer, false).await.unwrap();
|
||||
|
|
@ -144,11 +173,11 @@ impl IrcAction {
|
|||
|
||||
IrcAction::JoinChannels(channels) => {
|
||||
for channel in channels {
|
||||
let join_message = JoinMessage {
|
||||
let join_message = ChanJoinMessage {
|
||||
sender: user_state.clone().unwrap_all(),
|
||||
channel: channel.clone(),
|
||||
};
|
||||
sender.send(Message::JoinMessage(join_message)).unwrap();
|
||||
sender.send(Message::ChanJoinMessage(join_message)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,7 +185,13 @@ impl IrcAction {
|
|||
sender.send(msg.clone()).unwrap();
|
||||
}
|
||||
|
||||
IrcAction::UpgradeToServerConn => {
|
||||
return ReturnAction::ServerConn;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
return ReturnAction::Nothing;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,26 @@ impl IrcHandler for Nick {
|
|||
command: Vec<String>,
|
||||
_authenticated: bool,
|
||||
user_state: &mut User,
|
||||
) -> IrcAction {
|
||||
user_state.nickname = Some(command[0].clone());
|
||||
_server_outgoing_password: String,
|
||||
_server_incoming_passwords: Vec<String>,
|
||||
_user_passwords: Vec<String>,
|
||||
) -> Vec<IrcAction> {
|
||||
user_state.nickname = Some({
|
||||
if command[0].len() > 9 {
|
||||
String::from_utf8(
|
||||
command[0]
|
||||
.clone()
|
||||
.chars()
|
||||
.map(|x| x.clone() as u8)
|
||||
.collect::<Vec<u8>>()[0..8]
|
||||
.to_vec(),
|
||||
)
|
||||
.unwrap()
|
||||
} else {
|
||||
command[0].clone()
|
||||
}
|
||||
});
|
||||
|
||||
IrcAction::DoNothing
|
||||
vec![IrcAction::DoNothing]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
36
src/commands/pass.rs
Normal file
36
src/commands/pass.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
commands::{IrcAction, IrcHandler},
|
||||
user::User,
|
||||
};
|
||||
|
||||
pub struct Pass;
|
||||
|
||||
#[async_trait]
|
||||
impl IrcHandler for Pass {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
_authenticated: bool,
|
||||
_user_state: &mut User,
|
||||
server_outgoing_password: String,
|
||||
server_incoming_passwords: Vec<String>,
|
||||
_user_passwords: Vec<String>,
|
||||
) -> Vec<IrcAction> {
|
||||
if server_incoming_passwords.contains(&command[0]) {
|
||||
vec![
|
||||
IrcAction::SendText(crate::sender::IrcResponse {
|
||||
sender: None,
|
||||
command: "PASS".to_owned(),
|
||||
receiver: None,
|
||||
arguments: Vec::new(),
|
||||
message: server_outgoing_password.clone(),
|
||||
}),
|
||||
IrcAction::UpgradeToServerConn,
|
||||
]
|
||||
} else {
|
||||
vec![IrcAction::DoNothing]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,17 +15,20 @@ impl IrcHandler for Ping {
|
|||
command: Vec<String>,
|
||||
authenticated: bool,
|
||||
user_state: &mut User,
|
||||
) -> IrcAction {
|
||||
_server_outgoing_password: String,
|
||||
_server_incoming_passwords: Vec<String>,
|
||||
_user_passwords: Vec<String>,
|
||||
) -> Vec<IrcAction> {
|
||||
if authenticated {
|
||||
IrcAction::SendText(IrcResponse {
|
||||
vec![IrcAction::SendText(IrcResponse {
|
||||
sender: None,
|
||||
command: "PONG".into(),
|
||||
arguments: Vec::new(),
|
||||
receiver: Some(user_state.username.clone().unwrap()),
|
||||
message: format!(":{}", command[0].clone()),
|
||||
})
|
||||
})]
|
||||
} else {
|
||||
IrcAction::DoNothing
|
||||
vec![IrcAction::DoNothing]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
CONNECTED_USERS,
|
||||
commands::{IrcAction, IrcHandler},
|
||||
messages::{Message, PrivMessage},
|
||||
messages::{Message, PrivMessage, Receiver},
|
||||
user::User,
|
||||
};
|
||||
|
||||
|
|
@ -16,21 +15,26 @@ impl IrcHandler for PrivMsg {
|
|||
command: Vec<String>,
|
||||
authenticated: bool,
|
||||
user_state: &mut User,
|
||||
) -> IrcAction {
|
||||
_server_outgoing_password: String,
|
||||
_server_incoming_passwords: Vec<String>,
|
||||
_user_passwords: Vec<String>,
|
||||
) -> Vec<IrcAction> {
|
||||
if !authenticated {
|
||||
return IrcAction::ErrorAuthenticateFirst;
|
||||
return vec![IrcAction::ErrorAuthenticateFirst];
|
||||
}
|
||||
let connected_users = CONNECTED_USERS.lock().await;
|
||||
|
||||
println!("{connected_users:#?}");
|
||||
drop(connected_users);
|
||||
let receiver = if command[0].clone().starts_with("#") {
|
||||
Receiver::ChannelName(command[0].clone())
|
||||
} else {
|
||||
Receiver::Username(command[0].clone())
|
||||
};
|
||||
|
||||
let message = PrivMessage {
|
||||
sender: user_state.clone().unwrap_all(),
|
||||
receiver: command[0].clone(),
|
||||
receiver,
|
||||
text: command[1].clone(),
|
||||
};
|
||||
|
||||
IrcAction::SendMessage(Message::PrivMessage(message))
|
||||
vec![IrcAction::SendMessage(Message::PrivMessage(message))]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,32 @@ impl IrcHandler for User {
|
|||
command: Vec<String>,
|
||||
_authenticated: bool,
|
||||
user_state: &mut UserState,
|
||||
) -> IrcAction {
|
||||
_server_outgoing_password: String,
|
||||
_server_incoming_passwords: Vec<String>,
|
||||
_user_passwords: Vec<String>,
|
||||
) -> Vec<IrcAction> {
|
||||
if command.len() < 4 {
|
||||
return IrcAction::DoNothing; // XXX: return an error
|
||||
return vec![IrcAction::DoNothing]; // XXX: return an error
|
||||
}
|
||||
user_state.username = Some(command[0].clone());
|
||||
|
||||
// oh my god this is a mess
|
||||
user_state.username = Some({
|
||||
if command[0].len() > 9 {
|
||||
String::from_utf8(
|
||||
command[0]
|
||||
.clone()
|
||||
.chars()
|
||||
.map(|x| x.clone() as u8)
|
||||
.collect::<Vec<u8>>()[0..8]
|
||||
.to_vec(),
|
||||
)
|
||||
.unwrap()
|
||||
} else {
|
||||
command[0].clone()
|
||||
}
|
||||
});
|
||||
user_state.realname = Some(command[3].clone());
|
||||
|
||||
IrcAction::DoNothing
|
||||
vec![IrcAction::DoNothing]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ impl IrcHandler for Who {
|
|||
_arguments: Vec<String>,
|
||||
_authenticated: bool,
|
||||
_user_state: &mut User,
|
||||
) -> super::IrcAction {
|
||||
IrcAction::DoNothing // TODO
|
||||
_server_outgoing_password: String,
|
||||
_server_incoming_passwords: Vec<String>,
|
||||
_user_passwords: Vec<String>,
|
||||
) -> Vec<super::IrcAction> {
|
||||
vec![IrcAction::DoNothing] // TODO
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ pub struct ServerInfo {
|
|||
pub server_hostname: String,
|
||||
pub network_name: String,
|
||||
pub operators: Vec<String>,
|
||||
pub server_incoming_passwords: Vec<String>,
|
||||
pub server_outgoing_password: String,
|
||||
}
|
||||
|
||||
fn get_config_path() -> Result<PathBuf, ConfigReadError> {
|
||||
|
|
|
|||
185
src/main.rs
185
src/main.rs
|
|
@ -1,8 +1,9 @@
|
|||
use std::{
|
||||
clone,
|
||||
collections::HashSet,
|
||||
net::{SocketAddr, TcpListener, TcpStream},
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use anyhow::Error as AnyhowError;
|
||||
|
|
@ -25,8 +26,13 @@ use crate::{
|
|||
config::ServerInfo,
|
||||
error_structs::{HandlerError, ListenerError},
|
||||
login::send_motd,
|
||||
messages::Message,
|
||||
messages::Receiver as MsgReceiver,
|
||||
messages::{Message, NetJoinMessage},
|
||||
sender::{IrcResponse, IrcResponseCodes},
|
||||
ts6::{
|
||||
Ts6,
|
||||
structs::{ServerId, UserId},
|
||||
},
|
||||
user::{User, UserUnwrapped},
|
||||
};
|
||||
|
||||
|
|
@ -37,10 +43,15 @@ mod error_structs;
|
|||
mod login;
|
||||
mod messages;
|
||||
mod sender;
|
||||
mod ts6;
|
||||
mod user;
|
||||
mod userid_gen;
|
||||
mod usermodes;
|
||||
|
||||
pub static CONNECTED_USERS: Lazy<Mutex<HashSet<UserUnwrapped>>> =
|
||||
Lazy::new(|| Mutex::new(HashSet::new()));
|
||||
pub static FOREIGN_CONNECTED_USERS: Lazy<Mutex<HashSet<UserUnwrapped>>> =
|
||||
Lazy::new(|| Mutex::new(HashSet::new()));
|
||||
pub static JOINED_CHANNELS: Lazy<Mutex<HashSet<Channel>>> =
|
||||
Lazy::new(|| Mutex::new(HashSet::new()));
|
||||
pub static SENDER: Lazy<Mutex<Option<Sender<Message>>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
|
@ -53,6 +64,11 @@ struct Args {
|
|||
pub config_path: Option<String>,
|
||||
}
|
||||
|
||||
enum TcpListenerResult {
|
||||
UpdatedUser(User),
|
||||
ServerConnectionInit,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), AnyhowError> {
|
||||
#[cfg(feature = "tokio-console")]
|
||||
|
|
@ -92,20 +108,33 @@ async fn handle_connection(
|
|||
let mut message_receiver = tx.clone().subscribe();
|
||||
let mut tcp_reader = TokioBufReader::new(TokioTcpStream::from_std(stream.try_clone()?)?);
|
||||
let mut tcp_writer = TokioBufWriter::new(TokioTcpStream::from_std(stream)?);
|
||||
|
||||
'connection_handler: {
|
||||
let mut state = User::default();
|
||||
|
||||
let hostname = info.server_hostname.clone();
|
||||
|
||||
// TODO: generate randomally and allow overriding from config
|
||||
let my_server_id = ServerId::try_from("000".to_owned()).unwrap();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = tcp_listener(&stream_tcp, state.clone(), &info, &mut tcp_reader) => {
|
||||
result = tcp_listener(&stream_tcp, state.clone(), &info, &mut tcp_reader, my_server_id.clone()) => {
|
||||
match result {
|
||||
Ok(modified_user) => {
|
||||
state = modified_user;
|
||||
Ok(tcp_listener_result) => {
|
||||
match tcp_listener_result {
|
||||
TcpListenerResult::UpdatedUser(user) => {
|
||||
state = user;
|
||||
}
|
||||
|
||||
TcpListenerResult::ServerConnectionInit => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(_) => {
|
||||
break;
|
||||
break 'connection_handler;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -115,7 +144,7 @@ async fn handle_connection(
|
|||
Err(err) => {
|
||||
match err {
|
||||
ListenerError::ConnectionError => {
|
||||
break;
|
||||
break 'connection_handler;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
|
|
@ -126,6 +155,35 @@ async fn handle_connection(
|
|||
}
|
||||
}
|
||||
|
||||
println!("upgrade to server connection");
|
||||
|
||||
let mut ts6_server_status = Ts6::default();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = ts6_server_status.tcp_listener(&stream_tcp, &info, &mut tcp_reader, &my_server_id) => {
|
||||
match result {
|
||||
Ok(new_status) => {
|
||||
println!("{new_status:#?}");
|
||||
ts6_server_status = new_status;
|
||||
},
|
||||
Err(_) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
result = ts6_server_status.message_listener(&mut message_receiver, &mut tcp_writer, &my_server_id, &hostname) => {
|
||||
match result {
|
||||
Ok(_) => {},
|
||||
Err(_) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream_tcp.shutdown(std::net::Shutdown::Both)?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -133,34 +191,45 @@ async fn handle_connection(
|
|||
|
||||
async fn tcp_listener(
|
||||
stream: &TcpStream,
|
||||
mut state: User,
|
||||
mut user_state: User,
|
||||
info: &ServerInfo,
|
||||
reader: &mut TokioBufReader<TokioTcpStream>,
|
||||
) -> Result<User, ListenerError> {
|
||||
our_sid: ServerId,
|
||||
) -> Result<TcpListenerResult, ListenerError> {
|
||||
let mut buffer = String::new();
|
||||
|
||||
let mut writer = TokioBufWriter::new(TokioTcpStream::from_std(stream.try_clone()?)?);
|
||||
|
||||
buffer.clear();
|
||||
match reader.read_line(&mut buffer).await {
|
||||
Ok(0) => return Err(ListenerError::ConnectionError),
|
||||
Ok(_) => {}
|
||||
|
||||
Err(_) => {
|
||||
let mut conneted_users = CONNECTED_USERS.lock().await;
|
||||
let _ = conneted_users.remove(&state.clone().unwrap_all());
|
||||
let _ = conneted_users.remove(&user_state.clone().unwrap_all());
|
||||
|
||||
return Err(ListenerError::ConnectionError);
|
||||
}
|
||||
}
|
||||
|
||||
let command = commands::IrcCommand::new(buffer.clone());
|
||||
let command = commands::IrcCommand::new(buffer.clone()).await;
|
||||
match command
|
||||
.execute(&mut writer, &info.server_hostname, &mut state)
|
||||
.execute(&mut writer, &info.server_hostname, &mut user_state, info)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
Ok(return_actions) => {
|
||||
for return_action in return_actions {
|
||||
match return_action {
|
||||
commands::ReturnAction::ServerConn => {
|
||||
return Ok(TcpListenerResult::ServerConnectionInit);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => match error {
|
||||
error_structs::CommandExecError::NonexistantCommand => {
|
||||
let error_string = format!("error processing your command: {error:#?}\n");
|
||||
let error = IrcResponseCodes::UnknownCommand;
|
||||
|
||||
|
|
@ -170,19 +239,41 @@ async fn tcp_listener(
|
|||
.await
|
||||
.unwrap();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if !state.identified && state.is_populated() {
|
||||
send_motd(info.clone(), state.clone(), &mut writer).await?;
|
||||
if !user_state.identified && user_state.is_populated_without_uid() {
|
||||
let id = userid_gen::increase_user_id()
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
let user_id = format!("{our_sid}{id}");
|
||||
|
||||
user_state.identified = true;
|
||||
user_state.user_id = Some(UserId::try_from(user_id).unwrap()); // XXX: error handling
|
||||
user_state.timestamp = Some(SystemTime::now());
|
||||
|
||||
send_motd(info.clone(), user_state.clone(), &mut writer).await?;
|
||||
|
||||
let broadcast_sender = SENDER.lock().await.clone().unwrap();
|
||||
|
||||
broadcast_sender
|
||||
.send(Message::NetJoinMessage(NetJoinMessage {
|
||||
user: user_state.clone().unwrap_all(),
|
||||
server_id: our_sid.clone(),
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
state.identified = true;
|
||||
CONNECTED_USERS
|
||||
.lock()
|
||||
.await
|
||||
.insert(state.clone().unwrap_all());
|
||||
.insert(user_state.clone().unwrap_all());
|
||||
}
|
||||
|
||||
Ok(state)
|
||||
Ok(TcpListenerResult::UpdatedUser(user_state))
|
||||
}
|
||||
|
||||
async fn message_listener(
|
||||
|
|
@ -208,12 +299,32 @@ async fn message_listener(
|
|||
match message {
|
||||
Message::PrivMessage(message) => {
|
||||
for channel in joined_channels.clone() {
|
||||
if channel.joined_users.contains(user_wrapped) && channel.name == message.receiver {
|
||||
if let MsgReceiver::ChannelName(channelname) = message.clone().receiver
|
||||
&& channelname == channel.name
|
||||
&& channel.joined_users.contains(user_wrapped)
|
||||
{
|
||||
channel_name = Some(channel.name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if user.nickname.clone().to_ascii_lowercase() == message.receiver.to_ascii_lowercase() {
|
||||
dbg!(&message);
|
||||
|
||||
if match message.clone().receiver {
|
||||
MsgReceiver::UserId(userid) => {
|
||||
println!("{userid} ?= {}", user.user_id);
|
||||
if userid == user.user_id { true } else { false }
|
||||
}
|
||||
|
||||
MsgReceiver::Username(username) => {
|
||||
if username.to_lowercase() == user.username.to_lowercase() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
_ => false,
|
||||
} {
|
||||
IrcResponse {
|
||||
sender: Some(message.sender.hostmask()),
|
||||
command: "PRIVMSG".into(),
|
||||
|
|
@ -238,7 +349,7 @@ async fn message_listener(
|
|||
}
|
||||
}
|
||||
|
||||
Message::JoinMessage(message) => {
|
||||
Message::ChanJoinMessage(message) => {
|
||||
if message.channel.joined_users.contains(user_wrapped) || message.sender == user {
|
||||
let channel = message.channel.clone();
|
||||
|
||||
|
|
@ -263,7 +374,35 @@ async fn message_listener(
|
|||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Message::NetJoinMessage(_) => {} // we don't care about these here :)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::userid_gen;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_user_id_generator() {
|
||||
while let Ok(userid) = userid_gen::increase_user_id().await {
|
||||
if userid == ['A', 'B', 'C', 'D', 'E', 'F'] {
|
||||
userid_gen::manually_set_user_id(['Z', 'Z', 'Z', 'Z', 'Z', 'Y'].to_vec()).await;
|
||||
break;
|
||||
}
|
||||
|
||||
dbg!(userid);
|
||||
}
|
||||
|
||||
while let Ok(userid) = userid_gen::increase_user_id().await {
|
||||
if userid == ['A', '1', '2', '3', '4', '5'] {
|
||||
// ff a bit
|
||||
userid_gen::manually_set_user_id(['Z', '1', '2', '3', '4', '5'].to_vec()).await;
|
||||
}
|
||||
|
||||
dbg!(userid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,42 @@
|
|||
use crate::{channels::Channel, user::UserUnwrapped};
|
||||
use crate::{
|
||||
channels::Channel,
|
||||
ts6::structs::{ServerId, UserId},
|
||||
user::UserUnwrapped,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
PrivMessage(PrivMessage),
|
||||
JoinMessage(JoinMessage),
|
||||
ChanJoinMessage(ChanJoinMessage),
|
||||
NetJoinMessage(NetJoinMessage),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JoinMessage {
|
||||
pub struct ChanJoinMessage {
|
||||
pub sender: UserUnwrapped,
|
||||
pub channel: Channel,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NetJoinMessage {
|
||||
pub user: UserUnwrapped,
|
||||
pub server_id: ServerId,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PrivMessage {
|
||||
pub sender: UserUnwrapped,
|
||||
pub receiver: String,
|
||||
pub receiver: Receiver,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Receiver {
|
||||
Username(String),
|
||||
UserId(UserId),
|
||||
ChannelName(String),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use tokio::{
|
|||
|
||||
use crate::error_structs::SenderError;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IrcResponse {
|
||||
pub sender: Option<String>,
|
||||
pub command: String,
|
||||
|
|
@ -15,16 +15,17 @@ pub struct IrcResponse {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u16)]
|
||||
pub enum IrcResponseCodes {
|
||||
UnknownCommand,
|
||||
Welcome,
|
||||
YourHost,
|
||||
MyInfo,
|
||||
ISupport,
|
||||
NoMotd,
|
||||
NoTopic,
|
||||
NameReply,
|
||||
EndOfNames,
|
||||
UnknownCommand = 421,
|
||||
Welcome = 001,
|
||||
YourHost = 002,
|
||||
MyInfo = 004,
|
||||
ISupport = 005,
|
||||
NoMotd = 422,
|
||||
NoTopic = 331,
|
||||
NameReply = 353,
|
||||
EndOfNames = 366,
|
||||
}
|
||||
|
||||
impl IrcResponse {
|
||||
|
|
@ -38,8 +39,8 @@ impl IrcResponse {
|
|||
let mut full_response = Vec::new();
|
||||
|
||||
full_response.push(sender);
|
||||
full_response.extend_from_slice(&self.arguments);
|
||||
full_response.push(self.command.clone());
|
||||
full_response.extend_from_slice(&self.arguments);
|
||||
if let Some(receiver) = self.receiver.clone() {
|
||||
full_response.push(receiver);
|
||||
}
|
||||
|
|
@ -52,29 +53,17 @@ impl IrcResponse {
|
|||
writer.write_all(full_response.join(" ").as_bytes()).await?;
|
||||
writer.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
println!("sending: {full_response:#?}");
|
||||
|
||||
impl From<IrcResponseCodes> for &str {
|
||||
fn from(value: IrcResponseCodes) -> Self {
|
||||
match value {
|
||||
IrcResponseCodes::UnknownCommand => "421",
|
||||
IrcResponseCodes::Welcome => "001",
|
||||
IrcResponseCodes::YourHost => "002",
|
||||
IrcResponseCodes::MyInfo => "004",
|
||||
IrcResponseCodes::ISupport => "005",
|
||||
IrcResponseCodes::NoMotd => "422",
|
||||
IrcResponseCodes::NoTopic => "331",
|
||||
IrcResponseCodes::NameReply => "353",
|
||||
IrcResponseCodes::EndOfNames => "366",
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IrcResponseCodes> for String {
|
||||
fn from(value: IrcResponseCodes) -> Self {
|
||||
Into::<&str>::into(value).to_string()
|
||||
let value = value as u16;
|
||||
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
38
src/ts6/commands/capab.rs
Normal file
38
src/ts6/commands/capab.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use crate::ts6::{
|
||||
ServerId, Ts6,
|
||||
commands::{CommandSender, Ts6Action, Ts6Handler},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub struct Capab;
|
||||
|
||||
// TODO: handle capabilities
|
||||
|
||||
#[async_trait]
|
||||
impl Ts6Handler for Capab {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
_server_status: Ts6,
|
||||
_my_sid: ServerId,
|
||||
sender: Option<CommandSender>,
|
||||
_hostname: &str,
|
||||
) -> Vec<Ts6Action> {
|
||||
let args = {
|
||||
let args_without_command = command[1..].to_vec();
|
||||
|
||||
if args_without_command.len() == 1 {
|
||||
args_without_command[0]
|
||||
.split_whitespace()
|
||||
.map(|x| x.to_owned())
|
||||
.collect::<Vec<String>>()
|
||||
} else {
|
||||
args_without_command
|
||||
}
|
||||
};
|
||||
|
||||
println!("{args:#?}");
|
||||
|
||||
vec![Ts6Action::DoNothing]
|
||||
}
|
||||
}
|
||||
201
src/ts6/commands/mod.rs
Normal file
201
src/ts6/commands/mod.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
SENDER,
|
||||
commands::IrcMessage,
|
||||
messages::Message,
|
||||
sender::IrcResponse,
|
||||
ts6::{
|
||||
ServerId, Ts6,
|
||||
commands::{
|
||||
capab::Capab, ping::Ping, privmsg::Privmsg, server::Server, svinfo::Svinfo, uid::Uid,
|
||||
},
|
||||
structs::UserId,
|
||||
},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use tokio::{io::BufWriter, net::TcpStream};
|
||||
|
||||
mod capab;
|
||||
mod ping;
|
||||
mod privmsg;
|
||||
mod server;
|
||||
mod svinfo;
|
||||
mod uid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Ts6Info {
|
||||
pub sid: Option<ServerId>,
|
||||
pub hopcount: Option<u16>,
|
||||
pub description: Option<String>,
|
||||
pub name: Option<String>,
|
||||
|
||||
pub identified: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Ts6Action {
|
||||
SetInfo(Ts6Info),
|
||||
SendText(IrcResponse),
|
||||
SendMessage(Message),
|
||||
DoNothing,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Ts6Handler: Send + Sync {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
server_status: Ts6,
|
||||
my_sid: ServerId,
|
||||
sender: Option<CommandSender>,
|
||||
hostname: &str,
|
||||
) -> Vec<Ts6Action>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Ts6Command {
|
||||
command: String,
|
||||
arguments: Vec<String>,
|
||||
sender: Option<CommandSender>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CommandSender {
|
||||
User(UserId),
|
||||
Server(ServerId),
|
||||
}
|
||||
|
||||
impl Ts6Command {
|
||||
pub async fn new(command_with_arguments: String) -> Self {
|
||||
let mut command_sender = None;
|
||||
let mut split_command: Vec<&str> = command_with_arguments
|
||||
.split_whitespace()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
if split_command[0].starts_with(":") {
|
||||
let sender = split_command.remove(0).to_string().replace(":", "");
|
||||
|
||||
dbg!(&sender);
|
||||
|
||||
match sender.len() {
|
||||
3 => {
|
||||
if let Ok(sid) = ServerId::try_from(sender) {
|
||||
command_sender = Some(CommandSender::Server(sid));
|
||||
}
|
||||
}
|
||||
|
||||
9 => {
|
||||
if let Ok(uid) = UserId::try_from(sender) {
|
||||
command_sender = Some(CommandSender::User(uid));
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let command = split_command[0].to_owned();
|
||||
let mut arguments = Vec::new();
|
||||
let mut buffer: Option<String> = None;
|
||||
|
||||
split_command[1..]
|
||||
.iter()
|
||||
.for_each(|e| match (buffer.as_mut(), e.starts_with(":")) {
|
||||
(None, false) => arguments.push(e.to_string()),
|
||||
(None, true) => {
|
||||
buffer = Some(e[1..].to_string());
|
||||
}
|
||||
(Some(buf), starts_with_colon) => {
|
||||
buf.push(' ');
|
||||
buf.push_str(if starts_with_colon { &e[1..] } else { &e });
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(buf) = buffer {
|
||||
arguments.push(buf.to_string());
|
||||
}
|
||||
|
||||
Self {
|
||||
command: command,
|
||||
arguments: arguments,
|
||||
sender: command_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
&self,
|
||||
ts6_status: &mut Ts6,
|
||||
hostname: &str,
|
||||
my_sid: &ServerId,
|
||||
writer: &mut BufWriter<TcpStream>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let mut command_map: HashMap<String, &dyn Ts6Handler> = HashMap::new();
|
||||
let message_sender = SENDER.lock().await.clone().unwrap();
|
||||
|
||||
command_map.insert("CAPAB".to_owned(), &Capab);
|
||||
command_map.insert("SERVER".to_owned(), &Server);
|
||||
command_map.insert("PING".to_owned(), &Ping);
|
||||
command_map.insert("SVINFO".to_owned(), &Svinfo);
|
||||
command_map.insert("UID".to_owned(), &Uid);
|
||||
command_map.insert("PRIVMSG".to_owned(), &Privmsg);
|
||||
|
||||
let command_to_execute = command_map
|
||||
.get(&self.command.to_uppercase())
|
||||
.map(|v| *v)
|
||||
.ok_or(anyhow!("error"))?; // TODO: error handling!!!
|
||||
|
||||
let actions = command_to_execute
|
||||
.handle(
|
||||
self.arguments.clone(),
|
||||
ts6_status.clone(),
|
||||
ServerId::try_from(my_sid.clone().to_owned()).unwrap(),
|
||||
self.sender.clone(),
|
||||
hostname,
|
||||
)
|
||||
.await;
|
||||
|
||||
println!("{actions:#?}");
|
||||
|
||||
for action in actions {
|
||||
match action {
|
||||
Ts6Action::DoNothing => {}
|
||||
Ts6Action::SetInfo(new_info) => {
|
||||
if let Some(sid) = new_info.sid {
|
||||
(*ts6_status).server_id = sid;
|
||||
};
|
||||
|
||||
if let Some(hopcount) = new_info.hopcount {
|
||||
(*ts6_status).hopcount = hopcount;
|
||||
};
|
||||
|
||||
if let Some(name) = new_info.name {
|
||||
(*ts6_status).hostname = name;
|
||||
};
|
||||
|
||||
if let Some(description) = new_info.description {
|
||||
(*ts6_status).description = description;
|
||||
};
|
||||
|
||||
if let Some(identified) = new_info.identified {
|
||||
(*ts6_status).identified = identified;
|
||||
}
|
||||
}
|
||||
Ts6Action::SendText(response) => {
|
||||
response
|
||||
.send(&my_sid.to_string(), writer, false)
|
||||
.await
|
||||
.unwrap();
|
||||
// TODO: error handling
|
||||
}
|
||||
Ts6Action::SendMessage(message) => {
|
||||
message_sender.send(message.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
31
src/ts6/commands/ping.rs
Normal file
31
src/ts6/commands/ping.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
sender::IrcResponse,
|
||||
ts6::{
|
||||
ServerId, Ts6,
|
||||
commands::{CommandSender, Ts6Action, Ts6Handler},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Ping;
|
||||
|
||||
#[async_trait]
|
||||
impl Ts6Handler for Ping {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
_server_status: Ts6,
|
||||
my_sid: ServerId,
|
||||
sender: Option<CommandSender>,
|
||||
_hostname: &str,
|
||||
) -> Vec<Ts6Action> {
|
||||
vec![Ts6Action::SendText(IrcResponse {
|
||||
sender: None,
|
||||
command: "PONG".into(),
|
||||
arguments: Vec::new(),
|
||||
receiver: None,
|
||||
message: format!("{my_sid} {}", command[0].clone()),
|
||||
})]
|
||||
}
|
||||
}
|
||||
58
src/ts6/commands/privmsg.rs
Normal file
58
src/ts6/commands/privmsg.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
FOREIGN_CONNECTED_USERS,
|
||||
messages::{PrivMessage, Receiver},
|
||||
ts6::{
|
||||
Ts6,
|
||||
commands::{CommandSender, Ts6Action, Ts6Handler},
|
||||
structs::{ServerId, UserId},
|
||||
},
|
||||
user::UserUnwrapped,
|
||||
};
|
||||
|
||||
pub struct Privmsg;
|
||||
|
||||
#[async_trait]
|
||||
impl Ts6Handler for Privmsg {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
server_status: Ts6,
|
||||
my_sid: ServerId,
|
||||
sender: Option<CommandSender>,
|
||||
hostname: &str,
|
||||
) -> Vec<Ts6Action> {
|
||||
'priv_msg: {
|
||||
let mut sending_user: Option<UserUnwrapped> = None;
|
||||
|
||||
dbg!(&sender);
|
||||
|
||||
if let Ok(user_id) = UserId::try_from(command[0].clone()) {
|
||||
if let Some(CommandSender::User(command_sender)) = sender {
|
||||
let foreign_users = FOREIGN_CONNECTED_USERS.lock().await;
|
||||
|
||||
for user in foreign_users.iter() {
|
||||
if user.user_id == command_sender {
|
||||
sending_user = Some(user.clone())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dbg!("sender");
|
||||
break 'priv_msg vec![];
|
||||
}
|
||||
|
||||
vec![Ts6Action::SendMessage(
|
||||
crate::messages::Message::PrivMessage(PrivMessage {
|
||||
sender: sending_user.unwrap(),
|
||||
receiver: Receiver::UserId(user_id),
|
||||
text: command[1].clone(),
|
||||
}),
|
||||
)]
|
||||
} else {
|
||||
dbg!("userid");
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/ts6/commands/server.rs
Normal file
58
src/ts6/commands/server.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use crate::ts6::{
|
||||
ServerId, Ts6,
|
||||
commands::{CommandSender, Ts6Action, Ts6Handler},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub struct Server;
|
||||
|
||||
// TODO: handle *ALL* params
|
||||
|
||||
#[async_trait]
|
||||
impl Ts6Handler for Server {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
_server_status: Ts6,
|
||||
my_sid: ServerId,
|
||||
sender: Option<CommandSender>,
|
||||
hostname: &str,
|
||||
) -> Vec<Ts6Action> {
|
||||
let name = Some(command[0].clone());
|
||||
let hopcount = Some(command[1].parse::<u16>().unwrap());
|
||||
let sid = {
|
||||
if let Some(sid) = ServerId::try_from(command[2].clone()).ok() {
|
||||
Some(sid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let _flags = Some(command[3].clone());
|
||||
let description = Some(command[4].clone());
|
||||
|
||||
println!("server cmd");
|
||||
|
||||
vec![
|
||||
Ts6Action::SetInfo(super::Ts6Info {
|
||||
sid,
|
||||
hopcount,
|
||||
description,
|
||||
name,
|
||||
|
||||
identified: Some(true),
|
||||
}),
|
||||
Ts6Action::SendText(crate::sender::IrcResponse {
|
||||
sender: Some(hostname.to_owned().clone()),
|
||||
command: "SERVER".to_owned(),
|
||||
receiver: None,
|
||||
arguments: vec![
|
||||
hostname.to_owned().clone(),
|
||||
"1".to_owned(),
|
||||
my_sid.clone().to_string(),
|
||||
"+".to_owned(),
|
||||
],
|
||||
message: String::from(":TODO"),
|
||||
}),
|
||||
]
|
||||
}
|
||||
}
|
||||
47
src/ts6/commands/svinfo.rs
Normal file
47
src/ts6/commands/svinfo.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use crate::{
|
||||
sender::IrcResponse,
|
||||
ts6::{
|
||||
ServerId, Ts6,
|
||||
commands::{CommandSender, Ts6Action, Ts6Handler},
|
||||
},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub struct Svinfo;
|
||||
|
||||
const TS_CURRENT: u8 = 6;
|
||||
const TS_MINIMUM: u8 = 6;
|
||||
|
||||
#[async_trait]
|
||||
impl Ts6Handler for Svinfo {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
_server_status: Ts6,
|
||||
_my_sid: ServerId,
|
||||
sender: Option<CommandSender>,
|
||||
_hostname: &str,
|
||||
) -> Vec<Ts6Action> {
|
||||
let ts_current = command[0].parse::<u8>().unwrap();
|
||||
let ts_minimum = command[1].parse::<u8>().unwrap();
|
||||
let current_time = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
// XXX: we need to properly disconnect with a QUIT message but we currently don't handle
|
||||
// that.. we probably need a Ts6Action for that. same goes for regular irc commands
|
||||
assert_eq!(ts_current, TS_CURRENT);
|
||||
assert_eq!(ts_minimum, TS_MINIMUM);
|
||||
|
||||
vec![Ts6Action::SendText(IrcResponse {
|
||||
sender: None,
|
||||
command: "SVINFO".to_owned(),
|
||||
receiver: None,
|
||||
arguments: vec!["6".to_owned(), "6".to_owned(), "0".to_owned()],
|
||||
message: format!(":{}", current_time),
|
||||
})]
|
||||
}
|
||||
}
|
||||
58
src/ts6/commands/uid.rs
Normal file
58
src/ts6/commands/uid.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use std::{
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
str::FromStr,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
FOREIGN_CONNECTED_USERS,
|
||||
ts6::{
|
||||
ServerId, Ts6,
|
||||
commands::{CommandSender, Ts6Action, Ts6Handler},
|
||||
structs::UserId,
|
||||
},
|
||||
user::UserUnwrapped,
|
||||
usermodes::Usermodes,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub struct Uid;
|
||||
|
||||
#[async_trait]
|
||||
impl Ts6Handler for Uid {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
server_status: Ts6,
|
||||
my_sid: ServerId,
|
||||
sender: Option<CommandSender>,
|
||||
hostname: &str,
|
||||
) -> Vec<Ts6Action> {
|
||||
let username = command[0].clone();
|
||||
let hops = command[1].clone().parse::<u16>().unwrap();
|
||||
let timestamp = UNIX_EPOCH + Duration::new(command[2].parse::<u64>().unwrap(), 0);
|
||||
let usermodes = Usermodes::default(); // XXX
|
||||
let ip = IpAddr::from_str(&command[7]).unwrap_or(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
|
||||
let user_id = UserId::try_from(command[8].clone()).unwrap();
|
||||
// TODO: error handling
|
||||
|
||||
let user = UserUnwrapped {
|
||||
username: username.clone(),
|
||||
nickname: username.clone(),
|
||||
realname: username.clone(),
|
||||
hopcount: hops,
|
||||
identified: true,
|
||||
user_id,
|
||||
usermodes,
|
||||
timestamp,
|
||||
ip,
|
||||
};
|
||||
|
||||
dbg!(&user);
|
||||
|
||||
let mut foreign_users = FOREIGN_CONNECTED_USERS.lock().await;
|
||||
foreign_users.insert(user.clone());
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
148
src/ts6/mod.rs
Normal file
148
src/ts6/mod.rs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// TODO: better error handling
|
||||
|
||||
use std::{
|
||||
net::TcpStream,
|
||||
time::{Duration, UNIX_EPOCH},
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, BufReader as TokioBufReader, BufWriter as TokioBufWriter},
|
||||
net::TcpStream as TokioTcpStream,
|
||||
sync::broadcast::Receiver,
|
||||
time::sleep,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::ServerInfo,
|
||||
messages::Message,
|
||||
sender::IrcResponse,
|
||||
ts6::{commands::Ts6Command, structs::ServerId},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Ts6 {
|
||||
pub server_id: ServerId,
|
||||
pub hopcount: u16,
|
||||
pub description: String,
|
||||
pub hostname: String,
|
||||
|
||||
identified: bool,
|
||||
}
|
||||
|
||||
mod commands;
|
||||
pub mod structs;
|
||||
|
||||
impl Ts6 {
|
||||
pub async fn handle_command(
|
||||
&mut self,
|
||||
_my_server_id: &ServerId,
|
||||
args: String,
|
||||
hostname: &str,
|
||||
my_sid: &ServerId,
|
||||
writer: &mut TokioBufWriter<TokioTcpStream>,
|
||||
) {
|
||||
println!("server command: {}", self.server_id);
|
||||
let args = Ts6Command::new(args).await;
|
||||
println!("args: {args:#?}");
|
||||
|
||||
// XXX
|
||||
let result = args.execute(self, hostname, my_sid, writer).await;
|
||||
if result.is_err() {
|
||||
println!("{result:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn tcp_listener(
|
||||
&self,
|
||||
stream: &TcpStream,
|
||||
info: &ServerInfo,
|
||||
reader: &mut TokioBufReader<TokioTcpStream>,
|
||||
my_server_id: &ServerId,
|
||||
) -> Result<Ts6, anyhow::Error> {
|
||||
let mut buffer = String::new();
|
||||
let mut self_clone = self.clone();
|
||||
|
||||
let mut writer = TokioBufWriter::new(TokioTcpStream::from_std(stream.try_clone()?)?);
|
||||
|
||||
match reader.read_line(&mut buffer).await {
|
||||
Ok(0) => anyhow::bail!(""),
|
||||
Ok(_) => {}
|
||||
|
||||
Err(_) => {
|
||||
anyhow::bail!("");
|
||||
}
|
||||
}
|
||||
|
||||
println!("ts6: {buffer}");
|
||||
|
||||
let args = buffer
|
||||
.split_whitespace()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
self_clone
|
||||
.handle_command(
|
||||
my_server_id,
|
||||
args.join(" "),
|
||||
&info.server_hostname,
|
||||
my_server_id,
|
||||
&mut writer,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(self_clone)
|
||||
}
|
||||
|
||||
pub async fn message_listener(
|
||||
&self,
|
||||
receiver: &mut Receiver<Message>,
|
||||
writer: &mut TokioBufWriter<TokioTcpStream>,
|
||||
my_sid: &ServerId,
|
||||
hostname: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if !self.identified {
|
||||
sleep(Duration::from_millis(250)).await; // avoid immediate returns b'cuz they result in high
|
||||
// cpu usage
|
||||
return Ok(()); // TODO: error handling
|
||||
}
|
||||
|
||||
let message: Message = receiver.recv().await.unwrap();
|
||||
|
||||
match message {
|
||||
Message::NetJoinMessage(net_join_message) => {
|
||||
let user = net_join_message.user.clone();
|
||||
|
||||
// TODO: refactor this entire thing. we need hostmask and ip and such fully working
|
||||
IrcResponse {
|
||||
sender: Some(my_sid.clone().to_string()),
|
||||
command: "UID".to_string(),
|
||||
receiver: None,
|
||||
arguments: vec![
|
||||
user.nickname.clone(),
|
||||
(user.hopcount + 1).to_string(),
|
||||
user.timestamp
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
.to_string(),
|
||||
user.usermodes.into(),
|
||||
format!("~{}", user.username.clone()),
|
||||
user.ip.to_string(),
|
||||
user.ip.to_string(),
|
||||
user.ip.to_string(),
|
||||
user.user_id.to_string().clone(),
|
||||
"*".to_owned(),
|
||||
format!(":{}", user.username.clone()),
|
||||
],
|
||||
message: String::new(),
|
||||
}
|
||||
.send(hostname, writer, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
215
src/ts6/structs.rs
Normal file
215
src/ts6/structs.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
const A_TO_Z: &'static [u8] = b"ABCDEFGHIJKLMNOPQRSTUVW";
|
||||
const ZERO_TO_9: &'static [u8] = b"0123456789";
|
||||
|
||||
#[derive(Clone, Default, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct ServerId([char; 3]);
|
||||
|
||||
#[derive(Clone, Default, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct UserId([char; 9]);
|
||||
|
||||
impl UserId {
|
||||
pub fn to_vec(&self) -> Vec<char> {
|
||||
self.0.to_vec()
|
||||
}
|
||||
|
||||
pub fn get_server_id(&self) -> ServerId {
|
||||
let vector = self.to_vec();
|
||||
let server_id_chars = vector[..3].to_vec();
|
||||
|
||||
let server_id = ServerId::try_from(server_id_chars).unwrap();
|
||||
|
||||
server_id
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> Vec<char> {
|
||||
let vector = self.to_vec();
|
||||
let id_chars = vector[3..].to_vec();
|
||||
|
||||
id_chars
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for UserId {
|
||||
fn into(self) -> String {
|
||||
String::from_utf8_lossy(
|
||||
self.to_vec()
|
||||
.iter()
|
||||
.map(|x| x.clone() as u8)
|
||||
.collect::<Vec<u8>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for UserId {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
dbg!(&value);
|
||||
|
||||
let chars = value.chars().into_iter().collect::<Vec<char>>();
|
||||
|
||||
if chars.len() != 9 || !ServerId::is_server_id(&value[..3]) {
|
||||
return Err("string isn't a user id");
|
||||
}
|
||||
|
||||
Ok(Self([
|
||||
chars[0].clone(),
|
||||
chars[1].clone(),
|
||||
chars[2].clone(),
|
||||
chars[3].clone(),
|
||||
chars[4].clone(),
|
||||
chars[5].clone(),
|
||||
chars[6].clone(),
|
||||
chars[7].clone(),
|
||||
chars[8].clone(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<char>> for UserId {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(chars: Vec<char>) -> Result<Self, Self::Error> {
|
||||
if chars.len() != 9
|
||||
|| !ServerId::is_server_id(
|
||||
&String::from_utf8_lossy(
|
||||
&chars.iter().map(|x| x.clone() as u8).collect::<Vec<u8>>()[..3],
|
||||
)
|
||||
.to_string(),
|
||||
)
|
||||
{
|
||||
return Err("string isn't a user id");
|
||||
}
|
||||
|
||||
Ok(Self([
|
||||
chars[0].clone(),
|
||||
chars[1].clone(),
|
||||
chars[2].clone(),
|
||||
chars[3].clone(),
|
||||
chars[4].clone(),
|
||||
chars[5].clone(),
|
||||
chars[6].clone(),
|
||||
chars[7].clone(),
|
||||
chars[8].clone(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UserId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// We could just call our implementation of Into<String>, but as we can return an error
|
||||
// here, this seems a better option
|
||||
if let Some(string) = String::from_utf8(
|
||||
self.to_vec()
|
||||
.iter()
|
||||
.map(|x| x.clone() as u8)
|
||||
.collect::<Vec<u8>>(),
|
||||
)
|
||||
.ok()
|
||||
{
|
||||
f.write_str(&string)?;
|
||||
} else {
|
||||
return Err(std::fmt::Error);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerId {
|
||||
pub fn to_vec(&self) -> Vec<char> {
|
||||
self.0.to_vec()
|
||||
}
|
||||
|
||||
// there might be a cleaner way to do this?
|
||||
pub fn is_server_id(id: &str) -> bool {
|
||||
let chars = id.chars().collect::<Vec<char>>();
|
||||
|
||||
if chars.len() != 3 {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !ZERO_TO_9.contains(&(chars[0] as u8)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !(A_TO_Z.contains(&(chars[1] as u8)) || ZERO_TO_9.contains(&(chars[1] as u8))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !(A_TO_Z.contains(&(chars[2] as u8)) || ZERO_TO_9.contains(&(chars[2] as u8))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for ServerId {
|
||||
fn into(self) -> String {
|
||||
String::from_utf8_lossy(
|
||||
self.to_vec()
|
||||
.iter()
|
||||
.map(|x| x.clone() as u8)
|
||||
.collect::<Vec<u8>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ServerId {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
let chars = value.chars().into_iter().collect::<Vec<char>>();
|
||||
|
||||
if chars.len() != 3 || !Self::is_server_id(&value) {
|
||||
return Err("string isn't a server id");
|
||||
}
|
||||
|
||||
Ok(Self([chars[0].clone(), chars[1].clone(), chars[2].clone()]))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<char>> for ServerId {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(chars: Vec<char>) -> Result<Self, Self::Error> {
|
||||
if chars.len() != 3
|
||||
|| !Self::is_server_id(
|
||||
&String::from_utf8_lossy(
|
||||
&chars.iter().map(|x| x.clone() as u8).collect::<Vec<u8>>(),
|
||||
)
|
||||
.to_string(),
|
||||
)
|
||||
{
|
||||
return Err("string isn't a server id");
|
||||
}
|
||||
|
||||
Ok(Self([chars[0].clone(), chars[1].clone(), chars[2].clone()]))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ServerId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// We could just call our implementation of Into<String>, but as we can return an error
|
||||
// here, this seems a better option
|
||||
if let Some(string) = String::from_utf8(
|
||||
self.to_vec()
|
||||
.iter()
|
||||
.map(|x| x.clone() as u8)
|
||||
.collect::<Vec<u8>>(),
|
||||
)
|
||||
.ok()
|
||||
{
|
||||
f.write_str(&string)?;
|
||||
} else {
|
||||
return Err(std::fmt::Error);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
33
src/user.rs
33
src/user.rs
|
|
@ -1,11 +1,24 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use crate::{ts6::structs::UserId, usermodes::Usermodes};
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct User {
|
||||
pub nickname: Option<String>,
|
||||
pub username: Option<String>,
|
||||
pub realname: Option<String>,
|
||||
pub identified: bool,
|
||||
pub hopcount: Option<u16>,
|
||||
pub user_id: Option<UserId>,
|
||||
pub usermodes: Usermodes,
|
||||
pub timestamp: Option<SystemTime>,
|
||||
pub ip: Option<IpAddr>,
|
||||
// pub hostname: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
|
|
@ -14,9 +27,19 @@ pub struct UserUnwrapped {
|
|||
pub username: String,
|
||||
pub realname: String,
|
||||
pub identified: bool,
|
||||
pub hopcount: u16,
|
||||
pub user_id: UserId,
|
||||
pub usermodes: Usermodes,
|
||||
pub timestamp: SystemTime,
|
||||
pub ip: IpAddr,
|
||||
// pub hostname: Option<String>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn is_populated_without_uid(&self) -> bool {
|
||||
self.realname.is_some() && self.username.is_some() && self.nickname.is_some()
|
||||
}
|
||||
|
||||
pub fn is_populated(&self) -> bool {
|
||||
self.realname.is_some() && self.username.is_some() && self.nickname.is_some()
|
||||
}
|
||||
|
|
@ -27,6 +50,11 @@ impl User {
|
|||
username: self.username.clone().unwrap(),
|
||||
realname: self.realname.clone().unwrap(),
|
||||
identified: self.identified,
|
||||
hopcount: self.hopcount.clone().unwrap(),
|
||||
user_id: self.user_id.clone().unwrap(),
|
||||
usermodes: self.usermodes.clone(),
|
||||
timestamp: self.timestamp.clone().unwrap(),
|
||||
ip: self.ip.unwrap_or(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -36,6 +64,11 @@ impl User {
|
|||
username: None,
|
||||
realname: None,
|
||||
identified: false,
|
||||
hopcount: Some(0),
|
||||
user_id: None,
|
||||
usermodes: Usermodes::default(),
|
||||
timestamp: None,
|
||||
ip: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
77
src/userid_gen.rs
Normal file
77
src/userid_gen.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
static CURRENT_ID: Lazy<Mutex<Vec<char>>> =
|
||||
Lazy::new(|| Mutex::new(vec!['A', 'A', 'A', 'A', 'A', 'A']));
|
||||
static ZZZZZZ_REACHED: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UidIncreaseError {
|
||||
#[error("cap reached")]
|
||||
UserCapReached,
|
||||
}
|
||||
|
||||
pub async fn increase_user_id() -> Result<Vec<char>, UidIncreaseError> {
|
||||
let mut current_id = CURRENT_ID.lock().await;
|
||||
let mut zzzzzz_reached = ZZZZZZ_REACHED.lock().await;
|
||||
|
||||
let mut idx = 5;
|
||||
|
||||
'id_increaser: {
|
||||
if !zzzzzz_reached.clone() {
|
||||
loop {
|
||||
if current_id[idx] != 'Z' {
|
||||
current_id[idx] = (current_id[idx] as u8 + 1) as char;
|
||||
break 'id_increaser;
|
||||
} else {
|
||||
if idx == 0 {
|
||||
*zzzzzz_reached = true;
|
||||
break;
|
||||
}
|
||||
|
||||
current_id[idx] = 'A';
|
||||
idx -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here, our id is ZZZZZZ and we need to start using numbers
|
||||
idx = 5;
|
||||
|
||||
(*current_id) = vec!['A', '0', '0', '0', '0', '0'];
|
||||
}
|
||||
|
||||
loop {
|
||||
if idx != 0 {
|
||||
if current_id[idx] != '9' {
|
||||
current_id[idx] = (current_id[idx] as u8 + 1) as char;
|
||||
break 'id_increaser;
|
||||
} else {
|
||||
current_id[idx] = '0';
|
||||
idx -= 1;
|
||||
}
|
||||
} else {
|
||||
if current_id[idx] != 'Z' {
|
||||
current_id[idx] = (current_id[idx] as u8 + 1) as char;
|
||||
idx = 5;
|
||||
} else {
|
||||
return Err(UidIncreaseError::UserCapReached);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(current_id.to_vec())
|
||||
}
|
||||
|
||||
// THIS SHOULD BE USED *ONLY* FOR TESTING PURPOSES! DO NOT USE IT IN PRODUCTION CODE!
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn manually_set_user_id(user_id: Vec<char>) {
|
||||
assert_eq!(user_id.len(), 6);
|
||||
|
||||
let mut lock = CURRENT_ID.lock().await;
|
||||
|
||||
(*lock) = user_id;
|
||||
}
|
||||
45
src/usermodes.rs
Normal file
45
src/usermodes.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#[repr(u8)]
|
||||
#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
|
||||
pub enum Usermode {
|
||||
Invisible = b'i',
|
||||
HostHiding = b'x',
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
|
||||
pub struct Usermodes(Vec<Usermode>);
|
||||
|
||||
impl Into<Vec<String>> for Usermodes {
|
||||
fn into(self) -> Vec<String> {
|
||||
let mut vector: Vec<String> = vec![];
|
||||
|
||||
for i in self.0 {
|
||||
vector.push(Into::<String>::into(i));
|
||||
}
|
||||
|
||||
vector
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for Usermodes {
|
||||
fn into(self) -> String {
|
||||
format!("+{}", Into::<Vec<String>>::into(self).join(""))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Usermodes {
|
||||
fn default() -> Self {
|
||||
Self(vec![Usermode::Invisible, Usermode::HostHiding])
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<char> for Usermode {
|
||||
fn into(self) -> char {
|
||||
self as u8 as char
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for Usermode {
|
||||
fn into(self) -> String {
|
||||
Into::<char>::into(self).to_string()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue