diff --git a/Cargo.lock b/Cargo.lock index a20fbae..4ce5e8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + [[package]] name = "aes" version = "0.9.1" @@ -29,6 +50,20 @@ dependencies = [ "cpufeatures 0.3.0", ] +[[package]] +name = "aes-gcm" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" +dependencies = [ + "aead 0.6.1", + "aes 0.9.1", + "cipher 0.5.2", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -44,6 +79,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "asn1-rs" version = "0.7.2" @@ -83,6 +133,148 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.5.0", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "blocking", + "futures-lite 2.6.1", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.1", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.91" @@ -91,7 +283,7 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.1", ] [[package]] @@ -160,6 +352,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.1" @@ -185,7 +383,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", - "zeroize", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", ] [[package]] @@ -267,7 +477,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead", + "aead 0.5.2", "chacha20 0.9.1", "cipher 0.4.4", "poly1305", @@ -291,6 +501,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ + "block-buffer 0.12.1", "crypto-common 0.2.2", "inout 0.2.2", ] @@ -343,9 +554,9 @@ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "constant_time_eq" -version = "0.4.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation" @@ -458,6 +669,15 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher 0.5.2", +] + [[package]] name = "ctutils" version = "0.4.2" @@ -477,7 +697,7 @@ dependencies = [ "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", - "fiat-crypto", + "fiat-crypto 0.2.9", "rustc_version", "subtle", "zeroize", @@ -561,6 +781,17 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "digest" version = "0.10.7" @@ -583,7 +814,6 @@ dependencies = [ "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", - "zeroize", ] [[package]] @@ -639,6 +869,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed448-goldilocks" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" +dependencies = [ + "fiat-crypto 0.1.20", + "hex", + "subtle", +] + [[package]] name = "either" version = "1.16.0" @@ -684,6 +925,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "event-listener" version = "5.4.1" @@ -695,6 +942,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + [[package]] name = "fastbloom" version = "0.17.0" @@ -707,12 +964,27 @@ dependencies = [ "siphasher", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -838,6 +1110,34 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.5.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.33" @@ -901,6 +1201,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -910,11 +1222,32 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.1", "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" +dependencies = [ + "polyval", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.4.15" @@ -941,7 +1274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" dependencies = [ "bytes", - "fastrand", + "fastrand 2.5.0", "futures-util", "http", "pin-project-lite", @@ -1028,6 +1361,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1136,9 +1481,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.11.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -1187,7 +1532,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.5", "system-configuration", "tokio", "tower-service", @@ -1326,6 +1671,26 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1408,6 +1773,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "keccak" version = "0.1.6" @@ -1427,6 +1798,15 @@ dependencies = [ "cpufeatures 0.3.0", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1460,7 +1840,7 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags", + "bitflags 2.13.1", "libc", "plain", "redox_syscall 0.9.0", @@ -1476,6 +1856,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -1496,6 +1888,9 @@ name = "log" version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +dependencies = [ + "value-bag", +] [[package]] name = "lru-slab" @@ -1505,11 +1900,12 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "lzma-rust2" -version = "0.16.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" +checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" dependencies = [ - "sha2 0.11.0", + "crc", + "sha2 0.10.9", ] [[package]] @@ -1856,25 +2252,31 @@ dependencies = [ name = "omega" version = "0.1.0" dependencies = [ + "aes-gcm", "ansi_term", + "anyhow", "base64", "bytes", "dashmap", "dotenv", + "hex", + "hkdf 0.12.4", "http", + "json", "mtp", "once_cell", - "rand 0.10.2", + "rand 0.8.7", + "rand_core 0.6.4", "reqwest", "rustls", - "serde", - "serde_json", + "sha2 0.10.9", "sqlx", "strum", "strum_macros", "thiserror 2.0.19", "tokio", "uuid", + "x448", "zip", ] @@ -1927,12 +2329,12 @@ dependencies = [ [[package]] name = "pbkdf2" -version = "0.13.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest 0.11.3", - "hmac 0.13.0", + "digest 0.10.7", + "hmac 0.12.1", ] [[package]] @@ -1966,6 +2368,23 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand 2.5.0", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -2009,6 +2428,36 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -2017,7 +2466,18 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures 0.2.17", "opaque-debug", - "universal-hash", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" +dependencies = [ + "cpubits", + "cpufeatures 0.3.0", + "universal-hash 0.6.1", ] [[package]] @@ -2079,7 +2539,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.5", "thiserror 2.0.19", "tokio", "tracing", @@ -2120,7 +2580,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.5", "tracing", "windows-sys 0.61.2", ] @@ -2134,6 +2594,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -2172,6 +2638,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + [[package]] name = "rand_core" version = "0.6.4" @@ -2217,7 +2689,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.1", ] [[package]] @@ -2226,7 +2698,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ - "bitflags", + "bitflags 2.13.1", ] [[package]] @@ -2325,6 +2797,33 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.42" @@ -2444,7 +2943,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.13.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2494,14 +2993,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.1", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -2546,17 +3045,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha1" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - [[package]] name = "sha2" version = "0.10.9" @@ -2679,6 +3167,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.6.5" @@ -2743,12 +3241,14 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ + "async-io 1.13.0", + "async-std", "base64", "bytes", "crc", "crossbeam-queue", "either", - "event-listener", + "event-listener 5.4.1", "futures-core", "futures-intrusive", "futures-io", @@ -2765,8 +3265,6 @@ dependencies = [ "sha2 0.10.9", "smallvec", "thiserror 2.0.19", - "tokio", - "tokio-stream", "tracing", "url", ] @@ -2790,6 +3288,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ + "async-std", "dotenvy", "either", "heck", @@ -2805,7 +3304,6 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", - "tokio", "url", ] @@ -2817,7 +3315,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags", + "bitflags 2.13.1", "byteorder", "bytes", "crc", @@ -2841,7 +3339,7 @@ dependencies = [ "rand 0.8.7", "rsa", "serde", - "sha1 0.10.7", + "sha1", "sha2 0.10.9", "smallvec", "sqlx-core", @@ -2859,7 +3357,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags", + "bitflags 2.13.1", "byteorder", "crc", "dotenvy", @@ -2931,15 +3429,15 @@ dependencies = [ [[package]] name = "strum" -version = "0.28.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" [[package]] name = "strum_macros" -version = "0.28.0" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", @@ -2966,9 +3464,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.2" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f" dependencies = [ "proc-macro2", "quote", @@ -3001,7 +3499,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags", + "bitflags 2.13.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3053,17 +3551,16 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.1", ] [[package]] name = "time" -version = "0.3.54" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "js-sys", "num-conv", "powerfmt", "serde_core", @@ -3079,9 +3576,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.32" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -3114,9 +3611,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.1" +version = "1.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" dependencies = [ "bytes", "libc", @@ -3124,7 +3621,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.5", "tokio-macros", "windows-sys 0.61.2", ] @@ -3150,17 +3647,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.18" @@ -3195,7 +3681,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags", + "bitflags 2.13.1", "bytes", "futures-util", "http", @@ -3257,12 +3743,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "typed-path" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" - [[package]] name = "typenum" version = "1.20.1" @@ -3306,6 +3786,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3353,6 +3843,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "value-bag" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd4ec1eb1d240636e354a30110a1dfcb37047169a4d9bd6d9d3469df574b5c4" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3365,6 +3861,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.5.0" @@ -3390,6 +3892,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasite" version = "0.1.0" @@ -3704,6 +4215,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "writeable" version = "0.6.3" @@ -3724,7 +4241,7 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "sha2 0.11.0", - "socket2", + "socket2 0.6.5", "thiserror 2.0.19", "time", "tokio", @@ -3758,6 +4275,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "x448" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" +dependencies = [ + "ed448-goldilocks", + "hex", + "rand_core 0.5.1", +] + [[package]] name = "x509-parser" version = "0.18.1" @@ -3812,18 +4340,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", @@ -3906,26 +4434,26 @@ dependencies = [ [[package]] name = "zip" -version = "8.6.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" dependencies = [ - "aes", + "aes 0.8.4", + "arbitrary", "bzip2", "constant_time_eq", "crc32fast", "deflate64", "flate2", - "getrandom 0.4.3", - "hmac 0.13.0", + "getrandom 0.3.4", + "hmac 0.12.1", "indexmap", "lzma-rust2", "memchr", "pbkdf2", "ppmd-rust", - "sha1 0.11.0", + "sha1", "time", - "typed-path", "zeroize", "zopfli", "zstd", diff --git a/Cargo.toml b/Cargo.toml index 3b4f6a3..e601d1a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,27 +10,33 @@ mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ "web-server", ] } +aes-gcm = "*" ansi_term = "0.12.1" +anyhow = "1.0.101" base64 = "0.22.1" bytes = "1" -dashmap = "6.2.1" +dashmap = "6.1.0" dotenv = "0.15.0" +hex = "0.4.3" +hkdf = "0.12.4" http = "1" -once_cell = "1.21.4" -rand = "0.10.2" -reqwest = "0.13.4" -rustls = { version = "0.23.42", default-features = false, features = [ +json = "0.12.4" +once_cell = "1.21.3" +rand = "0.8" +rand_core = { version = "0.6", features = ["getrandom", "std"] } +reqwest = { version = "0.13.2" } +rustls = { version = "0.23.37", default-features = false, features = [ "std", "tls12", "aws-lc-rs", "prefer-post-quantum", ] } -sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio", "migrate"] } -strum = "0.28.0" -strum_macros = "0.28.0" +sha2 = "0.10.9" +sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } +strum = "0.27.2" +strum_macros = "0.27.2" tokio = { version = "*", features = ["full"] } -uuid = { version = "1.24.0", features = ["v4", "v7"] } -zip = "8.6.0" -thiserror = "2.0.19" -serde = { version = "1.0.229", features = ["derive"] } -serde_json = "1.0.151" +uuid = { version = "1.19.0", features = ["v4"] } +x448 = "0.6.0" +zip = "6.0.0" +thiserror = "2.0.18" diff --git a/migrations/001.sql b/migrations/001.sql deleted file mode 100644 index dc3a096..0000000 --- a/migrations/001.sql +++ /dev/null @@ -1,44 +0,0 @@ -CREATE TABLE IF NOT EXISTS users ( - id BIGINT NOT NULL PRIMARY KEY, - iota_id BIGINT NOT NULL, - username VARBINARY(255) NOT NULL, - display VARBINARY(255), - status VARBINARY(255), - about VARBINARY(1000), - avatar BLOB, - sub_level INT NOT NULL DEFAULT 0, - sub_end BIGINT NOT NULL DEFAULT 0, - public_key BLOB NOT NULL, - token BLOB NOT NULL, - UNIQUE KEY uk_users_username (username), - UNIQUE KEY uk_users_iota_id (iota_id) -); - -CREATE TABLE IF NOT EXISTS iotas ( - id BIGINT NOT NULL PRIMARY KEY, - public_key BLOB NOT NULL -); - -CREATE TABLE IF NOT EXISTS omikrons ( - id BIGINT NOT NULL PRIMARY KEY, - public_key BLOB NOT NULL, - location VARBINARY(255) NOT NULL, - ip_address VARBINARY(45) NOT NULL, - port INT NOT NULL -); - -CREATE TABLE IF NOT EXISTS notifications ( - id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, - sender_id BIGINT NOT NULL, - receiver_id BIGINT NOT NULL, - amount BIGINT NOT NULL DEFAULT 1, - UNIQUE KEY uk_notifications_sender_receiver (sender_id, receiver_id), - INDEX idx_notifications_receiver (receiver_id) -); - -CREATE TABLE IF NOT EXISTS short_links ( - short_key VARCHAR(12) CHARACTER SET ascii COLLATE ascii_bin NOT NULL PRIMARY KEY, - long_url TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - INDEX idx_short_links_created_at (created_at) -); diff --git a/src/api/mod.rs b/src/api/mod.rs deleted file mode 100644 index 4c6f2cd..0000000 --- a/src/api/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod response; diff --git a/src/api/response.rs b/src/api/response.rs deleted file mode 100644 index e8489b5..0000000 --- a/src/api/response.rs +++ /dev/null @@ -1,70 +0,0 @@ -use serde::Serialize; -use std::collections::BTreeMap; - -#[derive(Serialize)] -pub struct StatusResponse { - pub status: &'static str, -} - -#[derive(Serialize)] -pub struct OmikronResponse { - pub status: &'static str, - pub id: i64, - pub public_key: String, - pub ip_address: String, - pub port: u16, -} - -#[derive(Serialize)] -pub struct IotaResponse { - pub status: &'static str, - pub iota_id: i64, - pub public_key: String, -} - -#[derive(Serialize)] -pub struct UserResponse { - pub status: &'static str, - pub username: String, - pub public_key: String, - pub user_id: i64, - pub iota_id: i64, - pub sub_level: i32, - pub sub_end: i64, - #[serde(skip_serializing_if = "Option::is_none")] - pub display: Option, - #[serde(rename = "status_message", skip_serializing_if = "Option::is_none")] - pub status_message: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub about: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub avatar: Option, -} - -#[derive(Serialize)] -pub struct UsernameResponse { - pub status: &'static str, - pub username: String, - pub public_key: String, - pub user_id: i64, - pub iota_id: i64, - pub sub_level: i32, - pub sub_end: i64, -} - -#[derive(Serialize)] -pub struct ConnectionsResponse { - pub status: &'static str, - #[serde(flatten)] - pub connections: BTreeMap>>, -} - -#[derive(Serialize)] -pub struct PublicKeyResponse { - pub status: &'static str, - pub public_key: String, -} - -pub fn json(value: &T) -> String { - serde_json::to_string(value).unwrap_or_else(|_| "{\"status\":\"error\"}".to_string()) -} diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 5ba9dc2..0000000 --- a/src/config.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::{env, time::Duration}; - -#[derive(Clone, Debug)] -pub struct RateLimitConfig { - pub window: Duration, - pub general_requests: usize, - pub registration_requests: usize, - pub transport_connections: usize, - pub transport_connections_per_ip: usize, -} - -pub fn cors_origin() -> String { - env::var("CORS_ORIGIN").unwrap_or_else(|_| "https://tensamin.net".to_string()) -} - -impl Default for RateLimitConfig { - fn default() -> Self { - Self { - window: Duration::from_secs(60), - general_requests: 120, - registration_requests: 20, - transport_connections: 512, - transport_connections_per_ip: 32, - } - } -} - -impl RateLimitConfig { - pub fn from_env() -> Self { - let defaults = Self::default(); - Self { - window: env_duration("RATE_LIMIT_WINDOW_SECONDS", defaults.window), - general_requests: env_usize("RATE_LIMIT_GENERAL_REQUESTS", defaults.general_requests), - registration_requests: env_usize( - "RATE_LIMIT_REGISTRATION_REQUESTS", - defaults.registration_requests, - ), - transport_connections: env_usize( - "RATE_LIMIT_TRANSPORT_CONNECTIONS", - defaults.transport_connections, - ), - transport_connections_per_ip: env_usize( - "RATE_LIMIT_TRANSPORT_CONNECTIONS_PER_IP", - defaults.transport_connections_per_ip, - ), - } - } -} - -fn env_usize(name: &str, fallback: usize) -> usize { - env::var(name) - .ok() - .and_then(|value| value.parse::().ok()) - .filter(|value| *value > 0) - .unwrap_or(fallback) -} - -fn env_duration(name: &str, fallback: Duration) -> Duration { - env::var(name) - .ok() - .and_then(|value| value.parse::().ok()) - .filter(|value| *value > 0) - .map(Duration::from_secs) - .unwrap_or(fallback) -} diff --git a/src/db/iota_repo.rs b/src/db/iota_repo.rs deleted file mode 100644 index a5952f1..0000000 --- a/src/db/iota_repo.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{ - db::pool, - error::{OmegaError, Result}, - models::{Iota, IotaId}, -}; -use mtp::crypto::PublicKeyBundle; -use sqlx::Row; - -pub async fn get_iota_by_id(id: IotaId) -> Result { - let row = sqlx::query("SELECT id, public_key FROM iotas WHERE id = ?") - .bind(id.0) - .fetch_optional(&pool().await?) - .await? - .ok_or(OmegaError::NotFound)?; - let key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|error| OmegaError::Validation(error.to_string()))?; - Ok(Iota { - id: row.get::("id").into(), - public_key: key, - }) -} - -pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { - let id = crate::db::user_repo::get_register_id().await?; - let iota_id = IotaId::from(id.0); - register_complete_iota(iota_id, public_key).await?; - Ok(iota_id) -} - -pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) -> Result<()> { - sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") - .bind(id.0) - .bind(public_key.as_bytes()) - .execute(&pool().await?) - .await?; - Ok(()) -} - -pub async fn change_iota_key(id: IotaId, key: PublicKeyBundle) -> Result<()> { - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(key.as_bytes()) - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} -pub async fn delete_iota(id: IotaId) -> Result<()> { - sqlx::query("DELETE FROM iotas WHERE id = ?") - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} diff --git a/src/db/mod.rs b/src/db/mod.rs deleted file mode 100644 index f8afccb..0000000 --- a/src/db/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::error::{OmegaError, Result}; -use once_cell::sync::Lazy; -use sqlx::{MySql, Pool, mysql::MySqlPoolOptions}; -use std::{env, sync::Arc}; -use tokio::sync::RwLock; - -pub mod iota_repo; -pub mod notification_repo; -pub mod omikron_repo; -pub mod short_link_repo; -pub mod user_repo; - -pub type DbPool = Pool; - -static POOL: Lazy>>> = Lazy::new(|| Arc::new(RwLock::new(None))); - -pub async fn pool() -> Result { - POOL.read() - .await - .as_ref() - .cloned() - .ok_or(OmegaError::DatabaseNotInitialized) -} - -pub async fn initialize() -> Result<()> { - let url = - env::var("DB_URL").map_err(|_| OmegaError::Validation("DB_URL is not set".to_string()))?; - let pool = MySqlPoolOptions::new() - .max_connections(200) - .connect(&url) - .await?; - sqlx::migrate!() - .run(&pool) - .await - .map_err(|error| OmegaError::Validation(format!("database migration failed: {error}")))?; - *POOL.write().await = Some(pool); - Ok(()) -} diff --git a/src/db/notification_repo.rs b/src/db/notification_repo.rs deleted file mode 100644 index b317472..0000000 --- a/src/db/notification_repo.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::{ - db::pool, - error::Result, - models::{Notification, UserId}, -}; -use sqlx::Row; - -pub async fn add_notification(sender_id: UserId, receiver_id: UserId) -> Result<()> { - sqlx::query("INSERT INTO notifications (sender_id, receiver_id, amount) VALUES (?, ?, 1) ON DUPLICATE KEY UPDATE amount = amount + 1").bind(sender_id.0).bind(receiver_id.0).execute(&pool().await?).await?; - Ok(()) -} - -pub async fn read_notification(sender_id: UserId, receiver_id: UserId) -> Result<()> { - sqlx::query("DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ?") - .bind(sender_id.0) - .bind(receiver_id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} - -pub async fn get_notifications(receiver_id: UserId) -> Result> { - let rows = sqlx::query( - "SELECT id, sender_id, receiver_id, amount FROM notifications WHERE receiver_id = ?", - ) - .bind(receiver_id.0) - .fetch_all(&pool().await?) - .await?; - Ok(rows - .into_iter() - .map(|row| Notification { - id: row.get("id"), - sender_id: row.get::("sender_id").into(), - receiver_id: row.get::("receiver_id").into(), - amount: row.get("amount"), - }) - .collect()) -} diff --git a/src/db/omikron_repo.rs b/src/db/omikron_repo.rs deleted file mode 100644 index 03e1661..0000000 --- a/src/db/omikron_repo.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{ - db::pool, - error::{OmegaError, Result}, - models::{Omikron, OmikronId}, -}; -use mtp::crypto::PublicKeyBundle; -use sqlx::Row; - -pub async fn get_omikron_by_id(id: OmikronId) -> Result { - let row = - sqlx::query("SELECT id, public_key, location, ip_address, port FROM omikrons WHERE id = ?") - .bind(id.0) - .fetch_optional(&pool().await?) - .await? - .ok_or(OmegaError::NotFound)?; - let key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|error| OmegaError::Validation(error.to_string()))?; - let location = String::from_utf8(row.get("location")) - .map_err(|error| OmegaError::Validation(format!("invalid location UTF-8: {error}")))?; - let ip_address = String::from_utf8(row.get("ip_address")) - .map_err(|error| OmegaError::Validation(format!("invalid IP address UTF-8: {error}")))?; - Ok(Omikron { - id: row.get::("id").into(), - public_key: key, - location, - ip_address, - port: row.get::("port") as u16, - }) -} diff --git a/src/db/short_link_repo.rs b/src/db/short_link_repo.rs deleted file mode 100644 index 24d85ac..0000000 --- a/src/db/short_link_repo.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::{db::pool, error::Result}; - -pub async fn count() -> Result { - let count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM short_links") - .fetch_one(&pool().await?) - .await?; - Ok(count.max(0) as u64) -} - -pub async fn insert(short_key: &str, long_url: &str) -> Result { - let result = sqlx::query("INSERT IGNORE INTO short_links (short_key, long_url) VALUES (?, ?)") - .bind(short_key) - .bind(long_url) - .execute(&pool().await?) - .await?; - Ok(result.rows_affected() == 1) -} - -pub async fn get(short_key: &str) -> Result> { - Ok( - sqlx::query_scalar::<_, String>("SELECT long_url FROM short_links WHERE short_key = ?") - .bind(short_key) - .fetch_optional(&pool().await?) - .await?, - ) -} - -pub async fn delete_expired() -> Result { - let result = sqlx::query( - "DELETE FROM short_links WHERE created_at < CURRENT_TIMESTAMP - INTERVAL 7 DAY", - ) - .execute(&pool().await?) - .await?; - Ok(result.rows_affected()) -} diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs deleted file mode 100644 index ca71dd3..0000000 --- a/src/db/user_repo.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::{ - db::pool, - error::{OmegaError, Result}, - models::{IotaId, User, UserId}, -}; -use mtp::crypto::PublicKeyBundle; -use sqlx::FromRow; - -pub async fn get_register_id() -> Result { - let bytes = *uuid::Uuid::now_v7().as_bytes(); - let id = - i64::from_be_bytes(bytes[8..].try_into().map_err(|_| { - OmegaError::Validation("generated ID has an invalid length".to_string()) - })?) & i64::MAX; - Ok(UserId::from(id.max(1))) -} - -const USER_BY_USERNAME_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE username = ?"; -const USER_BY_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE id = ?"; -const USERS_BY_IOTA_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE iota_id = ?"; - -#[derive(FromRow)] -struct UserRow { - id: i64, - iota_id: i64, - username: Vec, - display: Option>, - status: Option>, - about: Option>, - avatar: Option>, - sub_level: i32, - sub_end: i64, - public_key: Vec, - token: Vec, -} - -impl TryFrom for User { - type Error = sqlx::Error; - - fn try_from(row: UserRow) -> std::result::Result { - let public_key = PublicKeyBundle::from_bytes(&row.public_key) - .map_err(|error| sqlx::Error::Decode(Box::new(error)))?; - let decode = - |value| String::from_utf8(value).map_err(|error| sqlx::Error::Decode(Box::new(error))); - Ok(User { - id: row.id.into(), - iota_id: row.iota_id.into(), - username: decode(row.username)?, - display: row.display.map(decode).transpose()?, - status: row.status.map(decode).transpose()?, - about: row.about.map(decode).transpose()?, - avatar: row.avatar, - sub_level: row.sub_level, - sub_end: row.sub_end, - public_key, - token: decode(row.token)?, - }) - } -} - -pub async fn get_by_username(username: &str) -> Result { - let row = sqlx::query_as::<_, UserRow>(USER_BY_USERNAME_QUERY) - .bind(username) - .fetch_optional(&pool().await?) - .await? - .ok_or(OmegaError::NotFound)?; - row.try_into().map_err(OmegaError::from) -} - -pub async fn get_by_user_id(id: UserId) -> Result { - let row = sqlx::query_as::<_, UserRow>(USER_BY_ID_QUERY) - .bind(id.0) - .fetch_optional(&pool().await?) - .await? - .ok_or(OmegaError::NotFound)?; - row.try_into().map_err(OmegaError::from) -} - -pub async fn get_users_by_iota_id(id: IotaId) -> Result> { - let rows = sqlx::query_as::<_, UserRow>(USERS_BY_IOTA_ID_QUERY) - .bind(id.0) - .fetch_all(&pool().await?) - .await?; - rows.into_iter() - .map(|row| row.try_into().map_err(OmegaError::from)) - .collect() -} - -async fn update( - id: UserId, - query: &'static str, - value: impl Send + sqlx::Encode<'static, sqlx::MySql> + sqlx::Type + 'static, -) -> Result<()> { - sqlx::query(query) - .bind(value) - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} - -pub async fn change_username(id: UserId, value: String) -> Result<()> { - update( - id, - "UPDATE users SET username = ? WHERE id = ?", - value.into_bytes(), - ) - .await -} -pub async fn change_display_name(id: UserId, value: String) -> Result<()> { - update( - id, - "UPDATE users SET display = ? WHERE id = ?", - value.into_bytes(), - ) - .await -} -pub async fn change_avatar(id: UserId, value: String) -> Result<()> { - update( - id, - "UPDATE users SET avatar = ? WHERE id = ?", - value.into_bytes(), - ) - .await -} -pub async fn change_about(id: UserId, value: String) -> Result<()> { - update( - id, - "UPDATE users SET about = ? WHERE id = ?", - value.into_bytes(), - ) - .await -} -pub async fn change_status(id: UserId, value: String) -> Result<()> { - update( - id, - "UPDATE users SET status = ? WHERE id = ?", - value.into_bytes(), - ) - .await -} - -pub async fn change_iota_id(id: UserId, value: IotaId) -> Result<()> { - sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") - .bind(value.0) - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} -pub async fn change_token(id: UserId, value: String) -> Result<()> { - update( - id, - "UPDATE users SET token = ? WHERE id = ?", - value.into_bytes(), - ) - .await -} -pub async fn delete_user(id: UserId) -> Result<()> { - sqlx::query("DELETE FROM users WHERE id = ?") - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} - -pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> { - sqlx::query("UPDATE users SET public_key = ? WHERE id = ?") - .bind(public_key.as_bytes()) - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} - -pub async fn register_complete_user( - id: UserId, - username: String, - public_key: PublicKeyBundle, - iota_id: IotaId, - token: String, -) -> Result<()> { - sqlx::query( - "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", - ) - .bind(id.0) - .bind(username.into_bytes()) - .bind(public_key.as_bytes()) - .bind(iota_id.0) - .bind(token.into_bytes()) - .execute(&pool().await?) - .await?; - Ok(()) -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 9911d25..0000000 --- a/src/error.rs +++ /dev/null @@ -1,57 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum OmegaError { - #[error("database pool is not initialized")] - DatabaseNotInitialized, - #[error("database error: {0}")] - Database(sqlx::Error), - #[error("invalid input: {0}")] - Validation(String), - #[error("resource not found")] - NotFound, - #[error("transport error: {0}")] - Transport(String), - #[error("not connected")] - NotConnected, - #[error("not authenticated")] - NotAuthenticated, - #[error("invalid response")] - InvalidResponse, - #[error("authentication failed")] - AuthenticationFailed, - #[error("send error: {0}")] - SendError(String), - #[error("I/O error: {0}")] - Io(#[from] std::io::Error), -} - -pub type Result = std::result::Result; - -impl From for OmegaError { - fn from(error: sqlx::Error) -> Self { - if matches!(error, sqlx::Error::RowNotFound) { - Self::NotFound - } else { - Self::Database(error) - } - } -} - -impl OmegaError { - pub fn status_code(&self) -> http::StatusCode { - match self { - Self::Validation(_) => http::StatusCode::BAD_REQUEST, - Self::NotFound => http::StatusCode::NOT_FOUND, - Self::DatabaseNotInitialized - | Self::Database(_) - | Self::Transport(_) - | Self::NotConnected - | Self::NotAuthenticated - | Self::InvalidResponse - | Self::AuthenticationFailed - | Self::SendError(_) - | Self::Io(_) => http::StatusCode::INTERNAL_SERVER_ERROR, - } - } -} diff --git a/src/main.rs b/src/main.rs index 251713e..9e35297 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,41 +1,30 @@ -mod api; -mod config; -mod db; -pub mod error; -mod models; mod server; mod sql; mod transport; mod util; -pub use error::{OmegaError, Result}; - -use crate::db::initialize; +use crate::sql::sql::initialize_db; +use crate::sql::sql::print_users; use crate::transport::omikron_connection; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; use dotenv::from_path; -use mtp::crypto::Keyring; use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; +use mtp::crypto::Keyring; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; -use std::time::Duration; -use tokio::time::interval; const KEYRING_PATH: &str = "./omega.mk"; static KEYRING: Lazy = Lazy::new(|| { load_keyring_raw(KEYRING_PATH).unwrap_or_else(|_| { let kr = Keyring::generate(); - if let Err(error) = save_keyring_raw(&kr, KEYRING_PATH) { - eprintln!("Failed to save generated keyring: {error}"); - } - if let Err(error) = save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) { - eprintln!("Failed to save generated public key bundle: {error}"); - } + save_keyring_raw(&kr, KEYRING_PATH).expect("Failed to save generated keyring"); + save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) + .expect("Failed to save generated public key bundle"); eprintln!("Generated new keyring at {}", KEYRING_PATH); kr }) @@ -45,10 +34,7 @@ pub fn get_keyring() -> &'static Keyring { &KEYRING } pub fn load_keyring() -> Keyring { - Keyring::from_bytes(&KEYRING.to_bytes()).unwrap_or_else(|error| { - eprintln!("Failed to clone keyring: {error}"); - Keyring::generate() - }) + Keyring::from_bytes(&KEYRING.to_bytes()).unwrap() } #[tokio::main] @@ -64,7 +50,7 @@ async fn main() { log!("Started"); log!(" .env"); - if let Err(e) = initialize().await { + if let Err(e) = initialize_db().await { log!("[FATAL] Database initialization failed: {}", e); log!( "[FATAL] Please ensure the database is running and the .env file is configured correctly." @@ -73,21 +59,12 @@ async fn main() { } else { log!(" DB"); } - let rate_limit_cleanup = crate::server::middleware::spawn_cleanup_task(); - let short_link_cleanup = tokio::spawn(async { - let mut ticker = interval(Duration::from_secs(24 * 60 * 60)); - loop { - ticker.tick().await; - if let Err(error) = crate::db::short_link_repo::delete_expired().await { - log_err!( - 0, - PrintType::General, - "Short-link cleanup failed: {}", - error - ); - } - } - }); + if let Err(e) = print_users().await { + log!("[ERROR] Failed to print users: {}", e); + } else { + log!(" Users"); + } + let port: u16 = env::var("PORT") .ok() .and_then(|s| s.parse().ok()) @@ -103,6 +80,4 @@ async fn main() { log!("Shutting down on signal..."); } } - rate_limit_cleanup.abort(); - short_link_cleanup.abort(); } diff --git a/src/models/ids.rs b/src/models/ids.rs deleted file mode 100644 index 7eda84f..0000000 --- a/src/models/ids.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::fmt::{Display, Formatter}; - -macro_rules! id_type { - ($name:ident) => { - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)] - pub struct $name(pub i64); - - impl From for $name { - fn from(value: i64) -> Self { - Self(value) - } - } - - impl From<$name> for i64 { - fn from(value: $name) -> Self { - value.0 - } - } - - impl Display for $name { - fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { - self.0.fmt(formatter) - } - } - }; -} - -id_type!(UserId); -id_type!(IotaId); -id_type!(OmikronId); diff --git a/src/models/iota.rs b/src/models/iota.rs deleted file mode 100644 index 3a93bcb..0000000 --- a/src/models/iota.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::IotaId; -use mtp::crypto::PublicKeyBundle; - -#[derive(Clone, Debug, serde::Serialize)] -pub struct Iota { - pub id: IotaId, - #[serde(serialize_with = "crate::models::serialize_public_key")] - pub public_key: PublicKeyBundle, -} diff --git a/src/models/mod.rs b/src/models/mod.rs deleted file mode 100644 index 291729d..0000000 --- a/src/models/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -mod ids; -mod iota; -mod notification; -mod omikron; -mod user; - -fn serialize_public_key( - key: &mtp::crypto::PublicKeyBundle, - serializer: S, -) -> std::result::Result -where - S: serde::Serializer, -{ - serializer.serialize_str(&key.to_base64()) -} - -pub use ids::{IotaId, OmikronId, UserId}; -pub use iota::Iota; -pub use notification::Notification; -pub use omikron::Omikron; -pub use user::User; diff --git a/src/models/notification.rs b/src/models/notification.rs deleted file mode 100644 index 8dee06e..0000000 --- a/src/models/notification.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::UserId; - -#[derive(Clone, Debug, serde::Serialize)] -pub struct Notification { - pub id: i64, - pub sender_id: UserId, - pub receiver_id: UserId, - pub amount: i64, -} diff --git a/src/models/omikron.rs b/src/models/omikron.rs deleted file mode 100644 index 3b47d44..0000000 --- a/src/models/omikron.rs +++ /dev/null @@ -1,12 +0,0 @@ -use super::OmikronId; -use mtp::crypto::PublicKeyBundle; - -#[derive(Clone, Debug, serde::Serialize)] -pub struct Omikron { - pub id: OmikronId, - #[serde(serialize_with = "crate::models::serialize_public_key")] - pub public_key: PublicKeyBundle, - pub location: String, - pub ip_address: String, - pub port: u16, -} diff --git a/src/models/user.rs b/src/models/user.rs deleted file mode 100644 index 0dda84c..0000000 --- a/src/models/user.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::{IotaId, UserId}; -use mtp::crypto::PublicKeyBundle; - -#[derive(Clone, Debug, serde::Serialize)] -pub struct User { - pub id: UserId, - pub iota_id: IotaId, - pub username: String, - pub display: Option, - pub status: Option, - pub about: Option, - pub avatar: Option>, - pub sub_level: i32, - pub sub_end: i64, - #[serde(serialize_with = "crate::models::serialize_public_key")] - pub public_key: PublicKeyBundle, - #[serde(skip_serializing)] - pub token: String, -} diff --git a/src/server/api.rs b/src/server/api.rs index 6dc01aa..6471398 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,240 +1,356 @@ -use crate::api::response::{ - ConnectionsResponse, IotaResponse, OmikronResponse, PublicKeyResponse, StatusResponse, - UserResponse, UsernameResponse, json, -}; -use crate::db::{ - iota_repo::get_iota_by_id, - omikron_repo::get_omikron_by_id, - user_repo::{get_by_user_id, get_by_username}, -}; -use crate::error::{OmegaError, Result}; use crate::load_keyring; -use crate::models::UserId; -use crate::server::{ - middleware, - validation::{parse_positive_id, validate_non_empty}, -}; +use crate::sql::sql; +use crate::sql::sql::{get_by_user_id, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; use base64::Engine as _; use bytes::Bytes; use http::{Method, StatusCode}; +use json::JsonValue; use mtp::webserver::{Http3Request, Http3Response, RouteParams}; -use std::collections::BTreeMap; - -fn error_body(error: &OmegaError) -> String { - json(&StatusResponse { - status: match error { - OmegaError::Validation(_) => "error_bad_request", - OmegaError::NotFound => "error_not_found", - _ => "error", - }, - }) -} - -fn user_response(user: crate::models::User) -> UserResponse { - UserResponse { - status: "success", - username: user.username, - public_key: user.public_key.to_base64(), - user_id: user.id.0, - iota_id: user.iota_id.0, - sub_level: user.sub_level, - sub_end: user.sub_end, - display: user.display, - status_message: user.status, - about: user.about, - avatar: user - .avatar - .map(|value| base64::engine::general_purpose::STANDARD.encode(value)), - } -} - -async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { - match path_parts { - ["api", "get", "omikron"] => { - let connection = get_random_omikron() - .await - .map_err(|_| OmegaError::NotFound)?; - let id = connection - .get_omikron_id() - .await - .ok_or(OmegaError::NotFound)?; - let omikron = get_omikron_by_id(id.into()).await?; - Ok(( - StatusCode::OK, - json(&OmikronResponse { - status: "success", - id, - public_key: omikron.public_key.to_base64(), - ip_address: omikron.ip_address, - port: omikron.port, - }), - )) - } - ["api", "get", "omikron", id] => { - let id = parse_positive_id(id)?; - let omikron = match get_omikron_by_id(id.into()).await { - Ok(value) => value, - Err(_) => { - let fallback_id = - if let Some(fallback_id) = get_iota_primary_omikron_connection(id) { - fallback_id - } else { - let user = get_by_user_id(UserId::from(id)).await?; - get_iota_primary_omikron_connection(user.iota_id.0) - .ok_or(OmegaError::NotFound)? - }; - get_omikron_by_id(fallback_id.into()).await? - } - }; - Ok(( - StatusCode::OK, - json(&OmikronResponse { - status: "success", - id: omikron.id.0, - public_key: omikron.public_key.to_base64(), - ip_address: omikron.ip_address, - port: omikron.port, - }), - )) - } - ["api", "get", "connections"] => { - let connections = get_all_connections() - .await - .map_err(|_| OmegaError::Transport("failed to load connections".to_string()))?; - let connections = connections - .into_iter() - .map(|(omikron_id, iotas)| { - let iotas = iotas - .into_iter() - .map(|(iota_id, users)| { - ( - iota_id.to_string(), - users.into_iter().map(i64::from).collect(), - ) - }) - .collect(); - (omikron_id.to_string(), iotas) - }) - .collect::>(); - Ok(( - StatusCode::OK, - json(&ConnectionsResponse { - status: "success", - connections, - }), - )) - } - ["api", "get", "iota", id] => { - let id = parse_positive_id(id)?; - let iota = get_iota_by_id(id.into()).await?; - Ok(( - StatusCode::OK, - json(&IotaResponse { - status: "success", - iota_id: iota.id.0, - public_key: iota.public_key.to_base64(), - }), - )) - } - ["api", "get", "id", username] => { - validate_non_empty(username, "username", 15)?; - let user = get_by_username(username).await?; - Ok(( - StatusCode::OK, - json(&UsernameResponse { - status: "success", - username: user.username, - public_key: user.public_key.to_base64(), - user_id: user.id.0, - iota_id: user.iota_id.0, - sub_level: user.sub_level, - sub_end: user.sub_end, - }), - )) - } - ["api", "get", "public_key"] => { - let public_key = base64::engine::general_purpose::STANDARD - .encode(load_keyring().public_key_bundle().as_bytes()); - Ok(( - StatusCode::OK, - json(&PublicKeyResponse { - status: "success", - public_key, - }), - )) - } - ["api", "get", "user", id] => { - let id = parse_positive_id(id)?; - let user = get_by_user_id(UserId::from(id)).await?; - Ok((StatusCode::OK, json(&user_response(user)))) - } - _ => Ok(( - StatusCode::INTERNAL_SERVER_ERROR, - json(&StatusResponse { status: "error" }), - )), - } -} pub async fn handle(request: Http3Request, response: Http3Response) -> Http3Response { let method = request.method; let path = request.uri.path().to_string(); - if method != Method::OPTIONS && !middleware::allow(request.remote_addr.ip(), &path) { - return response - .status(StatusCode::TOO_MANY_REQUESTS) - .header("access-control-allow-origin", &crate::config::cors_origin()) - .body(json(&StatusResponse { - status: "error_rate_limited", - })); - } + let body_string = request + .body + .map(|body| String::from_utf8_lossy(&body).to_string()); + if method == Method::OPTIONS { return response .status(StatusCode::OK) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", "*") .header("access-control-allow-methods", "GET, POST, OPTIONS") .header("access-control-allow-headers", "*"); } - let path_parts: Vec<&str> = path.split('/').filter(|part| !part.is_empty()).collect(); - if let ["api", "download", "iota_frontend"] = path_parts.as_slice() { - let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); - return match std::fs::read(file_path) { - Ok(bytes) => response - .status(StatusCode::OK) - .header("access-control-allow-origin", &crate::config::cors_origin()) - .header("content-type", "application/zip") - .header( - "content-disposition", - "attachment; filename=\"iota_frontend.zip\"", - ) - .body(Bytes::from(bytes)), - Err(_) => response - .status(StatusCode::NOT_FOUND) - .header("access-control-allow-origin", &crate::config::cors_origin()) - .body(json(&StatusResponse { - status: "error_not_found", - })), - }; - } - if let ["direct", short @ ..] = path_parts.as_slice() { - let short = short.join(""); - let location = crate::server::short_link::get_short_link(&short) - .await - .unwrap_or_else(|_| "https://tensamin.net".to_string()); - return response - .status(StatusCode::TEMPORARY_REDIRECT) - .header("location", &location); - } - let (status, body) = route(&path_parts) - .await - .unwrap_or_else(|error| (error.status_code(), error_body(&error))); + + let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); + + let _body: Option = if let Some(ref bs) = body_string { + if let Ok(body_json) = json::parse(bs) { + Some(body_json) + } else { + None + } + } else { + None + }; + + let (status, body_text) = match path_parts.as_slice() { + // ================================================== + // DOWNLOAD IOTA FRONTEND + // ================================================== + ["api", "download", "iota_frontend"] => { + let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); + + match std::fs::read(file_path) { + Ok(file_bytes) => { + return response + .status(StatusCode::OK) + .header("access-control-allow-origin", "*") + .header("content-type", "application/zip") + .header( + "content-disposition", + "attachment; filename=\"iota_frontend.zip\"", + ) + .body(Bytes::from(file_bytes)); + } + Err(_) => { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + return response + .status(StatusCode::NOT_FOUND) + .header("access-control-allow-origin", "*") + .body(res.dump()); + } + } + } + + // ================================================== + // GET RANDOM OMIKRON + // ================================================== + ["api", "get", "omikron"] => { + if let Ok(omikron_conn) = get_random_omikron().await { + if let Some(id) = omikron_conn.get_omikron_id().await { + if let Ok((public_key, ip_address, port)) = sql::get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.to_base64().into(); + res["ip_address"] = ip_address.into(); + res["port"] = port.into(); + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } + + // ================================================== + // GET OMIKRON BY ID + // ================================================== + ["api", "get", "omikron", id] => { + let id = id.parse::().unwrap_or(0); + + if id == 0 { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok((public_key, ip_address, port)) = get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.to_base64().into(); + res["ip_address"] = ip_address.into(); + res["port"] = port.into(); + (StatusCode::OK, res.dump()) + } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { + if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.to_base64().into(); + res["ip_address"] = ip_address.into(); + res["port"] = port.into(); + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await + { + if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { + if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await + { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.to_base64().into(); + res["ip_address"] = ip_address.into(); + res["port"] = port.into(); + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } + + ["api", "get", "connections"] => { + if let Ok(connections) = get_all_connections().await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + + for (omikron_id, iota_map) in connections { + let mut omikron_obj = JsonValue::new_object(); + for (iota_id, user_ids) in iota_map { + let mut user_arr = JsonValue::new_array(); + for user_id in user_ids { + let _ = user_arr.push(user_id); + } + omikron_obj[&iota_id.to_string()] = user_arr; + } + res[&omikron_id.to_string()] = omikron_obj; + } + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + } + + // ================================================== + // GET IOTA BY ID + // ================================================== + ["api", "get", "iota", id] => { + let id: i64 = id.parse().unwrap_or(0); + + if id == 0 { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok((id, public_key)) = get_iota_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["iota_id"] = id.into(); + res["public_key"] = public_key.to_base64().into(); + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } + + // ================================================== + // GET ID BY USERNAME + // ================================================== + ["api", "get", "id", username] => { + if username.is_empty() { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok(( + id, + iota_id, + username, + _, + _, + _, + _, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.to_base64().into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) + } + } + + // ================================================== + // GET SERVER PUBLIC KEY + // ================================================== + ["api", "get", "public_key"] => { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + let bundle = load_keyring().public_key_bundle(); + res["public_key"] = base64::engine::general_purpose::STANDARD + .encode(bundle.as_bytes()) + .into(); + (StatusCode::OK, res.dump()) + } + + // ================================================== + // GET USER BY ID + // ================================================== + ["api", "get", "user", id] => { + let id: i64 = id.parse().unwrap_or(0); + + if id == 0 { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok(( + id, + iota_id, + username, + display, + status_msg, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.to_base64().into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + + if let Some(display) = display { + res["display"] = display.into(); + } + if let Some(status_msg) = status_msg { + res["status_message"] = status_msg.into(); + } + if let Some(about) = about { + res["about"] = about.into(); + } + if let Some(avatar) = avatar { + res["avatar"] = base64::engine::general_purpose::STANDARD + .encode(avatar) + .into(); + } + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) + } + } + + // ================================================== + // DIRECT - SHORT LINK RESOLUTION + // ================================================== + ["direct", short @ ..] => { + let short_str = short.join("/"); + let short = short_str.replace("/", ""); + if let Ok(long) = crate::server::short_link::get_short_link(&short).await { + return response + .status(StatusCode::TEMPORARY_REDIRECT) + .header("location", &long); + } else { + return response + .status(StatusCode::TEMPORARY_REDIRECT) + .header("location", "https://tensamin.net"); + } + } + + // ================================================== + // DEFAULT + // ================================================== + _ => { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + }; + response .status(status) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", "*") .header("access-control-allow-headers", "*") .header("access-control-allow-methods", "GET, POST, OPTIONS") - .body(body) + .body(body_text) } pub async fn handle_pattern( diff --git a/src/server/middleware.rs b/src/server/middleware.rs deleted file mode 100644 index 5f373ec..0000000 --- a/src/server/middleware.rs +++ /dev/null @@ -1,84 +0,0 @@ -use dashmap::DashMap; -use once_cell::sync::Lazy; -use std::net::IpAddr; -use std::{collections::VecDeque, time::Instant}; -use tokio::time::interval; - -static REQUESTS: Lazy>> = Lazy::new(DashMap::new); -static CONFIG: Lazy = - Lazy::new(crate::config::RateLimitConfig::from_env); -const MAX_TRACKED_CLIENT_BUCKETS: usize = 100_000; - -pub fn allow(remote_addr: IpAddr, path: &str) -> bool { - let key = if path.contains("register") { - "registration" - } else { - "general" - }; - let map_key = (remote_addr, key.to_string()); - if REQUESTS.len() >= MAX_TRACKED_CLIENT_BUCKETS { - cleanup_expired(); - if REQUESTS.len() >= MAX_TRACKED_CLIENT_BUCKETS && !REQUESTS.contains_key(&map_key) { - return false; - } - } - let limit = if key == "registration" { - CONFIG.registration_requests - } else { - CONFIG.general_requests - }; - let now = Instant::now(); - let mut entries = REQUESTS.entry(map_key).or_default(); - while entries - .front() - .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) - { - entries.pop_front(); - } - if entries.len() >= limit { - return false; - } - entries.push_back(now); - true -} - -pub fn spawn_cleanup_task() -> tokio::task::JoinHandle<()> { - tokio::spawn(async { - let mut ticker = interval(CONFIG.window); - loop { - ticker.tick().await; - cleanup_expired(); - } - }) -} - -fn cleanup_expired() { - let now = Instant::now(); - REQUESTS.retain(|_, entries| { - while entries - .front() - .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) - { - entries.pop_front(); - } - !entries.is_empty() - }); -} - -#[cfg(test)] -mod tests { - use super::allow; - use std::net::{IpAddr, Ipv4Addr}; - - #[test] - fn tracks_clients_independently() { - let first: IpAddr = "192.0.2.10" - .parse() - .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); - let second: IpAddr = "192.0.2.11" - .parse() - .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); - assert!(allow(first, "/api/get/user/1")); - assert!(allow(second, "/api/get/user/1")); - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index 2d01c5b..59e9769 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,4 @@ pub mod api; pub mod index; -pub mod middleware; pub mod server; pub mod short_link; -pub mod validation; diff --git a/src/server/short_link.rs b/src/server/short_link.rs index dd24ebf..b3f91bd 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -1,27 +1,37 @@ -use crate::db::short_link_repo; -use rand::RngExt; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::{Rng, thread_rng}; + +static LINKS: Lazy> = Lazy::new(DashMap::new); const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ1234567890"; pub async fn add_short_link(long: &str) -> Result { + let raw = generate_unique_short_link().await; + LINKS.insert(raw.clone(), long.to_string()); + + Ok(format!( + "hmtps://omega.tensamin.net/direct/{}", + format_with_dashes(&raw) + )) +} + +async fn generate_unique_short_link() -> String { loop { - let raw = generate_short_link().await; - if short_link_repo::insert(&raw, long).await.map_err(|_| ())? { - return Ok(format!( - "https://omega.tensamin.net/direct/{}", - format_with_dashes(&raw) - )); + let short = generate_short_link().await; + if !LINKS.contains_key(&short) { + return short; } } } pub async fn generate_short_link() -> String { - let len = short_length().await; + let len = short_length(); - let mut rng = rand::rng(); + let mut rng = thread_rng(); (0..len) .map(|_| { - let idx = rng.random_range(0..CHARSET.len()); + let idx = rng.gen_range(0..CHARSET.len()); CHARSET[idx] as char }) .collect() @@ -36,17 +46,17 @@ pub async fn get_short_link(short: &str) -> Result { let frag = short.replace(key, ""); let normalized = normalize_short(&key); - let target = short_link_repo::get(&normalized) - .await - .map_err(|_| ())? - .ok_or(())?; - Ok(format!("{}{}", target, frag)) + if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { + Ok(format!("{}{}", t, frag)) + } else { + Err(()) + } } /* ---------------- helpers ---------------- */ -async fn short_length() -> usize { - let count = short_link_repo::count().await.unwrap_or(0); +fn short_length() -> usize { + let count = LINKS.len(); match count { 0..=1_999 => 4, diff --git a/src/server/validation.rs b/src/server/validation.rs deleted file mode 100644 index b80799c..0000000 --- a/src/server/validation.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::error::{OmegaError, Result}; - -pub fn parse_positive_id(value: &str) -> Result { - let id = value - .parse::() - .map_err(|_| OmegaError::Validation("ID must be a positive integer".to_string()))?; - if id <= 0 { - return Err(OmegaError::Validation( - "ID must be a positive integer".to_string(), - )); - } - Ok(id) -} - -pub fn validate_non_empty(value: &str, field: &str, max_len: usize) -> Result<()> { - let length = value.chars().count(); - if length == 0 || length > max_len { - return Err(OmegaError::Validation(format!( - "{field} must contain 1 to {max_len} characters" - ))); - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::{parse_positive_id, validate_non_empty}; - - #[test] - fn rejects_invalid_ids() { - assert!(parse_positive_id("bad").is_err()); - assert!(parse_positive_id("0").is_err()); - assert_eq!(parse_positive_id("7").expect("valid test ID"), 7); - } - - #[test] - fn enforces_string_bounds() { - assert!(validate_non_empty("", "username", 15).is_err()); - assert!(validate_non_empty("abcdefghijklmnop", "username", 15).is_err()); - assert!(validate_non_empty("alice", "username", 15).is_ok()); - } -} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index bec658c..91ba841 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,2 +1,3 @@ pub mod connection_status; +pub mod sql; pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs new file mode 100644 index 0000000..91cd5ba --- /dev/null +++ b/src/sql/sql.rs @@ -0,0 +1,778 @@ +use crate::log; +use mtp::crypto::PublicKeyBundle; +use once_cell::sync::Lazy; +use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::{ + env, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; +use tokio::sync::RwLock; + +/* +use crate::sql::{ + iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, + sql::{ + 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_register_id, register_complete_iota, register_complete_user, + }, +}; + */ + +static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); + +pub async fn connect() -> Result, sqlx::Error> { + let url = env::var("DB_URL").expect("DB_URL is not set"); + + MySqlPoolOptions::new() + .max_connections(200) + .connect(&url) + .await +} +// Omega +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +pub async fn initialize_db() -> Result<(), sqlx::Error> { + let pool = connect().await?; + let mut db_lock = SQL_DB.write().await; + // create tables + // with indexes + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + users ( + id BIGINT NOT NULL PRIMARY KEY, + username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, + display VARCHAR(15) COLLATE utf8mb4_bin, + status VARCHAR(15) COLLATE utf8mb4_bin, + about VARCHAR(200) COLLATE utf8mb4_bin, + avatar MEDIUMBLOB, + sub_level INT(11) NOT NULL DEFAULT 0, + sub_end BIGINT(20) NOT NULL DEFAULT 0, + public_key BLOB NOT NULL, + private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', + iota_id BIGINT NOT NULL, + token VARCHAR(256) NOT NULL UNIQUE COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + iotas ( + id BIGINT NOT NULL PRIMARY KEY, + public_key BLOB NOT NULL + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + omikrons ( + id BIGINT NOT NULL PRIMARY KEY, + public_key BLOB NOT NULL, + location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + port INT(11) NOT NULL DEFAULT 959 + )", + ) + .execute(&pool) + .await; + // Retrofits `port` onto omikrons tables created before this column existed; + // `CREATE TABLE IF NOT EXISTS` above is a no-op against an already-existing table. + let _ = sqlx::query( + "ALTER TABLE omikrons ADD COLUMN IF NOT EXISTS port INT(11) NOT NULL DEFAULT 959", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + notifications ( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + sender_id BIGINT NOT NULL, + receiver_id BIGINT NOT NULL, + amount BIGINT NOT NULL DEFAULT 0 + )", + ) + .execute(&pool) + .await; + *db_lock = Some(pool); + Ok(()) +} + +// ========================================================================================== +// REGISTER +// ========================================================================================== +pub static CURRENT_MILLI_USED: Lazy> = Lazy::new(|| Arc::new(AtomicU64::new(0))); +pub static CURRENT_REGISTER_PROCESS: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); + +pub async fn get_register_id() -> u64 { + let mut current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + loop { + let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst); + + if current_locked < current_time { + let result = CURRENT_MILLI_USED.compare_exchange( + current_locked, // expected value + current_time, // new value + Ordering::SeqCst, // acquire/release ordering + Ordering::SeqCst, // failure ordering + ); + + match result { + Ok(_) => { + CURRENT_REGISTER_PROCESS.write().await.push(current_time); + return current_time; + } + Err(_) => { + continue; + } + } + } else { + current_time = current_locked + 1; + } + } +} + +// ========================================================================================== +// USERS +// ========================================================================================== + +pub async fn get_by_username( + username: &str, +) -> Result< + ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + PublicKeyBundle, + String, + String, + ), + sqlx::Error, +> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", + ) + .bind(username) + .fetch_optional(&pool) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: Vec = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + let private_key_hash: Vec = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + String::from_utf8_lossy(&username).to_string(), + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, + sub_level, + sub_end, + public_key, + String::from_utf8_lossy(&private_key_hash).to_string(), + String::from_utf8_lossy(&token).to_string(), + )) + } + _ => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get_by_user_id( + id: i64, +) -> Result< + ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + PublicKeyBundle, + String, + String, + ), + sqlx::Error, +> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", + ) + .bind(id) + .fetch_optional(&pool) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: Vec = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + let private_key_hash: Vec = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + String::from_utf8_lossy(&username).to_string(), + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, + sub_level, + sub_end, + public_key, + String::from_utf8_lossy(&private_key_hash).to_string(), + String::from_utf8_lossy(&token).to_string(), + )) + } + _ => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get_users_by_iota_id( + iota_id_param: i64, +) -> Result< + Vec<( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + PublicKeyBundle, + String, + String, + )>, + sqlx::Error, +> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool 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: Vec = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + let private_key_hash: Vec = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + users.push(( + id, + iota_id, + String::from_utf8_lossy(&username).to_string(), + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, + sub_level, + sub_end, + public_key, + String::from_utf8_lossy(&private_key_hash).to_string(), + String::from_utf8_lossy(&token).to_string(), + )); + } + + Ok(users) +} + +pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET username = ? WHERE id = ?") + .bind(new_username.as_bytes().to_vec()) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET display = ? WHERE id = ?") + .bind(new_display.as_bytes().to_vec()) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") + .bind(new_avatar.as_bytes().to_vec()) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET about = ? WHERE id = ?") + .bind(new_about.as_bytes().to_vec()) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET status = ? WHERE id = ?") + .bind(new_status.as_bytes().to_vec()) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("DELETE FROM users WHERE id = ?") + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") + .bind(new_iota_id) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_keys( + id: i64, + new_public_key: PublicKeyBundle, + new_private_key_hash: String, +) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") + .bind(new_public_key.as_bytes()) + .bind(new_private_key_hash.as_bytes().to_vec()) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET token = ? WHERE id = ?") + .bind(new_token.as_bytes().to_vec()) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn register_complete_user( + id: i64, + username: String, + public_key: PublicKeyBundle, + iota_id: i64, + token: String, +) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query( + "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", + ) + .bind(id) + .bind(username.as_bytes().to_vec()) + .bind(public_key.as_bytes()) + .bind(iota_id) + .bind(token.as_bytes().to_vec()) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn print_users() -> Result<(), Box> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + log!("Printing users..."); + for row in sqlx::query( + "SELECT id, iota_id, username, display, status, about, sub_level, sub_end, public_key, private_key_hash, token FROM users", + ) + .fetch_all(&pool) + .await? + .iter() + { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: Vec = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + + log!( + "User: {:?}", + ( + id, + iota_id, + String::from_utf8_lossy(&username).to_string(), + display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), + status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), + about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), + sub_level, + sub_end + ) + ); + } + + Ok(()) +} + +// ========================================================================================== +// IOTA +// ========================================================================================== +pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { + let new_id = get_register_id().await as i64; + register_complete_iota(new_id, public_key).await?; + Ok(new_id) +} + +pub async fn register_complete_iota( + id: i64, + public_key: PublicKeyBundle, +) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") + .bind(id) + .bind(public_key.as_bytes()) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn get_iota_by_id(id: i64) -> Result<(i64, PublicKeyBundle), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let result = + sqlx::query_as::<_, (i64, Vec)>("SELECT id, public_key FROM iotas WHERE id = ?") + .bind(id) + .fetch_optional(&pool) + .await; + + match result { + Ok(optional_row) => match optional_row { + Some((id_i64, public_key)) => { + let bundle = PublicKeyBundle::from_bytes(&public_key) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + Ok((id_i64, bundle)) + } + _ => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } +} + +pub async fn change_iota_key(id: i64, new_key: PublicKeyBundle) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") + .bind(new_key.as_bytes()) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("DELETE FROM iotas WHERE id = ?") + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +// ========================================================================================== +// OMIKRONS +// ========================================================================================== + +pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String, u16), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let row = sqlx::query_as::<_, (Vec, Vec, i32)>( + "SELECT public_key, ip_address, port FROM omikrons WHERE id = ?", + ) + .bind(id) + .fetch_optional(&pool) + .await?; + + match row { + Some((public_key, ip_address, port)) => { + let bundle = PublicKeyBundle::from_bytes(&public_key) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + Ok(( + bundle, + String::from_utf8_lossy(&ip_address).to_string(), + port as u16, + )) + } + _ => Err(sqlx::Error::RowNotFound), + } +} + +// ========================================================================================== +// PHI +// ========================================================================================== + +pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + INSERT INTO notifications (sender_id, receiver_id, amount) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1 + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64)>( + r#" + SELECT sender_id, amount FROM notifications WHERE receiver_id = ? + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await +} diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index a0c2cd8..288fcdc 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,5 +1,4 @@ -use crate::db::user_repo; -use crate::models::IotaId; +use crate::sql; use crate::sql::connection_status::UserStatus; use dashmap::DashMap; use once_cell::sync::Lazy; @@ -34,30 +33,27 @@ pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { } pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - let mut replacement = None; - let mut connections_empty = false; - if let Some(mut entry) = IOTA_OMIKRON_CONNECTIONS.get_mut(&iota_id) { - entry.retain(|&id| id != omikron_id); - connections_empty = entry.is_empty(); - replacement = entry.first().copied(); + let connections_empty = if let Some(r) = IOTA_OMIKRON_CONNECTIONS.get(&iota_id) { + let mut vec = r.value().clone(); + vec.retain(|&id| id != omikron_id); + let empty = vec.is_empty(); + drop(r); + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, vec); + empty + } else { + false + }; + + if let Some(primary_ref) = IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id) { + let primary_id = *primary_ref.value(); + drop(primary_ref); + if primary_id == omikron_id { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } } if connections_empty { - IOTA_OMIKRON_CONNECTIONS.remove_if(&iota_id, |_, connections| connections.is_empty()); - } - - if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .is_some_and(|primary| *primary == omikron_id) - { - match replacement { - Some(omikron_id) => { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); - } - None => { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); - } - } + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); } connections_empty @@ -91,9 +87,9 @@ pub async fn get_all_connections() let iota_id = *entry.key(); let omikron_ids = entry.value().clone(); - if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { for user in users { - let user_id = user.id.0; + let user_id = user.0 as i64; if let Some(conn) = USER_STATUS_MAP.get(&user_id) { let user_omikron_id = conn.omikron_id; if omikron_ids.contains(&user_omikron_id) { @@ -121,12 +117,6 @@ pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { ); } -pub fn untrack_user_status(user_id: i64, omikron_id: i64) { - USER_STATUS_MAP.remove_if(&user_id, |_, connection| { - connection.omikron_id == omikron_id - }); -} - pub fn get_user_status(user_id: i64) -> Option { USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) } @@ -138,38 +128,57 @@ pub fn untrack_many_users(user_ids: &[i64]) { } pub async fn untrack_omikron(omikron_id: i64) { + let primary_keys_to_remove: Vec = IOTA_PRIMARY_OMIKRON_CONNECTION + .iter() + .filter(|entry| *entry.value() == omikron_id) + .map(|entry| *entry.key()) + .collect(); + + for key in primary_keys_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&key); + } + let mut offline_iotas = Vec::new(); - let mut primary_replacements = Vec::new(); + let mut primary_to_remove = Vec::new(); - for mut entry in IOTA_OMIKRON_CONNECTIONS.iter_mut() { - let iota_id = *entry.key(); - entry.retain(|&id| id != omikron_id); - if entry.is_empty() { + // Collect iotas and primary info first + for r in IOTA_OMIKRON_CONNECTIONS.iter() { + let iota_id = *r.key(); + let mut connections = r.value().clone(); + connections.retain(|&id| id != omikron_id); + + if connections.is_empty() { offline_iotas.push(iota_id); - } else if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .is_some_and(|primary| *primary == omikron_id) - { - primary_replacements.push((iota_id, entry[0])); } + + if IOTA_PRIMARY_OMIKRON_CONNECTION + .get(&iota_id) + .map(|p| *p == omikron_id) + .unwrap_or(false) + { + primary_to_remove.push(iota_id); + } + + // Update the connections vector after filtering + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, connections); } - for iota_id in &offline_iotas { - IOTA_OMIKRON_CONNECTIONS.remove_if(iota_id, |_, connections| connections.is_empty()); - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); - } - - for (iota_id, replacement) in primary_replacements { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, replacement); + // Step 2: Remove primary connections safely + for iota_id in primary_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); } + // Step 3: Remove users that were on this omikron USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); + // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP for iota_id in offline_iotas { - if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { for user in users { - USER_STATUS_MAP.remove(&user.id.0); + USER_STATUS_MAP.remove(&(user.0 as i64)); } } + // Finally remove the empty connections vector + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); } } diff --git a/src/transport/connection.rs b/src/transport/connection.rs deleted file mode 100644 index eff0bb1..0000000 --- a/src/transport/connection.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) use super::omikron_connection::OmikronConnection; diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs deleted file mode 100644 index 1927b14..0000000 --- a/src/transport/handlers/account.rs +++ /dev/null @@ -1,43 +0,0 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; -use crate::{ - db::{iota_repo, user_repo}, - models::{IotaId, UserId}, -}; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use std::sync::Arc; - -async fn delete( - connection: Arc, - value: CommunicationValue, - result: impl std::future::Future>, -) -> OmikronResult<()> { - let response = match result.await { - Ok(()) => CommunicationValue::new(CommunicationType::Success), - Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) - .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), - }; - connection.send(&response.with_id(value.get_id())).await -} - -pub async fn user( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - delete( - connection, - value.clone(), - user_repo::delete_user(UserId::from(value.get_sender() as i64)), - ) - .await -} -pub async fn iota( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - delete( - connection, - value.clone(), - iota_repo::delete_iota(IotaId::from(value.get_sender() as i64)), - ) - .await -} diff --git a/src/transport/handlers/calls.rs b/src/transport/handlers/calls.rs deleted file mode 100644 index 9b16182..0000000 --- a/src/transport/handlers/calls.rs +++ /dev/null @@ -1 +0,0 @@ -/* Call and WebRTC commands are reserved for the transport extensions that define those protocol values. */ diff --git a/src/transport/handlers/links.rs b/src/transport/handlers/links.rs deleted file mode 100644 index a43cb99..0000000 --- a/src/transport/handlers/links.rs +++ /dev/null @@ -1,21 +0,0 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; -use crate::server::short_link::add_short_link; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use std::sync::Arc; - -pub async fn shorten( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let link = value - .get_data(DataType::Link) - .as_str() - .ok_or(crate::error::OmegaError::InvalidResponse)?; - let short = add_short_link(link) - .await - .map_err(|_| crate::error::OmegaError::Transport("short link error".to_string()))?; - let response = CommunicationValue::new(CommunicationType::ShortenLink) - .with_id(value.get_id()) - .add_typed_default(DataType::Link, DataValue::Str(short)); - connection.send(&response).await -} diff --git a/src/transport/handlers/messaging.rs b/src/transport/handlers/messaging.rs deleted file mode 100644 index 38c66d6..0000000 --- a/src/transport/handlers/messaging.rs +++ /dev/null @@ -1 +0,0 @@ -/* Message delivery remains in the connection dispatcher until the protocol exposes a separate message handler contract. */ diff --git a/src/transport/handlers/mod.rs b/src/transport/handlers/mod.rs deleted file mode 100644 index cb506cf..0000000 --- a/src/transport/handlers/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod account; -pub mod calls; -pub mod links; -pub mod messaging; -pub mod notifications; -pub mod presence; -pub mod register; -pub mod states; -pub mod user_data; diff --git a/src/transport/handlers/notifications.rs b/src/transport/handlers/notifications.rs deleted file mode 100644 index 5f23474..0000000 --- a/src/transport/handlers/notifications.rs +++ /dev/null @@ -1,116 +0,0 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; -use crate::{db::notification_repo, log, models::UserId}; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use mtp::type_map::TypeMap; -use std::sync::Arc; - -pub async fn get( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let notifications = - match notification_repo::get_notifications(UserId::from(value.get_sender() as i64)).await { - Ok(items) => items - .into_iter() - .map(|item| { - let tm = TypeMap::latest(); - let Some(sender) = DataType::SenderId.try_to_id(&tm) else { - return DataValue::Container(Vec::new()); - }; - let Some(amount) = DataType::Amount.try_to_id(&tm) else { - return DataValue::Container(Vec::new()); - }; - DataValue::Container(vec![ - (sender, DataValue::SignedNumber(item.sender_id.0.into())), - (amount, DataValue::SignedNumber(item.amount.into())), - ]) - }) - .collect(), - Err(error) => { - log!( - crate::util::logger::PrintType::General, - "SQL get_notifications error: {}", - error - ); - Vec::new() - } - }; - let response = CommunicationValue::new(CommunicationType::GetNotifications) - .with_id(value.get_id()) - .add_typed_default(DataType::Notifications, DataValue::Array(notifications)); - connection.send(&response).await -} - -pub async fn read( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let receiver = match value.get_sender() { - sender if sender > 0 => sender as i64, - _ => match value.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, - }; - let Some(other) = value - .get_data(DataType::SenderId) - .as_number() - .map(|id| id as i64) - else { - return Ok(()); - }; - if let Err(error) = - notification_repo::read_notification(UserId::from(receiver), UserId::from(other)).await - { - log!( - crate::util::logger::PrintType::General, - "SQL read_notification error: {}", - error - ); - } else { - let response = - CommunicationValue::new(CommunicationType::ReadNotification).with_id(value.get_id()); - let _ = connection.send(&response).await; - let sync = CommunicationValue::new(CommunicationType::ReadNotification) - .with_receiver(receiver as u64) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(other.into())); - crate::transport::omikron_manager::send_to_user(receiver, &sync).await; - } - Ok(()) -} - -pub async fn push( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let receiver = match value.get_receiver() { - receiver if receiver > 0 => receiver as i64, - _ => match value.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, - }; - let sender = value - .get_data(DataType::SenderId) - .as_number() - .map(|id| id as i64) - .unwrap_or(value.get_sender() as i64); - if let Err(error) = - notification_repo::add_notification(UserId::from(receiver), UserId::from(sender)).await - { - log!( - crate::util::logger::PrintType::General, - "SQL add_notification error: {}", - error - ); - } else { - let response = - CommunicationValue::new(CommunicationType::PushNotification).with_id(value.get_id()); - let _ = connection.send(&response).await; - let push = CommunicationValue::new(CommunicationType::PushNotification) - .with_receiver(receiver as u64) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender.into())); - crate::transport::omikron_manager::send_to_user(receiver, &push).await; - } - Ok(()) -} diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs deleted file mode 100644 index 1dd10d0..0000000 --- a/src/transport/handlers/presence.rs +++ /dev/null @@ -1,128 +0,0 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; -use crate::{ - db::user_repo, - log_in, - models::IotaId, - sql::{connection_status::UserStatus, user_online_tracker}, -}; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use std::sync::Arc; - -pub async fn user_connected( - _connection: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - log_in!(crate::util::logger::PrintType::Omega, "User connected"); - if let Some(user_id) = value.get_data(DataType::UserId).as_number() { - let status = value - .get_data(DataType::UserState) - .as_str() - .and_then(UserStatus::from_str) - .unwrap_or(UserStatus::user_online); - if let Ok(user_id) = i64::try_from(user_id) { - user_online_tracker::track_user_status(user_id, status, omikron_id); - } - } - Ok(()) -} - -pub async fn user_disconnected( - _: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - log_in!(crate::util::logger::PrintType::Omega, "User disconnected"); - if let Some(user_id) = value.get_data(DataType::UserId).as_number() { - user_online_tracker::untrack_user_status(user_id as i64, omikron_id); - } - Ok(()) -} - -pub async fn iota_connected( - connection: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - log_in!(crate::util::logger::PrintType::Omega, "IOTA connected"); - let Some(iota_id) = value - .get_data(DataType::IotaId) - .as_number() - .map(|id| id as i64) - else { - return Ok(()); - }; - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - let mut user_ids = Vec::new(); - match user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - Ok(users) => { - for user in users { - user_ids.push(DataValue::SignedNumber(user.id.0.into())); - user_online_tracker::track_user_status( - user.id.0, - UserStatus::user_offline, - omikron_id, - ); - } - } - Err(_) => log_in!( - crate::util::logger::PrintType::General, - "SQL error loading users for IOTA" - ), - } - let response = CommunicationValue::new(CommunicationType::IotaUserData) - .with_id(value.get_id()) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) - .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); - let _ = connection.send(&response).await; - Ok(()) -} - -pub async fn iota_disconnected( - _: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - log_in!(crate::util::logger::PrintType::Omega, "IOTA disconnected"); - let Some(iota_id) = value - .get_data(DataType::IotaId) - .as_number() - .map(|id| id as i64) - else { - return Ok(()); - }; - if user_online_tracker::untrack_iota_connection(iota_id, omikron_id) { - if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - user_online_tracker::untrack_many_users( - &users.iter().map(|user| user.id.0).collect::>(), - ); - } - } - Ok(()) -} - -pub async fn sync_status( - _: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - if let DataValue::Array(ids) = value.get_data(DataType::UserIds) { - for id in ids { - if let DataValue::SignedNumber(id) = id { - user_online_tracker::track_user_status( - *id as i64, - UserStatus::user_offline, - omikron_id, - ); - } - } - } - if let DataValue::Array(ids) = value.get_data(DataType::IotaIds) { - for id in ids { - if let DataValue::SignedNumber(id) = id { - user_online_tracker::track_iota_connection(*id as i64, omikron_id, true); - } - } - } - Ok(()) -} diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs deleted file mode 100644 index 23bf3e7..0000000 --- a/src/transport/handlers/register.rs +++ /dev/null @@ -1,151 +0,0 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; -use crate::{ - db::{iota_repo, user_repo}, - models::{IotaId, UserId}, -}; -use mtp::{ - codec::{CommunicationType, CommunicationValue, DataType, DataValue}, - crypto::PublicKeyBundle, -}; -use std::sync::Arc; - -pub async fn get_register( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let register_id = user_repo::get_register_id().await?; - let response = CommunicationValue::new(CommunicationType::GetRegister) - .with_id(value.get_id()) - .add_typed_default( - DataType::UserId, - DataValue::SignedNumber(register_id.0.into()), - ); - connection.send(&response).await -} - -pub async fn complete_iota( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let iota_id = value - .get_data(DataType::IotaId) - .as_number() - .map(|id| id as i64); - let public_key = value - .get_data(DataType::PublicKey) - .as_str() - .and_then(|key| PublicKeyBundle::from_base64(key).ok()); - let Some(public_key) = public_key else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) - .await; - }; - match iota_id { - Some(iota_id) => { - match iota_repo::register_complete_iota(IotaId::from(iota_id), public_key).await { - Ok(()) => { - connection - .send( - &CommunicationValue::new(CommunicationType::Success) - .with_id(value.get_id()), - ) - .await - } - Err(error) => { - connection - .send( - &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str(error.to_string()), - ), - ) - .await - } - } - } - None => match iota_repo::create_new_iota(public_key).await { - Ok(id) => { - connection - .send( - &CommunicationValue::new(CommunicationType::CompleteRegisterIota) - .with_id(value.get_id()) - .add_typed_default( - DataType::IotaId, - DataValue::SignedNumber(id.0.into()), - ), - ) - .await - } - Err(error) => { - connection - .send( - &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str(error.to_string()), - ), - ) - .await - } - }, - } -} - -pub async fn complete_user( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let user_id = value - .get_data(DataType::UserId) - .as_number() - .map(|id| id as i64); - let username = value - .get_data(DataType::Username) - .as_str() - .map(str::to_owned); - let public_key = value - .get_data(DataType::PublicKey) - .as_str() - .and_then(|key| PublicKeyBundle::from_base64(key).ok()); - let reset_token = value - .get_data(DataType::ResetToken) - .as_str() - .map(str::to_owned); - let Some((user_id, username, public_key, reset_token)) = user_id - .zip(username) - .zip(public_key) - .zip(reset_token) - .map(|(((id, name), key), token)| (id, name, key, token)) - else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) - .await; - }; - match user_repo::register_complete_user( - UserId::from(user_id), - username, - public_key, - IotaId::from(value.get_sender() as i64), - reset_token, - ) - .await - { - Ok(()) => { - connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) - .await - } - Err(error) => { - connection - .send( - &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), - ) - .await - } - } -} diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs deleted file mode 100644 index be116b1..0000000 --- a/src/transport/handlers/states.rs +++ /dev/null @@ -1,46 +0,0 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; -use crate::sql::{connection_status::UserStatus, user_online_tracker}; -use mtp::{ - codec::{CommunicationType, CommunicationValue, DataType, DataValue}, - type_map::TypeMap, -}; -use std::sync::Arc; - -pub async fn get( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let DataValue::Array(ids) = value.get_data(DataType::UserIds) else { - return Ok(()); - }; - let tm = TypeMap::latest(); - let states = ids - .iter() - .filter_map(|id| { - let DataValue::SignedNumber(id) = id else { - return None; - }; - let status = user_online_tracker::get_user_status(*id as i64) - .map(|status| { - if status.connection_type == UserStatus::user_invisible { - UserStatus::user_offline.to_string() - } else { - status.connection_type.to_string() - } - }) - .unwrap_or_else(|| UserStatus::iota_offline.to_string()); - let mut map = Vec::new(); - if let Some(kind) = DataType::UserId.try_to_id(&tm) { - map.push((kind, DataValue::SignedNumber((*id as i64).into()))); - } - if let Some(kind) = DataType::UserState.try_to_id(&tm) { - map.push((kind, DataValue::Str(status))); - } - Some(DataValue::Container(map)) - }) - .collect(); - let response = CommunicationValue::new(CommunicationType::GetStates) - .with_id(value.get_id()) - .add_typed_default(DataType::UserStates, DataValue::Array(states)); - connection.send(&response).await -} diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs deleted file mode 100644 index 27c9dbb..0000000 --- a/src/transport/handlers/user_data.rs +++ /dev/null @@ -1,271 +0,0 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; -use crate::{ - db::{iota_repo, user_repo}, - models::{IotaId, UserId}, - sql::{connection_status::UserStatus, user_online_tracker}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD}; -use mtp::{ - codec::{CommunicationType, CommunicationValue, DataType, DataValue}, - crypto::PublicKeyBundle, -}; -use std::sync::Arc; - -fn connections(iota_id: i64) -> DataValue { - DataValue::Array( - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default() - .into_iter() - .map(|id| DataValue::SignedNumber(id.into())) - .collect(), - ) -} - -pub async fn get_user( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let user = if let Some(id) = value.get_data(DataType::UserId).as_number() { - user_repo::get_by_user_id(UserId::from(id as i64)) - .await - .ok() - } else if let Some(name) = value.get_data(DataType::Username).as_str() { - user_repo::get_by_username(name).await.ok() - } else { - None - }; - let Some(user) = user else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) - .await; - }; - let id = user.id.0; - let iota_id = user.iota_id.0; - let username = user.username.clone(); - let display = user - .display - .filter(|name| !name.is_empty()) - .unwrap_or_else(|| username.clone()); - let mut response = CommunicationValue::new(CommunicationType::GetUserData) - .with_id(value.get_id()) - .add_typed_default(DataType::Username, DataValue::Str(username)) - .add_typed_default( - DataType::PublicKey, - DataValue::Str(user.public_key.to_base64()), - ) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) - .add_typed_default(DataType::Display, DataValue::Str(display)) - .add_typed_default( - DataType::SubLevel, - DataValue::SignedNumber(user.sub_level as i128), - ) - .add_typed_default( - DataType::SubEnd, - DataValue::SignedNumber(user.sub_end.into()), - ); - if let Some(status) = user.status.filter(|value| !value.is_empty()) { - response = response.add_typed_default(DataType::Status, DataValue::Str(status)); - } - if let Some(about) = user.about.filter(|value| !value.is_empty()) { - response = response.add_typed_default(DataType::About, DataValue::Str(about)); - } - if let Some(avatar) = user.avatar { - response = - response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(avatar))); - } - let online = user_online_tracker::get_user_status(id); - response = response - .add_typed_default( - DataType::OnlineStatus, - DataValue::Str( - online - .as_ref() - .map(|status| { - if status.connection_type == UserStatus::user_invisible { - UserStatus::user_offline.to_string() - } else { - status.connection_type.to_string() - } - }) - .unwrap_or_else(|| UserStatus::iota_offline.to_string()), - ), - ) - .add_typed_default(DataType::OmikronConnections, connections(iota_id)); - if let Some(status) = online { - response = response.add_typed_default( - DataType::OmikronId, - DataValue::SignedNumber(status.omikron_id.into()), - ); - } - connection.send(&response).await -} - -pub async fn get_iota( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let found = if let Some(id) = value.get_data(DataType::IotaId).as_number() { - iota_repo::get_iota_by_id(IotaId::from(id as i64)) - .await - .ok() - .map(|iota| (iota.id.0, iota.public_key, None, None)) - } else if let Some(id) = value.get_data(DataType::UserId).as_number() { - if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await { - iota_repo::get_iota_by_id(user.iota_id) - .await - .ok() - .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None)) - } else { - None - } - } else if let Some(name) = value.get_data(DataType::Username).as_str() { - if let Ok(user) = user_repo::get_by_username(name).await { - iota_repo::get_iota_by_id(user.iota_id) - .await - .ok() - .map(|iota| { - ( - iota.id.0, - iota.public_key, - Some(user.id.0), - Some(name.to_owned()), - ) - }) - } else { - None - } - } else { - None - }; - let Some((id, key, user_id, username)) = found else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) - .await; - }; - let mut response = CommunicationValue::new(CommunicationType::GetIotaData) - .with_id(value.get_id()) - .add_typed_default(DataType::PublicKey, DataValue::Str(key.to_base64())) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into())) - .add_typed_default(DataType::OmikronConnections, connections(id)); - if let Some(user_id) = user_id { - response = - response.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())); - } - if let Some(username) = username { - response = response.add_typed_default(DataType::Username, DataValue::Str(username)); - } - connection.send(&response).await -} - -async fn update_user( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let id = UserId::from(value.get_sender() as i64); - let mut error = None; - if let Some(name) = value.get_data(DataType::Username).as_str() { - error = user_repo::change_username(id, name.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } - if error.is_none() { - if let Some(name) = value.get_data(DataType::Display).as_str() { - error = user_repo::change_display_name(id, name.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } - } - if error.is_none() { - if let Some(avatar) = value.get_data(DataType::Avatar).as_str() { - error = user_repo::change_avatar(id, avatar.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } - } - if error.is_none() { - if let Some(about) = value.get_data(DataType::About).as_str() { - error = user_repo::change_about(id, about.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } - } - if error.is_none() { - if let Some(status) = value.get_data(DataType::Status).as_str() { - error = user_repo::change_status(id, status.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } - } - if error.is_none() { - if let Some(key) = value - .get_data(DataType::PublicKey) - .as_str() - .and_then(|key| PublicKeyBundle::from_base64(key).ok()) - { - error = user_repo::change_keys(id, key) - .await - .err() - .map(|e| e.to_string()); - } - } - let response = match error { - None => CommunicationValue::new(CommunicationType::Success), - Some(error) => CommunicationValue::new(CommunicationType::ErrorInternal) - .add_typed_default(DataType::ErrorType, DataValue::Str(error)), - }; - connection.send(&response.with_id(value.get_id())).await -} - -pub async fn change_user( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - update_user(connection, value).await -} - -pub async fn change_iota( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - let Some(reset) = value.get_data(DataType::ResetToken).as_str() else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) - .await; - }; - let Some(new_token) = value.get_data(DataType::NewToken).as_str() else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) - .await; - }; - let user_id = UserId::from(value.get_sender() as i64); - let user = match user_repo::get_by_user_id(user_id).await { - Ok(user) => user, - Err(_) => { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) - .await; - } - }; - if user.token != reset { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) - .await; - } - let result = - match user_repo::change_iota_id(user_id, IotaId::from(value.get_sender() as i64)).await { - Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, - Err(error) => Err(error), - }; - let response = match result { - Ok(()) => CommunicationValue::new(CommunicationType::Success), - Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) - .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), - }; - connection.send(&response.with_id(value.get_id())).await -} diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 8171bf2..4f3953a 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,4 +1,2 @@ -pub mod connection; -pub mod handlers; pub mod omikron_connection; pub mod omikron_manager; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index b2b01b3..6a5517e 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,22 +1,23 @@ -use crate::models::OmikronId; use crate::{ - load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server, + load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, + server::{self, short_link::add_short_link}, + sql::{ + connection_status::UserStatus, + sql::{self, get_by_user_id, get_by_username, get_iota_by_id}, + user_online_tracker::{self}, + }, transport::omikron_manager, util::{file_util::load_file_vec, logger::PrintType}, }; +use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use mtp::{ - codec::{CommunicationType, CommunicationValue, DataType, DataValue}, - crypto::PublicKeyBundle, - host::{AuthenticationPolicy, HostConfig, Policy, SendMode}, - webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}, -}; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::host::{AuthenticationPolicy, HostConfig, Policy, SendMode}; +use mtp::webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}; +use mtp::crypto::PublicKeyBundle; +use std::net::{IpAddr, Ipv4Addr}; use std::{ - net::{IpAddr, Ipv4Addr}, - sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, - }, + sync::Arc, time::{Duration, Instant}, }; use tokio::{ @@ -26,24 +27,25 @@ use tokio::{ const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); -static ACTIVE_CONNECTIONS: AtomicUsize = AtomicUsize::new(0); -static ACTIVE_CONNECTIONS_BY_IP: once_cell::sync::Lazy> = - once_cell::sync::Lazy::new(DashMap::new); -struct ConnectionLimitGuard(Option); -impl Drop for ConnectionLimitGuard { - fn drop(&mut self) { - ACTIVE_CONNECTIONS.fetch_sub(1, Ordering::AcqRel); - if let Some(ip) = self.0 { - if let Some(mut count) = ACTIVE_CONNECTIONS_BY_IP.get_mut(&ip) { - *count = count.saturating_sub(1); - } - ACTIVE_CONNECTIONS_BY_IP.remove_if(&ip, |_, count| *count == 0); - } - } +#[derive(Debug, thiserror::Error)] +pub enum OmikronError { + #[error("Not connected")] + NotConnected, + #[error("Not authenticated")] + NotAuthenticated, + #[error("Invalid response")] + InvalidResponse, + #[error("Authentication failed")] + AuthenticationFailed, + #[error("SQL error: {0}")] + Sql(String), + #[error("Send error: {0}")] + Send(String), } -pub type OmikronResult = crate::error::Result; +pub type OmikronResult = Result; + pub struct WaitingTask { pub task: Box, CommunicationValue) -> bool + Send + Sync>, pub inserted_at: Instant, @@ -56,6 +58,7 @@ pub struct OmikronConnection { waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, } + impl Drop for OmikronConnection { fn drop(&mut self) { if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { @@ -66,13 +69,15 @@ impl Drop for OmikronConnection { impl OmikronConnection { pub fn new(sender: WebMtpSender, id: u64) -> Arc { - Arc::new(Self { + let conn = Arc::new(Self { id, sender: Mutex::new(Some(sender)), ping: RwLock::new(-1), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), - }) + }); + + conn } pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { @@ -81,24 +86,28 @@ impl OmikronConnection { PrintType::Omega, "Omikron connection started" ); + let cleanup_conn = self.clone(); - *self.cleanup_handle.lock().unwrap() = Some(tokio::spawn(async move { + let cleanup_handle = tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); loop { ticker.tick().await; cleanup_conn .waiting_tasks - .retain(|_, task| task.inserted_at.elapsed() < MAX_WAITING_AGE); + .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); } - })); - while let Ok(value) = receiver.receive().await { - if let Err(error) = self.clone().process_message(value).await { - log_err!(0, PrintType::Omega, "Error processing message: {}", error); - if matches!(error, crate::error::OmegaError::NotConnected) { + }); + *self.cleanup_handle.lock().unwrap() = Some(cleanup_handle); + + while let Ok(cv) = receiver.receive().await { + if let Err(e) = self.clone().process_message(cv).await { + log_err!(0, PrintType::Omega, "Error processing message: {}", e); + if matches!(e, OmikronError::NotConnected) { break; } } } + self.clone().cleanup().await; log_in!( self.id as i64, @@ -107,120 +116,845 @@ impl OmikronConnection { ); } - async fn process_message(self: Arc, value: CommunicationValue) -> OmikronResult<()> { - if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { - log_cv_in!(PrintType::Omikron, &value); + async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { + log_cv_in!(PrintType::Omikron, &cv); } - if let Some((_, task)) = self.waiting_tasks.remove(&value.get_id()) { - let _ = (task.task)(self.clone(), value); + + let msg_id = cv.get_id(); + + if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { + let _ = (task.task)(self.clone(), cv); return Ok(()); } - if value.is_type(CommunicationType::Ping) { - return self.ping(value).await; + + if cv.is_type(CommunicationType::Ping) { + return self.handle_ping(cv).await; } - self.dispatch(value).await + + let omikron_id = self.id as i64; + self.clone().handle_authenticated(cv, omikron_id).await } - async fn dispatch(self: Arc, value: CommunicationValue) -> OmikronResult<()> { - let id = self.id as i64; - match value.get_comm_type_enum() { - Some(CommunicationType::ShortenLink) => { - crate::transport::handlers::links::shorten(self, value).await - } + async fn handle_authenticated( + self: Arc, + cv: CommunicationValue, + omikron_id: i64, + ) -> OmikronResult<()> { + let comm_type = cv.get_comm_type_enum(); + match comm_type { + Some(CommunicationType::ShortenLink) => self.handle_shorten_link(cv).await, + Some(CommunicationType::UserConnected) => { - crate::transport::handlers::presence::user_connected(self, value, id).await + self.handle_user_connected(cv, omikron_id).await; + Ok(()) } Some(CommunicationType::UserDisconnected) => { - crate::transport::handlers::presence::user_disconnected(self, value, id).await + self.handle_user_disconnected(cv, omikron_id).await; + Ok(()) } Some(CommunicationType::IotaConnected) => { - crate::transport::handlers::presence::iota_connected(self, value, id).await + self.handle_iota_connected(cv, omikron_id).await; + Ok(()) } Some(CommunicationType::IotaDisconnected) => { - crate::transport::handlers::presence::iota_disconnected(self, value, id).await + self.handle_iota_disconnected(cv, omikron_id).await; + Ok(()) } Some(CommunicationType::SyncClientIotaStatus) => { - crate::transport::handlers::presence::sync_status(self, value, id).await - } - Some(CommunicationType::GetUserData) => { - crate::transport::handlers::user_data::get_user(self, value).await - } - Some(CommunicationType::GetIotaData) => { - crate::transport::handlers::user_data::get_iota(self, value).await - } - Some(CommunicationType::ChangeUserData) => { - crate::transport::handlers::user_data::change_user(self, value).await - } - Some(CommunicationType::ChangeIotaData) => { - crate::transport::handlers::user_data::change_iota(self, value).await - } - Some(CommunicationType::GetRegister) => { - crate::transport::handlers::register::get_register(self, value).await + self.handle_sync_status(cv, omikron_id).await; + Ok(()) } + + Some(CommunicationType::GetUserData) => self.handle_get_user_data(cv).await, + Some(CommunicationType::GetIotaData) => self.handle_get_iota_data(cv).await, + + Some(CommunicationType::GetRegister) => self.handle_get_register(cv).await, Some(CommunicationType::CompleteRegisterIota) => { - crate::transport::handlers::register::complete_iota(self, value).await + self.handle_complete_register_iota(cv).await } Some(CommunicationType::CompleteRegisterUser) => { - crate::transport::handlers::register::complete_user(self, value).await - } - Some(CommunicationType::DeleteUser) => { - crate::transport::handlers::account::user(self, value).await - } - Some(CommunicationType::DeleteIota) => { - crate::transport::handlers::account::iota(self, value).await - } - Some(CommunicationType::GetNotifications) => { - crate::transport::handlers::notifications::get(self, value).await - } - Some(CommunicationType::ReadNotification) => { - crate::transport::handlers::notifications::read(self, value).await - } - Some(CommunicationType::PushNotification) => { - crate::transport::handlers::notifications::push(self, value).await - } - Some(CommunicationType::GetStates) => { - crate::transport::handlers::states::get(self, value).await + self.handle_complete_register_user(cv).await } + + Some(CommunicationType::ChangeUserData) => self.handle_change_user_data(cv).await, + Some(CommunicationType::ChangeIotaData) => self.handle_change_iota_data(cv).await, + Some(CommunicationType::DeleteUser) => self.handle_delete_user(cv).await, + Some(CommunicationType::DeleteIota) => self.handle_delete_iota(cv).await, + + Some(CommunicationType::GetNotifications) => self.handle_get_notifications(cv).await, + Some(CommunicationType::ReadNotification) => self.handle_read_notification(cv).await, + Some(CommunicationType::PushNotification) => self.handle_push_notification(cv).await, + Some(CommunicationType::GetStates) => self.handle_get_states(cv).await, + _ => { log_err!( 0, PrintType::Omega, "Unknown message type: {:?}", - value.get_type() + cv.get_type() ); Ok(()) } } } - async fn ping(self: Arc, value: CommunicationValue) -> OmikronResult<()> { - if let DataValue::SignedNumber(last_ping) = value.get_data(DataType::LastPing) { - *self.ping.write().await = *last_ping as i64; - } - let response = CommunicationValue::new(CommunicationType::Pong).with_id(value.get_id()); + async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let link = cv + .get_data(DataType::Link) + .as_str() + .ok_or(OmikronError::InvalidResponse)?; + + let short = add_short_link(link) + .await + .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; + + let response = CommunicationValue::new(CommunicationType::ShortenLink) + .with_id(cv.get_id()) + .add_typed_default(DataType::Link, DataValue::Str(short)); + self.send(&response).await } - pub(crate) async fn send(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { - if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { - log_cv_out!(PrintType::Omikron, value); + + async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "User connected"); + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { + let status = cv + .get_data(DataType::UserState) + .as_str() + .and_then(|s| UserStatus::from_str(s)) + .unwrap_or(UserStatus::user_online); + user_online_tracker::track_user_status(user_id.try_into().unwrap(), status, omikron_id); } - let guard = self.sender.lock().await; - let sender = guard - .as_ref() - .ok_or(crate::error::OmegaError::NotConnected)?; - sender - .send(value) - .await - .map_err(|error| crate::error::OmegaError::SendError(error.to_string())) } - pub(crate) async fn send_error_response( + + async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { + log_in!(PrintType::Omega, "User disconnected"); + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { + if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { + user_online_tracker::track_user_status( + user_id as i64, + UserStatus::user_offline, + status.omikron_id, + ); + } + } + } + + async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA connected"); + let iota_id = match cv.get_data(DataType::IotaId).as_number() { + Some(id) => id as i64, + None => return, + }; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); + + let mut user_ids = Vec::new(); + if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { + user_ids.push(DataValue::SignedNumber(user_id.try_into().unwrap())); + user_online_tracker::track_user_status( + user_id.try_into().unwrap(), + UserStatus::user_offline, + omikron_id, + ); + } + } else { + log_in!(PrintType::General, "SQL error loading users for IOTA"); + } + + let response = CommunicationValue::new(CommunicationType::IotaUserData) + .with_id(cv.get_id()) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); + + let _ = self.send(&response).await; + } + + async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA disconnected"); + let iota_id = match cv.get_data(DataType::IotaId).as_number() { + Some(id) => id as i64, + None => return, + }; + let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + let user_ids: Vec = users.iter().map(|u| u.0.try_into().unwrap()).collect(); + user_online_tracker::untrack_many_users(&user_ids); + } + } + } + + async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { + if let DataValue::Array(user_ids) = cv.get_data(DataType::UserIds) { + for user_id_val in user_ids { + if let DataValue::SignedNumber(user_id) = user_id_val { + user_online_tracker::track_user_status( + *user_id as i64, + UserStatus::user_offline, + omikron_id, + ); + } + } + } + + if let DataValue::Array(iota_ids) = cv.get_data(DataType::IotaIds) { + for iota_id_val in iota_ids { + if let DataValue::SignedNumber(iota_id) = iota_id_val { + user_online_tracker::track_iota_connection(*iota_id as i64, omikron_id, true); + } + } + } + } + + async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { + if let Ok(user_data) = get_by_user_id(user_id as i64).await { + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; + return self.send(&response).await; + } + } + + if let Some(username) = cv.get_data(DataType::Username).as_str() { + if let Ok(user_data) = get_by_username(username).await { + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; + return self.send(&response).await; + } + } + + let response = + CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_user_data_response( + self: Arc, + msg_id: u32, + user: ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + PublicKeyBundle, + String, + String, + ), + ) -> CommunicationValue { + let ( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + ) = user; + + let mut response = CommunicationValue::new(CommunicationType::GetUserData) + .with_id(msg_id) + .add_typed_default(DataType::Username, DataValue::Str(username.clone())) + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default( + DataType::SubLevel, + DataValue::SignedNumber(sub_level as i128), + ) + .add_typed_default(DataType::SubEnd, DataValue::SignedNumber(sub_end.into())); + + let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); + response = response.add_typed_default(DataType::Display, DataValue::Str(display_name)); + + if let Some(s) = status.filter(|s| !s.is_empty()) { + response = response.add_typed_default(DataType::Status, DataValue::Str(s)); + } + if let Some(a) = about.filter(|a| !a.is_empty()) { + response = response.add_typed_default(DataType::About, DataValue::Str(a)); + } + if let Some(av) = avatar { + response = + response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); + } + + let user_status = user_online_tracker::get_user_status(id); + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + if let Some(us) = user_status { + let display_status = if us.connection_type == UserStatus::user_invisible { + UserStatus::user_offline + } else { + us.connection_type.clone() + }; + response = response.add_typed_default( + DataType::OnlineStatus, + DataValue::Str(display_status.to_string()), + ); + response = response.add_typed_default( + DataType::OmikronId, + DataValue::SignedNumber(us.omikron_id.into()), + ); + } else { + response = response.add_typed_default( + DataType::OnlineStatus, + DataValue::Str(UserStatus::iota_offline.to_string()), + ); + } + + response = response.add_typed_default( + DataType::OmikronConnections, + DataValue::Array( + iota_connections + .into_iter() + .map(|id| DataValue::SignedNumber(id as i128)) + .collect(), + ), + ); + + response + } + + async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if let Some(iota_id) = cv.get_data(DataType::IotaId).as_number() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { + let response = self + .clone() + .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) + .await; + return self.send(&response).await; + } + } + + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { + if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_user_id(user_id as i64).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let response = self + .clone() + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id as i64), + None, + ) + .await; + return self.send(&response).await; + } + } + } + + if let Some(username) = cv.get_data(DataType::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 response = self + .clone() + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id), + Some(username.to_string()), + ) + .await; + return self.send(&response).await; + } + } + } + + let response = + CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_iota_data_response( + self: Arc, + msg_id: u32, + iota_id: i64, + public_key: PublicKeyBundle, + user_id: Option, + username: Option, + ) -> CommunicationValue { + let mut response = CommunicationValue::new(CommunicationType::GetIotaData) + .with_id(msg_id) + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); + + if let Some(uid) = user_id { + response = + response.add_typed_default(DataType::UserId, DataValue::SignedNumber(uid.into())); + } + if let Some(uname) = username { + response = response.add_typed_default(DataType::Username, DataValue::Str(uname)); + } + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + response.add_typed_default( + DataType::OmikronConnections, + DataValue::Array( + iota_connections + .into_iter() + .map(|id| DataValue::SignedNumber(id as i128)) + .collect(), + ), + ) + } + + async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let register_id = sql::get_register_id().await; + let response = CommunicationValue::new(CommunicationType::GetRegister) + .with_id(cv.get_id()) + .add_typed_default( + DataType::UserId, + DataValue::SignedNumber(register_id as i128), + ); + self.send(&response).await + } + + async fn handle_complete_register_iota( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let iota_id_opt = cv.get_data(DataType::IotaId).as_number().map(|n| n as i64); + + let public_key = cv + .get_data(DataType::PublicKey) + .as_str() + .and_then(|s| PublicKeyBundle::from_base64(s).ok()); + + if let Some(public_key) = public_key { + if let Some(iota_id) = iota_id_opt { + match sql::register_complete_iota(iota_id, public_key).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::Success) + .with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(cv.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + match sql::create_new_iota(public_key).await { + Ok(new_iota_id) => { + let response = + CommunicationValue::new(CommunicationType::CompleteRegisterIota) + .with_id(cv.get_id()) + .add_typed_default( + DataType::IotaId, + DataValue::SignedNumber(new_iota_id.into()), + ); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(cv.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) + .await + } + } + + async fn handle_complete_register_user( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv.get_data(DataType::UserId).as_number().map(|n| n as i64); + let username = cv + .get_data(DataType::Username) + .as_str() + .map(|s| s.to_string()); + let public_key = cv + .get_data(DataType::PublicKey) + .as_str() + .and_then(|s| PublicKeyBundle::from_base64(s).ok()); + let iota_id = cv.get_sender(); + let reset_token = cv + .get_data(DataType::ResetToken) + .as_str() + .map(|s| s.to_string()); + + if let (Some(uid), Some(uname), Some(pk), Some(rt)) = + (user_id, username, public_key, reset_token) + { + match sql::register_complete_user(uid, uname, pk, iota_id as i64, rt).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(cv.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) + .await + } + } + + async fn handle_change_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + let mut success = true; + let mut error_message = String::new(); + + if let Some(username) = cv.get_data(DataType::Username).as_str() { + if let Err(e) = sql::change_username(user_id, username.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(display) = cv.get_data(DataType::Display).as_str() { + if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(avatar) = cv.get_data(DataType::Avatar).as_str() { + if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(about) = cv.get_data(DataType::About).as_str() { + if let Err(e) = sql::change_about(user_id, about.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(status) = cv.get_data(DataType::Status).as_str() { + if let Err(e) = sql::change_status(user_id, status.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let (Some(public_key), Some(private_key_hash)) = ( + cv.get_data(DataType::PublicKey) + .as_str() + .and_then(|s| PublicKeyBundle::from_base64(s).ok()), + cv.get_data(DataType::PrivateKeyHash).as_str(), + ) { + if let Err(e) = + sql::change_keys(user_id, public_key, private_key_hash.to_string()).await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); + self.send(&response).await + } else { + let response = CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(cv.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error_message)); + self.send(&response).await + } + } + + async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + + if let (iota_id, Some(reset_token), Some(new_token)) = ( + cv.get_sender(), + cv.get_data(DataType::ResetToken).as_str(), + cv.get_data(DataType::NewToken).as_str(), + ) { + match sql::get_by_user_id(user_id).await { + Ok(user) => { + let current_token = user.11; + if current_token == reset_token { + let mut success = true; + let mut error_message = String::new(); + + if let Err(e) = sql::change_iota_id(user_id, iota_id as i64).await { + success = false; + error_message = e.to_string(); + } + if success { + if let Err(e) = sql::change_token(user_id, new_token.to_string()).await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::Success) + .with_id(cv.get_id()); + self.send(&response).await + } else { + let response = + CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(cv.get_id()) + .add_typed_default( + DataType::ErrorType, + DataValue::Str(error_message), + ); + self.send(&response).await + } + } else { + self.send_error_response( + cv.get_id(), + CommunicationType::ErrorInvalidChallenge, + ) + .await + } + } + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound) + .await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) + .await + } + } + + async fn handle_delete_user(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + match sql::delete_user(user_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(cv.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } + + async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let iota_id = cv.get_sender(); + match sql::delete_iota(iota_id as i64).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(cv.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } + + async fn handle_get_notifications( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + let response_array = match sql::get_notifications(user_id).await { + Ok(notifications) => notifications + .into_iter() + .map(|(sender, amount)| { + let tm = mtp::type_map::TypeMap::latest(); + DataValue::Container(vec![ + ( + DataType::SenderId.to_id(&tm), + DataValue::SignedNumber(sender.into()), + ), + ( + DataType::Amount.to_id(&tm), + DataValue::SignedNumber(amount.into()), + ), + ]) + }) + .collect(), + Err(e) => { + log!(PrintType::General, "SQL get_notifications error: {}", e); + vec![] + } + }; + + let response = CommunicationValue::new(CommunicationType::GetNotifications) + .with_id(cv.get_id()) + .add_typed_default(DataType::Notifications, DataValue::Array(response_array)); + self.send(&response).await + } + + async fn handle_read_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let receiver_id = match cv.get_sender() { + s if s > 0 => s as i64, + _ => match cv.get_data(DataType::ReceiverId).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + + if let Some(other_id) = cv + .get_data(DataType::SenderId) + .as_number() + .map(|n| n as i64) + { + if let Err(e) = sql::read_notification(receiver_id, other_id).await { + log!(PrintType::General, "SQL read_notification error: {}", e); + } else { + let response = CommunicationValue::new(CommunicationType::ReadNotification) + .with_id(cv.get_id()); + let _ = self.send(&response).await; + + let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) + .with_receiver(receiver_id as u64) + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(other_id.into()), + ); + crate::transport::omikron_manager::send_to_user(receiver_id, &sync_cv).await; + } + } + Ok(()) + } + + async fn handle_push_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let receiver_id = match cv.get_receiver() { + r if r > 0 => r as i64, + _ => match cv.get_data(DataType::ReceiverId).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + + let sender_id = match cv.get_data(DataType::SenderId).as_number() { + Some(id) => id as i64, + None => cv.get_sender() as i64, + }; + + if let Err(e) = sql::add_notification(receiver_id, sender_id).await { + log!(PrintType::General, "SQL add_notification error: {}", e); + } else { + let response = + CommunicationValue::new(CommunicationType::PushNotification).with_id(cv.get_id()); + let _ = self.send(&response).await; + + let push_cv = CommunicationValue::new(CommunicationType::PushNotification) + .with_receiver(receiver_id as u64) + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(sender_id.into()), + ); + crate::transport::omikron_manager::send_to_user(receiver_id, &push_cv).await; + } + Ok(()) + } + + async fn handle_get_states(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_ids = match cv.get_data(DataType::UserIds) { + DataValue::Array(ids) => ids, + _ => return Ok(()), + }; + + let mut states = Vec::new(); + for id_val in user_ids { + if let DataValue::SignedNumber(user_id) = id_val { + let user_id = *user_id as i64; + let status = user_online_tracker::get_user_status(user_id); + let status_str = match status { + Some(ref us) => { + if us.connection_type == UserStatus::user_invisible { + "user_offline".to_string() + } else { + us.connection_type.to_string() + } + } + None => UserStatus::iota_offline.to_string(), + }; + let tm = mtp::type_map::TypeMap::latest(); + let mut map = Vec::new(); + map.push(( + DataType::UserId.to_id(&tm), + DataValue::SignedNumber(user_id.into()), + )); + map.push((DataType::UserState.to_id(&tm), DataValue::Str(status_str))); + states.push(DataValue::Container(map)); + } + } + + let response = CommunicationValue::new(CommunicationType::GetStates) + .with_id(cv.get_id()) + .add_typed_default(DataType::UserStates, DataValue::Array(states)); + self.send(&response).await + } + + async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) { + *self.ping.write().await = *last_ping as i64; + } + + let response = CommunicationValue::new(CommunicationType::Pong).with_id(cv.get_id()); + self.send(&response).await + } + + async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { + log_cv_out!(PrintType::Omikron, cv); + } + + let guard = self.sender.lock().await; + let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; + + sender + .send(cv) + .await + .map_err(|e| OmikronError::Send(e.to_string())) + } + + async fn send_error_response( self: Arc, message_id: u32, error_type: CommunicationType, ) -> OmikronResult<()> { - self.send(&CommunicationValue::new(error_type).with_id(message_id)) - .await + let error = CommunicationValue::new(error_type).with_id(message_id); + self.send(&error).await } + pub async fn close(self: Arc) { log_in!( self.get_omikron_id().await.unwrap_or(0), @@ -228,38 +962,51 @@ impl OmikronConnection { "Omikron connection Closed" ); } + async fn cleanup(self: Arc) { if self.id != 0 { log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); omikron_manager::remove_omikron(self.id as i64).await; - crate::sql::user_online_tracker::untrack_omikron(self.id as i64).await; + user_online_tracker::untrack_omikron(self.id as i64).await; } + if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { handle.abort(); } } + pub async fn get_omikron_id(self: Arc) -> Option { Some(self.id as i64) } - pub async fn send_message(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { - self.send(value).await + + pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { + self.send(cv).await } } -pub async fn get_by_omikron_id(omikron_id: u64, _: Option) -> Option { - crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id as i64)) +// ============================================================================ +// Server Startup +// ============================================================================ + +pub async fn get_by_omikron_id( + omikron_id: u64, + _description: Option, +) -> Option { + sql::get_omikron_by_id(omikron_id as i64) .await .ok() - .map(|omikron| omikron.public_key) + .map(|(bundle, _ip_address, _port)| bundle) } -pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { +pub async fn complete_register(_pub_key: PublicKeyBundle, _description: Option) -> u64 { 0 } pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "transport_cert.pem")?; - let key_pem = load_file_vec("certs", "transport_key.pem")?; + let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); + let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); + let web_config = server::server::build_web_config()?; + let host_config = HostConfig::new( IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, @@ -287,51 +1034,30 @@ pub async fn start(port: u16) -> Result<(), Box> { }) .with_authentication( load_keyring(), - Box::new(|id, description| Box::pin(get_by_omikron_id(id, description))), - Box::new(|key, description| Box::pin(complete_register(key, description))), + Box::new(|user_id, description| Box::pin(get_by_omikron_id(user_id, description))), + Box::new(|pub_key, description| Box::pin(complete_register(pub_key, description))), ) .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); + let mut server = MTPWebServer::new(host_config, web_config).await?; log!("OmegaServer listening on port {}", port); + loop { let mut conn = match server.accept().await { Ok(Some(conn)) => conn, Ok(None) => break, - Err(error) => { - log_err!( - 0, - PrintType::Omega, - "Rejected omikron connection: {}", - error - ); + Err(e) => { + log_err!(0, PrintType::Omega, "Rejected omikron connection: {}", e); continue; } }; - let config = crate::config::RateLimitConfig::from_env(); - let peer_ip = conn.remote_addr.map(|address| address.ip()); - let active = ACTIVE_CONNECTIONS.fetch_add(1, Ordering::AcqRel) + 1; - let peer_active = peer_ip.map(|ip| { - let mut count = ACTIVE_CONNECTIONS_BY_IP.entry(ip).or_insert(0); - *count += 1; - *count - }); - if active > config.transport_connections - || peer_active.is_some_and(|count| count > config.transport_connections_per_ip) - { - drop(ConnectionLimitGuard(peer_ip)); - log_err!( - 0, - PrintType::Omega, - "Rejected connection: active connection limit reached" - ); - continue; - } - let connection = OmikronConnection::new(conn.sender, conn.client_id); + + let omikron_connection = OmikronConnection::new(conn.sender, conn.client_id); tokio::spawn(async move { - let _guard = ConnectionLimitGuard(peer_ip); - omikron_manager::add_omikron(connection.clone()).await; - connection.handle(&mut conn.receiver).await; + omikron_manager::add_omikron(omikron_connection.clone()).await; + omikron_connection.handle(&mut conn.receiver).await; }); } + Ok(()) } diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 2de4ea3..f98a75c 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -1,4 +1,4 @@ -use crate::transport::connection::OmikronConnection; +use crate::transport::omikron_connection::OmikronConnection; use dashmap::DashMap; use mtp::codec::CommunicationValue; use once_cell::sync::Lazy; @@ -27,9 +27,11 @@ pub async fn remove_omikron(omikron_id: i64) { } pub async fn get_random_omikron() -> Result, ()> { + let mut rng = rand::thread_rng(); + let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); - if let Some(key) = keys.into_iter().choose(&mut rand::rng()) { + if let Some(key) = keys.into_iter().choose(&mut rng) { if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { return Ok(entry.clone()); } diff --git a/src/util/file_util.rs b/src/util/file_util.rs index 8630489..593adbe 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -8,8 +8,9 @@ use zip::ZipArchive; use crate::log; -static WORKING_DIR: Lazy = - Lazy::new(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); +static WORKING_DIR: Lazy = Lazy::new(|| { + std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) +}); pub fn delete_file(path: &str, name: &str) -> bool { let dir = Path::new(&get_directory()).join(path); diff --git a/src/util/logger.rs b/src/util/logger.rs index 39eb933..31606b6 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -269,7 +269,8 @@ fn format_data_container(data: Vec<(DataTypeId, DataValue)>, version: Version) - DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), DataValue::Container(inner) => { - let inner_formatted = format_data_container(inner, version.clone()); + let inner_formatted = + format_data_container(inner, version.clone()); format!("{}={{ {} }}", key_str, inner_formatted) }