Format, Status, User Stati & Endpoints

This commit is contained in:
Alex Emmet 2026-01-07 15:54:05 +01:00
commit 020bb7b072
11 changed files with 963 additions and 396 deletions

260
Cargo.lock generated
View file

@ -8,6 +8,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"ansi_term", "ansi_term",
"async-trait",
"async-tungstenite", "async-tungstenite",
"axum", "axum",
"base64 0.22.1", "base64 0.22.1",
@ -27,7 +28,7 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
"rand_core 0.6.4", "rand_core 0.6.4",
"reqwest", "reqwest",
"rustls 0.23.35", "rustls 0.23.36",
"rustls-pemfile 2.2.0", "rustls-pemfile 2.2.0",
"sha1", "sha1",
"sha2", "sha2",
@ -174,7 +175,7 @@ dependencies = [
"async-channel 2.5.0", "async-channel 2.5.0",
"async-executor", "async-executor",
"async-io 2.6.0", "async-io 2.6.0",
"async-lock 3.4.1", "async-lock 3.4.2",
"blocking", "blocking",
"futures-lite 2.6.1", "futures-lite 2.6.1",
"once_cell", "once_cell",
@ -229,9 +230,9 @@ dependencies = [
[[package]] [[package]]
name = "async-lock" name = "async-lock"
version = "3.4.1" version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
dependencies = [ dependencies = [
"event-listener 5.4.1", "event-listener 5.4.1",
"event-listener-strategy", "event-listener-strategy",
@ -259,7 +260,7 @@ dependencies = [
"async-channel 1.9.0", "async-channel 1.9.0",
"async-global-executor", "async-global-executor",
"async-io 2.6.0", "async-io 2.6.0",
"async-lock 3.4.1", "async-lock 3.4.2",
"crossbeam-utils", "crossbeam-utils",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -296,10 +297,21 @@ dependencies = [
] ]
[[package]] [[package]]
name = "async-tungstenite" name = "async-trait"
version = "0.32.0" version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91" checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-tungstenite"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc405d38be14342132609f06f02acaf825ddccfe76c4824a69281e0458ebd4"
dependencies = [ dependencies = [
"async-native-tls", "async-native-tls",
"async-std", "async-std",
@ -322,7 +334,7 @@ dependencies = [
"tokio-openssl", "tokio-openssl",
"tokio-rustls", "tokio-rustls",
"tungstenite", "tungstenite",
"webpki-roots 1.0.4", "webpki-roots 1.0.5",
] ]
[[package]] [[package]]
@ -348,9 +360,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "aws-lc-rs" name = "aws-lc-rs"
version = "1.15.1" version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288"
dependencies = [ dependencies = [
"aws-lc-sys", "aws-lc-sys",
"zeroize", "zeroize",
@ -358,9 +370,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-lc-sys" name = "aws-lc-sys"
version = "0.34.0" version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1"
dependencies = [ dependencies = [
"cc", "cc",
"cmake", "cmake",
@ -370,9 +382,9 @@ dependencies = [
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.8.7" version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"bytes", "bytes",
@ -403,9 +415,9 @@ dependencies = [
[[package]] [[package]]
name = "axum-core" name = "axum-core"
version = "0.5.5" version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@ -449,9 +461,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "base64ct" name = "base64ct"
version = "1.8.1" version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -492,9 +504,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.0" version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -519,9 +531,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.49" version = "1.2.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -531,9 +543,9 @@ dependencies = [
[[package]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726" checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720"
dependencies = [ dependencies = [
"smallvec", "smallvec",
"target-lexicon", "target-lexicon",
@ -557,9 +569,9 @@ dependencies = [
[[package]] [[package]]
name = "cmake" name = "cmake"
version = "0.1.56" version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
dependencies = [ dependencies = [
"cc", "cc",
] ]
@ -920,9 +932,9 @@ checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77"
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.5" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
[[package]] [[package]]
name = "flate2" name = "flate2"
@ -1167,9 +1179,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]] [[package]]
name = "gio" name = "gio"
version = "0.21.4" version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daeff3dd716d1ba91850b976b76a1c2d28f99ef6c1602cd8fdaa8fab8017fd9c" checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -1184,9 +1196,9 @@ dependencies = [
[[package]] [[package]]
name = "gio-sys" name = "gio-sys"
version = "0.21.2" version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"gobject-sys", "gobject-sys",
@ -1197,9 +1209,9 @@ dependencies = [
[[package]] [[package]]
name = "glib" name = "glib"
version = "0.21.4" version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9dbecb1c33e483a98be4acfea2ab369e1c28f517c6eadb674537409c25c4b2" checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"futures-channel", "futures-channel",
@ -1218,9 +1230,9 @@ dependencies = [
[[package]] [[package]]
name = "glib-macros" name = "glib-macros"
version = "0.21.4" version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb" checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro-crate", "proc-macro-crate",
@ -1231,9 +1243,9 @@ dependencies = [
[[package]] [[package]]
name = "glib-sys" name = "glib-sys"
version = "0.21.2" version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c"
dependencies = [ dependencies = [
"libc", "libc",
"system-deps", "system-deps",
@ -1259,9 +1271,9 @@ dependencies = [
[[package]] [[package]]
name = "gobject-sys" name = "gobject-sys"
version = "0.21.2" version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"libc", "libc",
@ -1270,9 +1282,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.12" version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@ -1447,7 +1459,7 @@ dependencies = [
"http", "http",
"hyper", "hyper",
"hyper-util", "hyper-util",
"rustls 0.23.35", "rustls 0.23.36",
"rustls-pki-types", "rustls-pki-types",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
@ -1660,9 +1672,9 @@ dependencies = [
[[package]] [[package]]
name = "iri-string" name = "iri-string"
version = "0.7.9" version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
dependencies = [ dependencies = [
"memchr", "memchr",
"serde", "serde",
@ -1670,9 +1682,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
@ -1726,9 +1738,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.178" version = "0.2.179"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
[[package]] [[package]]
name = "libm" name = "libm"
@ -1738,13 +1750,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.11" version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"libc", "libc",
"redox_syscall 0.6.0", "redox_syscall 0.7.0",
] ]
[[package]] [[package]]
@ -1759,9 +1771,9 @@ dependencies = [
[[package]] [[package]]
name = "libz-rs-sys" name = "libz-rs-sys"
version = "0.5.4" version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415"
dependencies = [ dependencies = [
"zlib-rs", "zlib-rs",
] ]
@ -1870,7 +1882,7 @@ dependencies = [
"libc", "libc",
"log", "log",
"openssl", "openssl",
"openssl-probe", "openssl-probe 0.1.6",
"openssl-sys", "openssl-sys",
"schannel", "schannel",
"security-framework 2.11.1", "security-framework 2.11.1",
@ -1886,9 +1898,9 @@ checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
[[package]] [[package]]
name = "ntapi" name = "ntapi"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081"
dependencies = [ dependencies = [
"winapi", "winapi",
] ]
@ -2017,6 +2029,12 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-probe"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.111" version = "0.9.111"
@ -2313,18 +2331,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.103" version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.42" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -2411,9 +2429,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.6.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
] ]
@ -2503,9 +2521,9 @@ dependencies = [
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "0.9.9" version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"digest", "digest",
@ -2568,9 +2586,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.35" version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"log", "log",
@ -2583,11 +2601,11 @@ dependencies = [
[[package]] [[package]]
name = "rustls-native-certs" name = "rustls-native-certs"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
dependencies = [ dependencies = [
"openssl-probe", "openssl-probe 0.2.0",
"rustls-pki-types", "rustls-pki-types",
"schannel", "schannel",
"security-framework 3.5.1", "security-framework 3.5.1",
@ -2613,9 +2631,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.13.1" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
dependencies = [ dependencies = [
"zeroize", "zeroize",
] ]
@ -2650,9 +2668,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.20" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
[[package]] [[package]]
name = "same-file" name = "same-file"
@ -2756,15 +2774,15 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.145" version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
"ryu",
"serde", "serde",
"serde_core", "serde_core",
"zmij",
] ]
[[package]] [[package]]
@ -2780,9 +2798,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
@ -3129,9 +3147,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.111" version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3214,9 +3232,9 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.23.0" version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [ dependencies = [
"fastrand 2.3.0", "fastrand 2.3.0",
"getrandom 0.3.4", "getrandom 0.3.4",
@ -3320,9 +3338,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.48.0" version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@ -3373,7 +3391,7 @@ version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [ dependencies = [
"rustls 0.23.35", "rustls 0.23.36",
"tokio", "tokio",
] ]
@ -3391,9 +3409,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.17" version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@ -3408,9 +3426,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.9.8" version = "0.9.10+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde_core", "serde_core",
@ -3423,18 +3441,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.7.3" version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.23.7" version = "0.23.10+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"toml_datetime", "toml_datetime",
@ -3444,18 +3462,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_parser" name = "toml_parser"
version = "1.0.4" version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
dependencies = [ dependencies = [
"winnow", "winnow",
] ]
[[package]] [[package]]
name = "toml_writer" name = "toml_writer"
version = "1.0.4" version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]] [[package]]
name = "tower" name = "tower"
@ -3505,9 +3523,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.43" version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [ dependencies = [
"log", "log",
"pin-project-lite", "pin-project-lite",
@ -3528,9 +3546,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.35" version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable", "valuable",
@ -3576,7 +3594,7 @@ dependencies = [
"log", "log",
"native-tls", "native-tls",
"rand 0.9.2", "rand 0.9.2",
"rustls 0.23.35", "rustls 0.23.36",
"rustls-pki-types", "rustls-pki-types",
"sha1", "sha1",
"thiserror 2.0.17", "thiserror 2.0.17",
@ -3635,9 +3653,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.7" version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
@ -3833,9 +3851,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c"
dependencies = [ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
@ -4251,9 +4269,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.13" version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -4306,18 +4324,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.31" version = "0.8.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.31" version = "0.8.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4356,9 +4374,9 @@ dependencies = [
[[package]] [[package]]
name = "zeroize_derive" name = "zeroize_derive"
version = "1.4.2" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4427,9 +4445,15 @@ dependencies = [
[[package]] [[package]]
name = "zlib-rs" name = "zlib-rs"
version = "0.5.4" version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3"
[[package]]
name = "zmij"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"
[[package]] [[package]]
name = "zopfli" name = "zopfli"

View file

@ -6,6 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
aes-gcm = "*" aes-gcm = "*"
ansi_term = "0.12.1" ansi_term = "0.12.1"
async-trait = "0.1.89"
async-tungstenite = { version = "0.32.0", features = ["futures-03-sink", "futures-util", "handshake", "__rustls-tls", "async-native-tls", "async-std", "async-std-runtime", "async-tls", "gio", "gio-runtime", "glib", "openssl", "real-async-native-tls", "real-async-tls", "real-native-tls", "real-tokio-native-tls", "real-tokio-openssl", "real-tokio-rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-openssl", "tokio-runtime", "tokio-rustls-manual-roots", "tokio-rustls-native-certs", "tokio-rustls-webpki-roots", "url", "verbose-logging", "webpki-roots" ] } async-tungstenite = { version = "0.32.0", features = ["futures-03-sink", "futures-util", "handshake", "__rustls-tls", "async-native-tls", "async-std", "async-std-runtime", "async-tls", "gio", "gio-runtime", "glib", "openssl", "real-async-native-tls", "real-async-tls", "real-native-tls", "real-tokio-native-tls", "real-tokio-openssl", "real-tokio-rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-openssl", "tokio-runtime", "tokio-rustls-manual-roots", "tokio-rustls-native-certs", "tokio-rustls-webpki-roots", "url", "verbose-logging", "webpki-roots" ] }
axum = "0.8.7" axum = "0.8.7"
base64 = "0.22.1" base64 = "0.22.1"

View file

@ -78,6 +78,9 @@ pub enum DataTypes {
communities, communities,
rho_connections, rho_connections,
user, user,
online_status,
omikron_id,
omikron_connections,
} }
impl DataTypes { impl DataTypes {
@ -155,6 +158,9 @@ impl DataTypes {
"communities" => DataTypes::communities, "communities" => DataTypes::communities,
"rhoconnections" => DataTypes::rho_connections, "rhoconnections" => DataTypes::rho_connections,
"user" => DataTypes::user, "user" => DataTypes::user,
"onlinestatus" => DataTypes::online_status,
"omikronid" => DataTypes::omikron_id,
"omikronconnections" => DataTypes::omikron_connections,
_ => DataTypes::error_type, // fallback if unknown _ => DataTypes::error_type, // fallback if unknown
} }
} }
@ -231,6 +237,15 @@ pub enum CommunicationType {
iota_connected, iota_connected,
iota_disconnected, iota_disconnected,
sync_client_iota_status, sync_client_iota_status,
get_user_data,
get_iota_data,
change_user_data,
change_iota_data,
start_register,
complete_register,
} }
impl CommunicationType { impl CommunicationType {
pub fn parse(p0: String) -> CommunicationType { pub fn parse(p0: String) -> CommunicationType {
@ -305,6 +320,15 @@ impl CommunicationType {
"userdisconnected" => CommunicationType::user_disconnected, "userdisconnected" => CommunicationType::user_disconnected,
"syncclientiotastatus" => CommunicationType::sync_client_iota_status, "syncclientiotastatus" => CommunicationType::sync_client_iota_status,
"getuserdata" => CommunicationType::get_user_data,
"getiotadata" => CommunicationType::get_iota_data,
"changeuserdata" => CommunicationType::change_user_data,
"changeiotadata" => CommunicationType::change_iota_data,
"startregister" => CommunicationType::start_register,
"completeregister" => CommunicationType::complete_register,
_ => CommunicationType::error, _ => CommunicationType::error,
} }
} }

View file

@ -1,13 +1,10 @@
use crate::{get_public_key, log}; use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::get_public_key;
use crate::sql::sql;
use crate::{ use crate::{
sql::{ sql::{
iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, iota_omikron_tracker::get_omikron_for_iota,
sql::{ sql::{get_by_user_id, get_omikron_by_id, get_random_omikron},
change_about, change_avatar, change_display_name, change_iota_id, change_iota_key,
change_keys, change_status, change_username, get_by_id, get_by_username,
get_iota_by_id, get_omikron_by_id, get_random_omikron, get_register_id,
register_complete_iota, register_complete_user,
},
}, },
util::crypto_helper::public_key_to_base64, util::crypto_helper::public_key_to_base64,
}; };
@ -16,6 +13,7 @@ use http_body_util::Full;
use hyper::body::Bytes; use hyper::body::Bytes;
use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; use hyper::{HeaderMap, Response as HttpResponse, StatusCode};
use json::JsonValue; use json::JsonValue;
use json::number::Number;
pub async fn handle( pub async fn handle(
path: &str, path: &str,
@ -37,11 +35,6 @@ pub async fn handle(
// get/ // get/
// omikron/ // omikron/
// id/ // id/
// register/
// innit/
// complete
log!("{}", path);
log!("{:?} .len = {}", path_parts, path_parts.len());
let (status, content, body_text) = if path_parts.len() >= 2 { let (status, content, body_text) = if path_parts.len() >= 2 {
match path_parts[1] { match path_parts[1] {
"get" => match path_parts[2] { "get" => match path_parts[2] {
@ -90,7 +83,7 @@ pub async fn handle(
not_found() not_found()
} }
} else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) =
get_by_id(id).await get_by_user_id(id).await
{ {
if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { if let Some(omikron_id) = get_omikron_for_iota(iota_id).await {
if let Ok((public_key, ip_address)) = if let Ok((public_key, ip_address)) =
@ -119,14 +112,131 @@ pub async fn handle(
} }
// get/id/<username> // get/id/<username>
"id" => { "id" => {
let username = path_parts[3]; let username = path_parts[2];
bad_request() if username.is_empty() {
not_found()
} else {
if let Ok((
id,
iota_id,
username,
display,
status,
about,
avatar,
sub_level,
sub_end,
public_key,
_,
_,
)) = sql::get_by_username(username).await
{
(
StatusCode::OK,
"application/json",
CommunicationValue::new(CommunicationType::success)
.add_data_str(DataTypes::username, username)
.add_data_str(DataTypes::public_key, public_key)
.add_data(
DataTypes::user_id,
JsonValue::Number(Number::from(id)),
)
.add_data(
DataTypes::iota_id,
JsonValue::Number(Number::from(iota_id)),
)
.add_data_str(DataTypes::display, display)
.add_data_str(DataTypes::status, status)
.add_data_str(DataTypes::about, about)
.add_data_str(DataTypes::avatar, avatar)
.add_data(
DataTypes::sub_level,
JsonValue::Number(Number::from(sub_level)),
)
.add_data(
DataTypes::sub_end,
JsonValue::Number(Number::from(sub_end)),
)
.to_json()
.to_string(),
)
} else {
(
StatusCode::OK,
"application/json",
CommunicationValue::new(CommunicationType::error_not_found)
.to_json()
.to_string(),
)
}
}
} }
"public_key" => ( "public_key" => (
StatusCode::OK, StatusCode::OK,
"application/json", "application/json",
public_key_to_base64(&get_public_key()), public_key_to_base64(&get_public_key()),
), ),
"user" => {
let id = path_parts[2];
let id: i64 = id.parse().unwrap_or(0);
if id == 0 {
bad_request()
} else {
if let Ok((
id,
iota_id,
username,
display,
status,
about,
avatar,
sub_level,
sub_end,
public_key,
_,
_,
)) = sql::get_by_user_id(id).await
{
(
StatusCode::OK,
"application/json",
CommunicationValue::new(CommunicationType::success)
.add_data_str(DataTypes::username, username)
.add_data_str(DataTypes::public_key, public_key)
.add_data(
DataTypes::user_id,
JsonValue::Number(Number::from(id)),
)
.add_data(
DataTypes::iota_id,
JsonValue::Number(Number::from(iota_id)),
)
.add_data_str(DataTypes::display, display)
.add_data_str(DataTypes::status, status)
.add_data_str(DataTypes::about, about)
.add_data_str(DataTypes::avatar, avatar)
.add_data(
DataTypes::sub_level,
JsonValue::Number(Number::from(sub_level)),
)
.add_data(
DataTypes::sub_end,
JsonValue::Number(Number::from(sub_end)),
)
.to_json()
.to_string(),
)
} else {
(
StatusCode::OK,
"application/json",
CommunicationValue::new(CommunicationType::error_not_found)
.to_json()
.to_string(),
)
}
}
}
_ => { _ => {
let id = path_parts[2]; let id = path_parts[2];
let id: i64 = id.parse().unwrap_or(0); let id: i64 = id.parse().unwrap_or(0);

View file

@ -1,11 +1,7 @@
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::sql::iota_omikron_tracker::{ use crate::sql::connection_status::ConnectionType;
track_iota_omikron, untrack_by_omikron as untrack_iota_by_omikron, untrack_iota, use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id};
}; use crate::sql::user_online_tracker::{self};
use crate::sql::sql::get_omikron_by_id;
use crate::sql::user_online_tracker::{
track_user_omikron, untrack_by_omikron as untrack_user_by_omikron, untrack_user,
};
use crate::util::crypto_helper::encrypt; use crate::util::crypto_helper::encrypt;
use crate::util::logger::PrintType; use crate::util::logger::PrintType;
use crate::{get_private_key, log_out}; use crate::{get_private_key, log_out};
@ -18,6 +14,7 @@ use futures::stream::SplitStream;
use hyper::upgrade::Upgraded; use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use json::JsonValue; use json::JsonValue;
use json::number::Number;
use rand::Rng; use rand::Rng;
use rand::distributions::Alphanumeric; use rand::distributions::Alphanumeric;
use std::sync::Arc; use std::sync::Arc;
@ -73,7 +70,7 @@ impl OmikronConnection {
} }
sender.send(message_text).await.unwrap(); sender.send(message_text).await.unwrap();
} }
pub async fn get_user_id(&self) -> i64 { pub async fn get_omikron_id(&self) -> i64 {
*self.omikron_id.read().await *self.omikron_id.read().await
} }
pub async fn is_identified(&self) -> bool { pub async fn is_identified(&self) -> bool {
@ -102,137 +99,168 @@ impl OmikronConnection {
return; return;
} }
// Handle identification // If not yet identified
if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { if !self.is_identified().await {
let omikron_id = cv // handle identification
.get_data(DataTypes::omikron) if !*self.identified.read().await && cv.is_type(CommunicationType::identification) {
.unwrap_or(&JsonValue::Null) let omikron_id = cv
.as_i64() .get_data(DataTypes::omikron)
.unwrap_or(0); .unwrap_or(&JsonValue::Null)
match get_omikron_by_id(omikron_id).await { .as_i64()
Ok((public_key, _)) => { .unwrap_or(0);
// Generate Challenge, encrypt it and send it to the omikron match get_omikron_by_id(omikron_id).await {
*self.omikron_id.write().await = omikron_id; Ok((public_key, _)) => {
// Generate Challenge, encrypt it and send it to the omikron
*self.omikron_id.write().await = omikron_id;
let challenge_str: String = rand::thread_rng() let challenge_str: String = rand::thread_rng()
.sample_iter(&Alphanumeric) .sample_iter(&Alphanumeric)
.take(32) .take(32)
.map(char::from) .map(char::from)
.collect(); .collect();
*self.challenge.write().await = challenge_str.clone(); *self.challenge.write().await = challenge_str.clone();
let user_public_key_bytes = match STANDARD.decode(&public_key) { let user_public_key_bytes = match STANDARD.decode(&public_key) {
Ok(bytes) => bytes, Ok(bytes) => bytes,
Err(_) => { Err(_) => {
self.send_error_response(
&cv.get_id(),
CommunicationType::error_invalid_omikron_id,
)
.await;
return;
}
};
*self.pub_key.write().await = Some(user_public_key_bytes.clone());
let omikron_pub_key: PublicKey =
match PublicKey::from_bytes(&user_public_key_bytes) {
Some(key) => key,
None => {
self.send_error_response( self.send_error_response(
&cv.get_id(), &cv.get_id(),
CommunicationType::error_invalid_public_key, CommunicationType::error_invalid_omikron_id,
) )
.await; .await;
return; return;
} }
}; };
*self.pub_key.write().await = Some(user_public_key_bytes.clone());
let encrypted_challenge = let omikron_pub_key: PublicKey =
encrypt(get_private_key(), omikron_pub_key, &challenge_str) match PublicKey::from_bytes(&user_public_key_bytes) {
.unwrap_or("".to_string()); Some(key) => key,
_ => {
self.send_error_response(
&cv.get_id(),
CommunicationType::error_invalid_public_key,
)
.await;
return;
}
};
let response = CommunicationValue::new(CommunicationType::challenge) let encrypted_challenge =
.add_data_str( encrypt(get_private_key(), omikron_pub_key, &challenge_str)
DataTypes::public_key, .unwrap_or("".to_string());
STANDARD.encode(get_public_key().as_bytes()),
let response = CommunicationValue::new(CommunicationType::challenge)
.add_data_str(
DataTypes::public_key,
STANDARD.encode(get_public_key().as_bytes()),
)
.add_data_str(DataTypes::challenge, encrypted_challenge)
.with_id(cv.get_id());
self.send_message(&response).await;
*self.identified.write().await = true;
return;
}
Err(e) => {
self.send_message(
&CommunicationValue::new(CommunicationType::error_not_authenticated)
.with_id(cv.get_id())
.add_data_str(DataTypes::error_type, e.to_string()),
) )
.add_data_str(DataTypes::challenge, encrypted_challenge) .await;
.with_id(cv.get_id());
self.send_message(&response).await; return;
*self.identified.write().await = true; }
return;
} }
Err(e) => { }
self.send_message(
&CommunicationValue::new(CommunicationType::error_not_authenticated) // Handle challenge response
.with_id(cv.get_id()) if *self.identified.read().await
.add_data_str(DataTypes::error_type, e.to_string()), && !*self.challenged.read().await
&& cv.is_type(CommunicationType::challenge_response)
{
let client_response = cv
.get_data(DataTypes::challenge)
.unwrap_or(&JsonValue::Null)
.as_str()
.unwrap_or("");
let expected_challenge = self.challenge.read().await.clone();
if client_response == expected_challenge {
*self.challenged.write().await = true;
let response =
CommunicationValue::new(CommunicationType::identification_response)
.with_id(cv.get_id());
let _ = sql::set_omikron_active(self.get_omikron_id().await, true);
self.send_message(&response).await;
} else {
self.send_error_response(
&cv.get_id(),
CommunicationType::error_invalid_challenge,
) )
.await; .await;
self.close().await;
return;
} }
return;
} }
}
// Handle challenge response // if not identified && not identifying
if *self.identified.read().await
&& !*self.challenged.read().await
&& cv.is_type(CommunicationType::challenge_response)
{
let client_response = cv
.get_data(DataTypes::challenge)
.unwrap_or(&JsonValue::Null)
.as_str()
.unwrap_or("");
let expected_challenge = self.challenge.read().await.clone();
if client_response == expected_challenge {
*self.challenged.write().await = true;
let response = CommunicationValue::new(CommunicationType::identification_response)
.with_id(cv.get_id());
self.send_message(&response).await;
} else {
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_challenge)
.await;
self.close().await;
}
return;
}
if !self.is_identified().await {
self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated)
.await; .await;
self.close().await; self.close().await;
return; return;
} }
let omikron_id = self.get_user_id().await; let omikron_id = self.get_omikron_id().await;
if cv.is_type(CommunicationType::user_connected) { if cv.is_type(CommunicationType::user_connected) {
if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) {
track_user_omikron(user_id, omikron_id).await; user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id)
.await;
} }
return; return;
} }
if cv.is_type(CommunicationType::user_disconnected) { if cv.is_type(CommunicationType::user_disconnected) {
if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) {
untrack_user(user_id).await; if let Some(status) = user_online_tracker::get_user_status(user_id).await {
user_online_tracker::track_user_status(
user_id,
ConnectionType::UserOffline,
status.omikron_id,
)
.await;
}
} }
return; return;
} }
if cv.is_type(CommunicationType::iota_connected) { if cv.is_type(CommunicationType::iota_connected) {
if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) {
track_iota_omikron(iota_id, omikron_id).await; user_online_tracker::track_iota_connection(iota_id, omikron_id).await;
if let Ok(users) = sql::get_users_by_iota_id(iota_id).await {
for user in users {
user_online_tracker::track_user_status(
user.0,
ConnectionType::UserOffline,
omikron_id,
)
.await;
}
}
} }
return; return;
} }
if cv.is_type(CommunicationType::iota_disconnected) { if cv.is_type(CommunicationType::iota_disconnected) {
if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) {
untrack_iota(iota_id).await; let iota_offline =
user_online_tracker::untrack_iota_connection(iota_id, omikron_id).await;
if iota_offline {
if let Ok(users) = sql::get_users_by_iota_id(iota_id).await {
let user_ids: Vec<i64> = users.iter().map(|u| u.0).collect();
user_online_tracker::untrack_many_users(&user_ids).await;
}
}
} }
return; return;
} }
@ -242,7 +270,12 @@ impl OmikronConnection {
{ {
for user_id_json in user_ids { for user_id_json in user_ids {
if let Some(user_id) = user_id_json.as_i64() { if let Some(user_id) = user_id_json.as_i64() {
track_user_omikron(user_id, omikron_id).await; user_online_tracker::track_user_status(
user_id,
ConnectionType::Online,
omikron_id,
)
.await;
} }
} }
} }
@ -251,12 +284,286 @@ impl OmikronConnection {
{ {
for iota_id_json in iota_ids { for iota_id_json in iota_ids {
if let Some(iota_id) = iota_id_json.as_i64() { if let Some(iota_id) = iota_id_json.as_i64() {
track_iota_omikron(iota_id, omikron_id).await; user_online_tracker::track_iota_connection(iota_id, omikron_id).await;
} }
} }
} }
return; return;
} }
if cv.is_type(CommunicationType::get_user_data) {
if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() {
if let Some(user_id) = user_id.as_i64() {
if let Ok((
id,
iota_id,
username,
display,
status,
about,
avatar,
sub_level,
sub_end,
public_key,
_,
_,
)) = get_by_user_id(user_id).await
{
let mut response =
CommunicationValue::new(CommunicationType::get_user_data)
.add_data_str(DataTypes::username, username)
.add_data_str(DataTypes::public_key, public_key)
.add_data(DataTypes::user_id, JsonValue::Number(Number::from(id)))
.add_data(
DataTypes::iota_id,
JsonValue::Number(Number::from(iota_id)),
)
.add_data_str(DataTypes::display, display)
.add_data_str(DataTypes::status, status)
.add_data_str(DataTypes::about, about)
.add_data_str(DataTypes::avatar, avatar)
.add_data(
DataTypes::sub_level,
JsonValue::Number(Number::from(sub_level)),
)
.add_data(
DataTypes::sub_end,
JsonValue::Number(Number::from(sub_end)),
);
let user_status = user_online_tracker::get_user_status(id).await;
let iota_connections =
user_online_tracker::get_iota_omikron_connections(iota_id)
.await
.unwrap_or_default();
response = response.add_data(
DataTypes::omikron_connections,
JsonValue::Array(
iota_connections
.into_iter()
.map(|id| JsonValue::Number(Number::from(id)))
.collect(),
),
);
if let Some(user_status) = user_status {
response = response.add_data(
DataTypes::online_status,
JsonValue::String(user_status.connection_type.to_string()),
);
response = response.add_data(
DataTypes::omikron_id,
JsonValue::Number(Number::from(user_status.omikron_id)),
);
} else {
response = response.add_data(
DataTypes::online_status,
JsonValue::String(ConnectionType::IotaOffline.to_string()),
);
}
self.send_message(&response).await;
return;
}
}
}
if let Some(username) = cv.get_data(DataTypes::username).cloned() {
if let Some(username) = username.as_str() {
if let Ok((
id,
iota_id,
username,
display,
status,
about,
avatar,
sub_level,
sub_end,
public_key,
_,
_,
)) = get_by_username(username).await
{
let mut response =
CommunicationValue::new(CommunicationType::get_user_data)
.add_data_str(DataTypes::username, username)
.add_data_str(DataTypes::public_key, public_key)
.add_data(DataTypes::user_id, JsonValue::Number(Number::from(id)))
.add_data(
DataTypes::iota_id,
JsonValue::Number(Number::from(iota_id)),
)
.add_data_str(DataTypes::display, display)
.add_data_str(DataTypes::status, status)
.add_data_str(DataTypes::about, about)
.add_data_str(DataTypes::avatar, avatar)
.add_data(
DataTypes::sub_level,
JsonValue::Number(Number::from(sub_level)),
)
.add_data(
DataTypes::sub_end,
JsonValue::Number(Number::from(sub_end)),
);
let user_status = user_online_tracker::get_user_status(id).await;
let iota_connections =
user_online_tracker::get_iota_omikron_connections(iota_id)
.await
.unwrap_or_default();
if let Some(user_status) = user_status {
response = response.add_data(
DataTypes::online_status,
JsonValue::String(user_status.connection_type.to_string()),
);
response = response.add_data(
DataTypes::omikron_id,
JsonValue::Number(Number::from(user_status.omikron_id)),
);
} else {
response = response.add_data(
DataTypes::online_status,
JsonValue::String(ConnectionType::IotaOffline.to_string()),
);
}
response = response.add_data(
DataTypes::omikron_connections,
JsonValue::Array(
iota_connections
.iter()
.map(|&id| JsonValue::Number(Number::from(id)))
.collect(),
),
);
self.send_message(&response).await;
return;
}
}
}
let response =
CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id());
self.send_message(&response).await;
return;
}
if cv.is_type(CommunicationType::get_iota_data) {
if let Some(iota_id) = cv.get_data(DataTypes::iota_id).cloned() {
if let Some(iota_id) = iota_id.as_i64() {
if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await {
let mut response =
CommunicationValue::new(CommunicationType::get_iota_data)
.add_data_str(DataTypes::public_key, public_key)
.add_data(
DataTypes::iota_id,
JsonValue::Number(Number::from(iota_id)),
);
let iota_connections =
user_online_tracker::get_iota_omikron_connections(iota_id)
.await
.unwrap_or_default();
response = response.add_data(
DataTypes::omikron_connections,
JsonValue::Array(
iota_connections
.iter()
.map(|&id| JsonValue::Number(Number::from(id)))
.collect(),
),
);
self.send_message(&response).await;
return;
}
}
}
if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() {
if let Some(user_id) = user_id.as_i64() {
if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) =
get_by_user_id(user_id).await
{
if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await {
let mut response =
CommunicationValue::new(CommunicationType::get_iota_data)
.add_data_str(DataTypes::public_key, public_key)
.add_data(
DataTypes::user_id,
JsonValue::Number(Number::from(user_id)),
)
.add_data(
DataTypes::iota_id,
JsonValue::Number(Number::from(iota_id)),
);
let iota_connections =
user_online_tracker::get_iota_omikron_connections(iota_id)
.await
.unwrap_or_default();
response = response.add_data(
DataTypes::omikron_connections,
JsonValue::Array(
iota_connections
.iter()
.map(|&id| JsonValue::Number(Number::from(id)))
.collect(),
),
);
self.send_message(&response).await;
return;
}
}
}
}
if let Some(username) = cv.get_data(DataTypes::username).cloned() {
if let Some(username) = username.as_str() {
if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) =
get_by_username(username).await
{
if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await {
let mut response =
CommunicationValue::new(CommunicationType::get_iota_data)
.add_data_str(DataTypes::public_key, public_key)
.add_data(
DataTypes::user_id,
JsonValue::Number(Number::from(user_id)),
)
.add_data_str(DataTypes::username, username.to_string())
.add_data(
DataTypes::iota_id,
JsonValue::Number(Number::from(iota_id)),
);
let iota_connections =
user_online_tracker::get_iota_omikron_connections(iota_id)
.await
.unwrap_or_default();
response = response.add_data(
DataTypes::omikron_connections,
JsonValue::Array(
iota_connections
.iter()
.map(|&id| JsonValue::Number(Number::from(id)))
.collect(),
),
);
self.send_message(&response).await;
return;
}
}
}
}
let response =
CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id());
self.send_message(&response).await;
return;
}
if cv.is_type(CommunicationType::change_user_data) {}
if cv.is_type(CommunicationType::change_iota_data) {}
} }
async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) {
@ -265,14 +572,16 @@ impl OmikronConnection {
} }
pub async fn close(&self) { pub async fn close(&self) {
let mut sender = self.sender.write().await; let mut sender = self.sender.write().await;
if self.is_identified().await {
let _ = sql::set_omikron_active(self.get_omikron_id().await, false);
}
let _ = sender.close().await; let _ = sender.close().await;
} }
pub async fn handle_close(self: Arc<Self>) { pub async fn handle_close(self: Arc<Self>) {
if self.is_identified().await { if self.is_identified().await {
let omikron_id = self.get_user_id().await; let omikron_id = self.get_omikron_id().await;
if omikron_id != 0 { if omikron_id != 0 {
untrack_iota_by_omikron(omikron_id).await; user_online_tracker::untrack_omikron(omikron_id).await;
untrack_user_by_omikron(omikron_id).await;
} }
} }
} }

View file

@ -1,12 +1,11 @@
use crate::log; use crate::log;
use crate::server::api; use crate::server::api;
use crate::server::omikron_connection::OmikronConnection; use crate::server::socket;
use crate::util::file_util::load_file_buf; use crate::util::file_util::load_file_buf;
use base64::Engine; use base64::Engine;
use base64::engine::general_purpose::STANDARD; use base64::engine::general_purpose::STANDARD;
use bytes::Bytes; use bytes::Bytes;
use futures::StreamExt;
use futures_util::TryFutureExt; use futures_util::TryFutureExt;
use http_body_util::BodyExt; use http_body_util::BodyExt;
use http_body_util::Full; use http_body_util::Full;
@ -30,9 +29,8 @@ use std::{future::Future, pin::Pin, time::Duration};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tokio_rustls::TlsAcceptor; use tokio_rustls::TlsAcceptor;
use tokio_tungstenite::{WebSocketStream, accept_async};
use tower::Service; use tower::Service;
use tungstenite::Message;
#[derive(Clone)] #[derive(Clone)]
struct HttpService { struct HttpService {
peer_addr: SocketAddr, peer_addr: SocketAddr,
@ -60,7 +58,7 @@ impl Service<HttpRequest<Incoming>> for HttpService {
let headers = parts.headers.clone(); let headers = parts.headers.clone();
let fut = async move { let fut = async move {
let is_websocket_upgrade = path == "/ws/omikron" let is_websocket_upgrade = path == "/ws"
&& method == Method::GET && method == Method::GET
&& headers && headers
.get("connection") .get("connection")
@ -94,39 +92,7 @@ impl Service<HttpRequest<Incoming>> for HttpService {
let upgrades = upgrade::on(req_for_upgrade); let upgrades = upgrade::on(req_for_upgrade);
log!("Handling WebSocket upgrade"); log!("Handling WebSocket upgrade");
// Spawn upgrade handling to avoid blocking the service call socket::handle(path, upgrades);
tokio::spawn(async move {
match upgrades.await {
Ok(upgraded_stream) => {
log!("Valid WebSocket upgrade");
let raw_stream = TokioIo::new(upgraded_stream);
let ws_stream = WebSocketStream::from_raw_socket(
raw_stream,
tungstenite::protocol::Role::Server,
None,
)
.await;
log!(
"WebSocket handshake successful, handling connection for Omikron"
);
// Split stream for OmikronConnection
let (writer, reader) = ws_stream.split();
// INTEGRATION START
// Erstelle die Connection und starte den Handler
let connection = OmikronConnection::new(writer, reader);
// Handler in separatem Task starten
start_omikron_handler(connection).await;
// INTEGRATION END
}
Err(e) => {
log!("WebSocket upgrade failed after response: {:?}", e);
}
}
});
log!("Handled WebSocket connection initiation"); log!("Handled WebSocket connection initiation");
Ok(response) Ok(response)
@ -192,7 +158,6 @@ async fn run_http_server(port: u16) -> bool {
port port
); );
// Create a broadcast channel for graceful shutdown signal
let (shutdown_tx, _) = broadcast::channel::<()>(1); let (shutdown_tx, _) = broadcast::channel::<()>(1);
tokio::spawn(async move { tokio::spawn(async move {
@ -205,7 +170,6 @@ async fn run_http_server(port: u16) -> bool {
let service = HttpService { peer_addr: addr }; let service = HttpService { peer_addr: addr };
let io = TokioIo::new(stream); let io = TokioIo::new(stream);
// Subscribe to the shutdown signal for this specific connection
let mut rx = shutdown_tx.subscribe(); let mut rx = shutdown_tx.subscribe();
tokio::spawn(async move { tokio::spawn(async move {
@ -216,7 +180,6 @@ async fn run_http_server(port: u16) -> bool {
.serve_connection(io, TowerToHyperService::new(service)) .serve_connection(io, TowerToHyperService::new(service))
.with_upgrades(); .with_upgrades();
// Wait for either the connection to finish naturally OR the shutdown signal
tokio::select! { tokio::select! {
res = conn => { res = conn => {
if let Err(err) = res { if let Err(err) = res {
@ -276,13 +239,11 @@ async fn run_tls_server(port: u16, tls_config: Arc<ServerConfig>) -> bool {
port port
); );
// Create a broadcast channel for graceful shutdown signal
let (shutdown_tx, _) = broadcast::channel::<()>(1); let (shutdown_tx, _) = broadcast::channel::<()>(1);
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {
tokio::select! { tokio::select! {
// Monitor for shutdown signal
_ = async { _ = async {
loop { loop {
tokio::time::sleep(Duration::from_millis(100)).await; tokio::time::sleep(Duration::from_millis(100)).await;
@ -294,7 +255,6 @@ async fn run_tls_server(port: u16, tls_config: Arc<ServerConfig>) -> bool {
break; break;
} }
// Accept new connections
accepted = listener.accept() => { accepted = listener.accept() => {
match accepted { match accepted {
std::result::Result::Ok((stream, addr)) => { std::result::Result::Ok((stream, addr)) => {
@ -317,14 +277,12 @@ async fn run_tls_server(port: u16, tls_config: Arc<ServerConfig>) -> bool {
}; };
let io = TokioIo::new(tls_stream); let io = TokioIo::new(tls_stream);
// Prepare connection future
let conn = http1::Builder::new() let conn = http1::Builder::new()
.preserve_header_case(true) .preserve_header_case(true)
.title_case_headers(true) .title_case_headers(true)
.serve_connection(io, TowerToHyperService::new(service)) .serve_connection(io, TowerToHyperService::new(service))
.with_upgrades(); .with_upgrades();
// Wait for either the connection to finish naturally OR the shutdown signal
tokio::select! { tokio::select! {
res = conn => { res = conn => {
if let Err(err) = res { if let Err(err) = res {
@ -363,7 +321,7 @@ pub async fn start(port: u16) -> bool {
match tls_result { match tls_result {
Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, Ok(Some(tls_config)) => run_tls_server(port, tls_config).await,
Ok(None) => run_http_server(port).await, Ok(_) => run_http_server(port).await,
Err(e) => { Err(e) => {
log!("Fatal error during TLS config load: {}", e); log!("Fatal error during TLS config load: {}", e);
false false
@ -376,7 +334,7 @@ fn calculate_accept_key(key: &str) -> String {
sha1.update(key.as_bytes()); sha1.update(key.as_bytes());
sha1.update(websocket_guid.as_bytes()); sha1.update(websocket_guid.as_bytes());
let result = sha1.finalize(); let result = sha1.finalize();
STANDARD.encode(result) // Base64 encode the result STANDARD.encode(result)
} }
/// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. /// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails.
@ -391,7 +349,7 @@ fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn Error>> {
log!("TLS certificate 'certs/cert.pem' not found."); log!("TLS certificate 'certs/cert.pem' not found.");
return Ok(None); return Ok(None);
} }
Err(e) => return Err(e.into()), // Other IO error Err(e) => return Err(e.into()),
}; };
let key_file_buf = match key_file_res { let key_file_buf = match key_file_res {
@ -400,7 +358,7 @@ fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn Error>> {
log!("TLS key 'certs/cert.key' not found."); log!("TLS key 'certs/cert.key' not found.");
return Ok(None); return Ok(None);
} }
Err(e) => return Err(e.into()), // Other IO error Err(e) => return Err(e.into()),
}; };
// Continue with configuration if both files were found // Continue with configuration if both files were found
@ -411,12 +369,12 @@ fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn Error>> {
// PKCS8 // PKCS8
let mut key_reader = BufReader::new(key_file_buf); let mut key_reader = BufReader::new(key_file_buf);
let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader)
.map(|r| r.map(Into::into)) // Explicit conversion .map(|r| r.map(Into::into))
.collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?; .collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
if key_ders.is_empty() { if key_ders.is_empty() {
// RSA // RSA
key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file key_reader = BufReader::new(load_file_buf("certs", "cert.key")?);
key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader)
.map(|r| r.map(Into::into)) .map(|r| r.map(Into::into))
.collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?; .collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
@ -424,14 +382,14 @@ fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn Error>> {
if key_ders.is_empty() { if key_ders.is_empty() {
// EC // EC
key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file key_reader = BufReader::new(load_file_buf("certs", "cert.key")?);
key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) key_ders = rustls_pemfile::ec_private_keys(&mut key_reader)
.map(|r| r.map(Into::into)) .map(|r| r.map(Into::into))
.collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?; .collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
} }
if key_ders.is_empty() { if key_ders.is_empty() {
return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); return Err("No valid private keys found in key file (Tried PKCS8, RSA and EC).".into());
} }
let config = rustls::ServerConfig::builder() let config = rustls::ServerConfig::builder()
@ -441,44 +399,3 @@ fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn Error>> {
Ok(Some(Arc::new(config))) Ok(Some(Arc::new(config)))
} }
// In deiner Server-Logik, wo OmikronConnection initialisiert wird:
pub async fn start_omikron_handler(connection: Arc<OmikronConnection>) {
loop {
let msg = match {
let mut receiver = connection.receiver.write().await;
receiver.next().await
} {
Some(Ok(msg)) => msg,
Some(Err(e)) => {
log!("WS Error: {}", e);
break;
}
None => break, // Stream ended
};
match msg {
Message::Text(text) => {
let conn_clone = connection.clone();
tokio::spawn(async move {
conn_clone.handle_message(text.to_string()).await;
});
}
Message::Ping(_) => {
let pong_response = crate::data::communication::CommunicationValue::new(
crate::data::communication::CommunicationType::pong,
);
let conn_clone = connection.clone();
tokio::spawn(async move {
conn_clone.send_message(&pong_response).await;
});
}
Message::Close(_) => {
break;
}
// Other message types like Binary, Pong are ignored.
_ => {}
}
}
connection.handle_close().await;
}

View file

@ -1,63 +1,70 @@
use std::sync::Arc; use std::sync::Arc;
use futures::StreamExt; use futures::StreamExt;
use futures::stream::SplitSink; use hyper::upgrade::OnUpgrade;
use futures::stream::SplitStream;
use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use tokio_tungstenite::WebSocketStream;
use tungstenite::Message; use tungstenite::Message;
use crate::log; use crate::log;
use crate::server::omikron_connection::OmikronConnection; use crate::server::omikron_connection::OmikronConnection;
pub fn handle( pub fn handle(path: String, upgrades: OnUpgrade) {
path: String,
writer: SplitSink<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>, Message>,
reader: SplitStream<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>>,
) {
log!("handling");
tokio::spawn(async move { tokio::spawn(async move {
if path.starts_with("/ws/phi/") { match upgrades.await {
} else if path.starts_with("/ws/omikron/") { Ok(upgraded_stream) => {
let community_conn: Arc<OmikronConnection> = log!("Valid WebSocket upgrade");
Arc::from(OmikronConnection::new(writer, reader)); let raw_stream = TokioIo::new(upgraded_stream);
loop {
let msg_result: Option<Result<_, _>> = {
let mut session_lock = community_conn.receiver.write().await;
session_lock.next().await
};
match msg_result { let ws_stream = WebSocketStream::from_raw_socket(
Some(Ok(msg)) => { raw_stream,
if msg.is_text() { tungstenite::protocol::Role::Server,
let text = msg.into_text().unwrap(); None,
community_conn )
.clone() .await;
.handle_message(text.to_string()) log!(
.await; "WebSocket handshake successful, handling connection for {}",
} else if msg.is_ping() { path
let pong_response = crate::data::communication::CommunicationValue::new( );
crate::data::communication::CommunicationType::pong,
); let (writer, reader) = ws_stream.split();
community_conn.send_message(&pong_response).await; if path == "/ws/omikron" {
} else if msg.is_close() { let connection = OmikronConnection::new(writer, reader);
log!("Closing: {}", msg); start_connecteable_handler(connection).await;
community_conn.handle_close().await;
return;
}
}
Some(Err(e)) => {
log!("Closing ERR: {}", e);
community_conn.handle_close().await;
return;
}
None => {
log!("Closed Session me!");
community_conn.handle_close().await;
return;
}
} }
} }
Err(e) => {
log!("WebSocket upgrade failed after response: {:?}", e);
}
} }
}); });
} }
pub async fn start_connecteable_handler(connection: Arc<OmikronConnection>) {
loop {
let msg = match {
let mut receiver = connection.receiver.write().await;
receiver.next().await
} {
Some(Ok(msg)) => msg,
Some(Err(e)) => {
log!("WS Error: {}", e);
break;
}
_ => break,
};
match msg {
Message::Text(text) => {
let conn_clone = connection.clone();
tokio::spawn(async move {
conn_clone.handle_message(text.to_string()).await;
});
}
Message::Close(_) => {
break;
}
_ => {}
}
}
connection.handle_close().await;
}

View file

@ -0,0 +1,38 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectionType {
Online,
UserOffline,
IotaOffline,
Away,
DoNotDisturb,
}
impl ConnectionType {
pub fn to_str(&self) -> &str {
match self {
ConnectionType::Online => "online",
ConnectionType::UserOffline => "user_offline",
ConnectionType::IotaOffline => "iota_offline",
ConnectionType::Away => "away",
ConnectionType::DoNotDisturb => "do_not_disturb",
}
}
pub fn to_string(&self) -> String {
match self {
ConnectionType::Online => "online".to_string(),
ConnectionType::UserOffline => "user_offline".to_string(),
ConnectionType::IotaOffline => "iota_offline".to_string(),
ConnectionType::Away => "away".to_string(),
ConnectionType::DoNotDisturb => "do_not_disturb".to_string(),
}
}
pub fn from_str(s: &str) -> Option<ConnectionType> {
match s.to_lowercase().as_str() {
"online" => Some(ConnectionType::Online),
"user_offline" => Some(ConnectionType::UserOffline),
"iota_offline" => Some(ConnectionType::IotaOffline),
"away" => Some(ConnectionType::Away),
"do_not_disturb" => Some(ConnectionType::DoNotDisturb),
_ => None,
}
}
}

View file

@ -1,3 +1,4 @@
pub mod connection_status;
pub mod iota_omikron_tracker; pub mod iota_omikron_tracker;
pub mod sql; pub mod sql;
pub mod user_online_tracker; pub mod user_online_tracker;

View file

@ -202,11 +202,11 @@ pub async fn get_by_username(
String::from_utf8_lossy(&token).to_string(), String::from_utf8_lossy(&token).to_string(),
)) ))
} }
None => Err(sqlx::Error::RowNotFound), _ => Err(sqlx::Error::RowNotFound),
} }
} }
pub async fn get_by_id( pub async fn get_by_user_id(
id: i64, id: i64,
) -> Result< ) -> Result<
( (
@ -265,10 +265,73 @@ pub async fn get_by_id(
String::from_utf8_lossy(&token).to_string(), String::from_utf8_lossy(&token).to_string(),
)) ))
} }
None => Err(sqlx::Error::RowNotFound), _ => Err(sqlx::Error::RowNotFound),
} }
} }
pub async fn get_users_by_iota_id(
iota_id_param: i64,
) -> Result<
Vec<(
i64,
i64,
String,
String,
String,
String,
String,
i32,
i64,
String,
String,
String,
)>,
sqlx::Error,
> {
let db_lock = SQL_DB.read().await;
let pool = db_lock.as_ref().expect("Database pool is not initialized");
let rows = sqlx::query(
"SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?",
)
.bind(iota_id_param)
.fetch_all(pool)
.await?;
let mut users = Vec::new();
for row in rows {
let id: i64 = row.get("id");
let iota_id: i64 = row.get("iota_id");
let username: String = row.get("username");
let display: Vec<u8> = row.get("display");
let status: Vec<u8> = row.get("status");
let about: Vec<u8> = row.get("about");
let avatar: Vec<u8> = row.get("avatar");
let sub_level: i32 = row.get("sub_level");
let sub_end: i64 = row.get("sub_end");
let public_key: String = row.get("public_key");
let private_key_hash: String = row.get("private_key_hash");
let token: Vec<u8> = row.get("token");
users.push((
id,
iota_id,
username,
String::from_utf8_lossy(&display).to_string(),
String::from_utf8_lossy(&status).to_string(),
String::from_utf8_lossy(&about).to_string(),
String::from_utf8_lossy(&avatar).to_string(),
sub_level,
sub_end,
public_key,
private_key_hash,
String::from_utf8_lossy(&token).to_string(),
));
}
Ok(users)
}
pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> {
let db_lock = SQL_DB.read().await; let db_lock = SQL_DB.read().await;
let pool = db_lock.as_ref().expect("Database pool is not initialized"); let pool = db_lock.as_ref().expect("Database pool is not initialized");
@ -514,7 +577,7 @@ pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error>
String::from_utf8_lossy(&public_key).to_string(), String::from_utf8_lossy(&public_key).to_string(),
String::from_utf8_lossy(&ip_address).to_string(), String::from_utf8_lossy(&ip_address).to_string(),
)), )),
None => Err(sqlx::Error::RowNotFound), _ => Err(sqlx::Error::RowNotFound),
} }
} }
@ -534,7 +597,7 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error>
String::from_utf8_lossy(&public_key).to_string(), String::from_utf8_lossy(&public_key).to_string(),
String::from_utf8_lossy(&ip_address).to_string(), String::from_utf8_lossy(&ip_address).to_string(),
)), )),
None => Err(sqlx::Error::RowNotFound), _ => Err(sqlx::Error::RowNotFound),
} }
} }

View file

@ -1,27 +1,100 @@
use crate::sql;
use crate::sql::connection_status::ConnectionType;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock; use tokio::sync::RwLock;
static USER_OMIKRON_MAP: Lazy<Arc<RwLock<HashMap<i64, i64>>>> = #[derive(Debug, Clone)]
pub struct UserStatus {
pub connection_type: ConnectionType,
pub omikron_id: i64,
}
// IotaID -> Vec<OmikronID>
static IOTA_OMIKRON_CONNECTIONS: Lazy<Arc<RwLock<HashMap<i64, Vec<i64>>>>> =
Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
pub async fn track_user_omikron(user: i64, omikron: i64) { // UserID -> UserStatus
let mut c = USER_OMIKRON_MAP.write().await; static USER_STATUS_MAP: Lazy<Arc<RwLock<HashMap<i64, UserStatus>>>> =
c.insert(user, omikron); Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
pub async fn track_iota_connection(iota_id: i64, omikron_id: i64) {
let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await;
let connections = iota_map.entry(iota_id).or_default();
if !connections.contains(&omikron_id) {
connections.push(omikron_id);
}
} }
pub async fn get_omikron_for_user(user: i64) -> Option<i64> { pub async fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool {
let c = USER_OMIKRON_MAP.read().await; let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await;
c.get(&user).cloned() if let Some(connections) = iota_map.get_mut(&iota_id) {
connections.retain(|&id| id != omikron_id);
if connections.is_empty() {
iota_map.remove(&iota_id);
return true; // Iota is now offline
}
}
false
} }
pub async fn untrack_user(user: i64) { pub async fn get_iota_omikron_connections(iota_id: i64) -> Option<Vec<i64>> {
let mut c = USER_OMIKRON_MAP.write().await; let iota_map = IOTA_OMIKRON_CONNECTIONS.read().await;
c.remove(&user); iota_map.get(&iota_id).cloned()
} }
pub async fn untrack_by_omikron(omikron: i64) { pub async fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) {
let mut c = USER_OMIKRON_MAP.write().await; let mut user_map = USER_STATUS_MAP.write().await;
c.retain(|_, v| *v != omikron); user_map.insert(
user_id,
UserStatus {
connection_type: status,
omikron_id,
},
);
}
pub async fn get_user_status(user_id: i64) -> Option<UserStatus> {
let user_map = USER_STATUS_MAP.read().await;
user_map.get(&user_id).cloned()
}
pub async fn untrack_user(user_id: i64) {
let mut user_map = USER_STATUS_MAP.write().await;
user_map.remove(&user_id);
}
pub async fn untrack_many_users(user_ids: &[i64]) {
let mut user_map = USER_STATUS_MAP.write().await;
for user_id in user_ids {
user_map.remove(user_id);
}
}
pub async fn untrack_omikron(omikron_id: i64) {
let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await;
let mut user_map = USER_STATUS_MAP.write().await;
let mut offline_iotas = Vec::new();
iota_map.retain(|iota_id, connections| {
connections.retain(|id| *id != omikron_id);
if connections.is_empty() {
offline_iotas.push(*iota_id);
false
} else {
true
}
});
user_map.retain(|_, status| status.omikron_id != omikron_id);
for iota_id in offline_iotas {
if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await {
for user in users {
user_map.remove(&user.0);
}
}
}
} }