diff --git a/Cargo.lock b/Cargo.lock index 04277be..cf4ad0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" dependencies = [ "actix-rt", - "bitflags 2.13.1", + "bitflags 2.13.0", "bytes", "crossbeam-channel", "futures-core", @@ -31,7 +31,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "bytes", "futures-core", "futures-sink", @@ -54,7 +54,7 @@ dependencies = [ "actix-tls", "actix-utils", "base64", - "bitflags 2.13.1", + "bitflags 2.13.0", "brotli", "bytes", "bytestring", @@ -89,7 +89,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -211,7 +211,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.6.5", + "socket2 0.6.4", "time", "tracing", "url", @@ -244,7 +244,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.104" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "approx" @@ -372,7 +372,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.19", + "thiserror 2.0.18", "time", ] @@ -384,7 +384,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "synstructure", ] @@ -396,18 +396,18 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 3.0.1", + "syn 2.0.118", ] [[package]] @@ -433,9 +433,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.3" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -444,9 +444,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.43.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", @@ -499,9 +499,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -556,9 +556,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.25.2" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -568,9 +568,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "bytestring" @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.3.0" +version = "1.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" dependencies = [ "find-msvc-tools", "jobserver", @@ -619,9 +619,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" @@ -701,7 +701,6 @@ dependencies = [ "hyper", "hyper-util", "iota-auth", - "iota-connection", "iota-logger", "iota-state", "iota-storage", @@ -712,7 +711,7 @@ dependencies = [ "once_cell", "open", "pnet", - "rand 0.8.7", + "rand 0.8.6", "rand_core 0.6.4", "ratatui", "reqwest", @@ -906,7 +905,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "crossterm_winapi", "derive_more", "document-features", @@ -1001,7 +1000,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -1024,7 +1023,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -1035,7 +1034,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -1083,9 +1082,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" dependencies = [ "const-oid 0.10.2", "zeroize", @@ -1119,7 +1118,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -1141,7 +1140,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.119", + "syn 2.0.118", "unicode-xid", ] @@ -1176,7 +1175,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -1297,23 +1296,11 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" -[[package]] -name = "fastbloom" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" -dependencies = [ - "foldhash", - "libm", - "portable-atomic", - "siphasher", -] - [[package]] name = "fastrand" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fiat-crypto" @@ -1411,9 +1398,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1426,9 +1413,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1436,15 +1423,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1453,38 +1440,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1600,62 +1587,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "h3" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" -dependencies = [ - "bytes", - "fastrand", - "futures-util", - "http 1.4.2", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "h3-datagram" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2c9f77921668673721ae40f17c729fc48b9e38a663858097cea547484fdf0f" -dependencies = [ - "bytes", - "h3", - "pin-project-lite", -] - -[[package]] -name = "h3-quinn" -version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2e732c8d91a74731663ac8479ab505042fbf547b9a207213ab7fbcbfc4f8b4" -dependencies = [ - "bytes", - "futures", - "h3", - "h3-datagram", - "quinn", - "tokio", - "tokio-util", -] - -[[package]] -name = "h3-webtransport" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d91a50fd582a5d67b1f756fba3cd9c66367ff4f23e1017c882f664d63b350a7" -dependencies = [ - "bytes", - "futures-util", - "h3", - "h3-datagram", - "http 1.4.2", - "pin-project-lite", - "tokio", - "tracing", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -1705,7 +1636,7 @@ dependencies = [ "http 1.4.2", "httpdate", "mime", - "sha1 0.10.7", + "sha1 0.10.6", ] [[package]] @@ -1794,9 +1725,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.4.2", @@ -1804,9 +1735,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", @@ -1891,7 +1822,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.5", + "socket2 0.6.4", "system-configuration", "tokio", "tower-service", @@ -2082,7 +2013,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -2111,7 +2042,7 @@ dependencies = [ "once_cell", "open", "pnet", - "rand 0.8.7", + "rand 0.8.6", "rand_core 0.6.4", "ratatui", "reqwest", @@ -2163,7 +2094,7 @@ dependencies = [ "once_cell", "open", "pnet", - "rand 0.8.7", + "rand 0.8.6", "rand_core 0.6.4", "ratatui", "reqwest", @@ -2185,15 +2116,6 @@ dependencies = [ "zip", ] -[[package]] -name = "iota-connection" -version = "0.1.0" -dependencies = [ - "iota-storage", - "iota-util", - "mtp", -] - [[package]] name = "iota-core" version = "0.1.0" @@ -2257,18 +2179,15 @@ dependencies = [ "json", "mtp", "once_cell", - "r2d2", - "rand 0.8.7", + "rand 0.8.6", "rand_core 0.6.4", "ratatui", "reqwest", "rusqlite", "serde", "serde_json", - "serde_yaml", "sha2 0.10.9", "sysinfo", - "thiserror 2.0.19", "tokio", "uuid", "walkdir", @@ -2323,6 +2242,7 @@ dependencies = [ "base64", "hex", "mtp", + "mtp-crypto", "reqwest", "sysinfo", "tokio", @@ -2392,7 +2312,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror 2.0.18", "walkdir", "windows-link", ] @@ -2407,7 +2327,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -2426,7 +2346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -2464,7 +2384,7 @@ checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ "hashbrown 0.16.1", "portable-atomic", - "thiserror 2.0.19", + "thiserror 2.0.18", ] [[package]] @@ -2538,7 +2458,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", ] [[package]] @@ -2593,9 +2513,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.18.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" dependencies = [ "hashbrown 0.17.1", ] @@ -2628,9 +2548,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.3" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmem" @@ -2681,9 +2601,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -2746,8 +2666,8 @@ dependencies = [ [[package]] name = "mtp" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" +version = "0.1.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-client", "mtp-codec", @@ -2755,52 +2675,50 @@ dependencies = [ "mtp-crypto", "mtp-files", "mtp-host", - "mtp-transport", "mtp-type-map", - "mtp-webserver", ] [[package]] name = "mtp-client" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" +version = "0.1.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.10.2", + "rand 0.8.6", "tokio", ] [[package]] name = "mtp-codec" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" +version = "0.1.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "base64", "byteorder", "mtp-common", "mtp-crypto", "mtp-type-map", - "rand 0.10.2", + "rand 0.8.6", ] [[package]] name = "mtp-common" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" +version = "0.1.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "quinn", "rustls", - "thiserror 2.0.19", + "thiserror 2.0.18", "wtransport", ] [[package]] name = "mtp-crypto" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" +version = "0.1.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "base64", "chacha20poly1305", @@ -2809,93 +2727,59 @@ dependencies = [ "hkdf 0.13.0", "ml-dsa", "mlkem-tls", - "rand 0.10.2", - "rand_core 0.10.1", - "rustls", + "rand_core 0.6.4", "serde", "sha2 0.11.0", "thiserror 1.0.69", - "tokio", "zeroize", ] [[package]] name = "mtp-files" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" +version = "0.1.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-crypto", - "rand 0.10.2", "thiserror 1.0.69", - "zeroize", ] [[package]] name = "mtp-host" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" +version = "0.1.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.8.7", + "rand 0.8.6", "tokio", - "tracing", - "wtransport", ] [[package]] name = "mtp-transport" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" +version = "0.1.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ - "async-trait", + "log", "mtp-codec", "mtp-common", - "mtp-crypto", "rcgen", "rustls", "rustls-native-certs", - "sha2 0.11.0", "tokio", - "tracing", "wtransport", ] [[package]] name = "mtp-type-map" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" +version = "0.1.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "serde", "serde_yaml", ] -[[package]] -name = "mtp-webserver" -version = "0.2.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#04760fd88d2bc3adf548a9ec532fa228227f0a49" -dependencies = [ - "async-trait", - "bytes", - "h3", - "h3-quinn", - "h3-webtransport", - "http 1.4.2", - "mtp-codec", - "mtp-common", - "mtp-crypto", - "mtp-host", - "mtp-transport", - "quinn", - "rand 0.10.2", - "rustls", - "thiserror 2.0.19", - "tokio", - "tracing", -] - [[package]] name = "native-tls" version = "0.2.18" @@ -2919,7 +2803,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -2975,7 +2859,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -3011,7 +2895,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", ] [[package]] @@ -3026,9 +2910,9 @@ dependencies = [ [[package]] name = "octets" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866cb5af6f3aa3c1b44c3c2d79d22165fbb1b102e1b3fb499864bfe34736ec4b" +checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" [[package]] name = "oid-registry" @@ -3046,14 +2930,13 @@ dependencies = [ "base64", "dashmap", "hex", - "iota-connection", "iota-logger", "iota-state", "iota-storage", "iota-util", "json", "mtp", - "rand 0.8.7", + "rand 0.8.6", "rand_core 0.6.4", "reqwest", "sha2 0.10.9", @@ -3076,9 +2959,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "open" -version = "5.4.0" +version = "5.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b3d059e795d52b8a72fef45658620edd4d9c359b338564aa14391ffa511ed5" +checksum = "cd8d3b65c44123a56e0133d2cd06ce4361bd3ca99d41198b2f25e3c3db9b8b4a" dependencies = [ "is-wsl", "libc", @@ -3090,7 +2973,7 @@ version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "cfg-if", "foreign-types", "libc", @@ -3106,7 +2989,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -3165,7 +3048,7 @@ dependencies = [ "once_cell", "open", "pnet", - "rand 0.8.7", + "rand 0.8.6", "rand_core 0.6.4", "ratatui", "reqwest", @@ -3208,7 +3091,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -3299,7 +3182,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -3338,7 +3221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.7", + "rand 0.8.6", ] [[package]] @@ -3351,7 +3234,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -3380,7 +3263,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -3405,7 +3288,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "der 0.8.1", + "der 0.8.0", "spki 0.8.0", ] @@ -3460,7 +3343,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -3531,9 +3414,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" @@ -3567,9 +3450,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.107" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3582,14 +3465,13 @@ checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", - "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.5", - "thiserror 2.0.19", + "socket2 0.6.4", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3603,7 +3485,6 @@ checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "aws-lc-rs", "bytes", - "fastbloom", "getrandom 0.4.3", "lru-slab", "rand 0.10.2", @@ -3612,9 +3493,8 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", - "rustls-platform-verifier", "slab", - "thiserror 2.0.19", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3629,16 +3509,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.5", + "socket2 0.6.4", "tracing", "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.47" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -3656,25 +3536,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "r2d2" -version = "0.8.10" +name = "rand" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ - "log", - "parking_lot", - "scheduled-thread-pool", + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", ] [[package]] name = "rand" -version = "0.8.7" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -3698,6 +3577,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -3713,6 +3602,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rand_core" version = "0.10.1" @@ -3750,7 +3648,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "compact_str", "critical-section", "hashbrown 0.17.1", @@ -3760,7 +3658,7 @@ dependencies = [ "palette", "serde", "strum 0.28.0", - "thiserror 2.0.19", + "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", "unicode-width", @@ -3815,7 +3713,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "hashbrown 0.17.1", "indoc", "instability", @@ -3850,14 +3748,14 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", ] [[package]] name = "regex" -version = "1.13.1" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -3867,9 +3765,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3947,7 +3845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ "hashbrown 0.16.1", - "thiserror 2.0.19", + "thiserror 2.0.18", ] [[package]] @@ -3956,7 +3854,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -3995,7 +3893,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -4004,9 +3902,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", "log", @@ -4118,15 +4016,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "scheduled-thread-pool" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" -dependencies = [ - "parking_lot", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -4145,7 +4034,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4181,9 +4070,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -4191,22 +4080,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 3.0.1", + "syn 2.0.118", ] [[package]] @@ -4249,9 +4138,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.7" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -4370,15 +4259,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simd_cesu8" -version = "1.2.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" dependencies = [ "rustc_version", "simdutf8", @@ -4420,9 +4309,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.5" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -4445,7 +4334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", - "der 0.8.1", + "der 0.8.0", ] [[package]] @@ -4508,7 +4397,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -4520,7 +4409,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -4542,20 +4431,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.119" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -4579,7 +4457,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -4602,7 +4480,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4636,7 +4514,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "parking_lot", "rustix", "signal-hook", @@ -4672,7 +4550,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", - "bitflags 2.13.1", + "bitflags 2.13.0", "fancy-regex", "filedescriptor", "finl_unicode", @@ -4717,11 +4595,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.18", ] [[package]] @@ -4732,18 +4610,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 3.0.1", + "syn 2.0.118", ] [[package]] @@ -4790,9 +4668,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -4805,9 +4683,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -4815,20 +4693,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.5", + "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.7.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -4853,9 +4731,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.30.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17a073bfed563fa236697a068031408a93cd9522e08abf9933ead3e73411bd71" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ "futures-util", "log", @@ -4899,7 +4777,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "bytes", "futures-util", "http 1.4.2", @@ -4943,7 +4821,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -4963,9 +4841,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.30.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ "bytes", "data-encoding", @@ -4973,9 +4851,9 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.10.2", - "sha1 0.11.0", - "thiserror 2.0.19", + "rand 0.9.4", + "sha1 0.10.6", + "thiserror 2.0.18", ] [[package]] @@ -5085,9 +4963,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.24.0" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ "atomic", "getrandom 0.4.3", @@ -5219,7 +5097,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -5236,13 +5114,7 @@ dependencies = [ name = "web-server" version = "0.1.0" dependencies = [ - "bytes", - "http 1.4.2", - "iota-logger", - "iota-state", - "iota-util", "mtp", - "tokio", ] [[package]] @@ -5294,7 +5166,7 @@ dependencies = [ "once_cell", "open", "pnet", - "rand 0.8.7", + "rand 0.8.6", "rand_core 0.6.4", "ratatui", "reqwest", @@ -5318,9 +5190,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.9" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] @@ -5481,7 +5353,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -5492,7 +5364,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -5657,8 +5529,8 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "sha2 0.11.0", - "socket2 0.6.5", - "thiserror 2.0.19", + "socket2 0.6.4", + "thiserror 2.0.18", "time", "tokio", "tracing", @@ -5675,7 +5547,7 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror 2.0.19", + "thiserror 2.0.18", "url", ] @@ -5717,7 +5589,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror 2.0.19", + "thiserror 2.0.18", "time", ] @@ -5750,28 +5622,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -5791,7 +5663,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "synstructure", ] @@ -5812,7 +5684,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -5845,7 +5717,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -5868,7 +5740,7 @@ dependencies = [ "memchr", "pbkdf2", "ppmd-rust", - "sha1 0.10.7", + "sha1 0.10.6", "time", "zeroize", "zopfli", @@ -5877,15 +5749,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.6" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" +checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5" [[package]] name = "zmij" -version = "1.0.23" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index ff17473..c62208e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,3 @@ [workspace] -members = [ - "iota-storage", - "iota-connection", - "client", - "iota-auth", - "other-iota", - "iota-updater", - "iota-terms", - "iota-state", - "iota-cli", - "iota-core", - "omikron-connector", - "web-server", - "web-ui", - "iota-logger", - "iota-util", -] +members = ["iota-storage", "client", "iota-auth", "other-iota", "iota-updater", "iota-terms", "iota-state", "iota-cli", "iota-core", "omikron-connector", "web-server", "web-ui", "iota-logger", "iota-util"] resolver = "3" diff --git a/client/Cargo.toml b/client/Cargo.toml index bdf08ad..c29f662 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -5,7 +5,6 @@ edition = "2024" [dependencies] mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = ["client"] } -iota-connection = { path = "../iota-connection" } iota-logger = { path = "../iota-logger" } iota-util = { path = "../iota-util" } iota-storage = { path = "../iota-storage" } diff --git a/client/src/client_connection.rs b/client/src/client_connection.rs index 95c66cc..774119f 100644 --- a/client/src/client_connection.rs +++ b/client/src/client_connection.rs @@ -1,22 +1,160 @@ use dashmap::DashMap; -use iota_connection::message_common::*; -use iota_connection::message_handlers; use iota_logger::{log_cv_in, log_cv_out, log_t}; use iota_state::SHUTDOWN; -use iota_storage::util::chat_files::{self, MessageState, change_message_state}; +use iota_storage::users::contact::Contact; +use iota_storage::util::chat_files::{MessageState, change_message_state}; +use iota_storage::util::chats_util::{get_user, mod_user}; +use iota_storage::util::communities_util::CommunitiesUtil; use iota_storage::util::config_util::CONFIG; -use iota_storage::util::e2ee_storage::{self, StoredChatSecret}; +use iota_storage::util::e2ee_storage::{self, ChatSecretQuery, StoredChatSecret}; +use iota_storage::util::{chat_files, chats_util}; use iota_util::crypto_helper::keyring_from_base64; use iota_util::crypto_util::{self}; +use iota_util::file_util::{get_children, load_file, save_file}; use mtp::client::{Receiver, Sender}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::type_map::TypeMap; use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::sync::{Mutex, RwLock, mpsc, watch}; use tokio::task::JoinHandle; use uuid::Uuid; +fn typed_container(items: Vec<(DataType, DataValue)>) -> DataValue { + use mtp::type_map::{DataTypeId, TypeMap}; + let tm = TypeMap::latest(); + DataValue::Container( + items + .into_iter() + .filter_map(|(dt, dv)| tm.data_id_enum(dt).map(|id| (DataTypeId(id), dv))) + .collect(), + ) +} + +fn data_string(cv: &CommunicationValue, dt: DataType) -> Option { + cv.get_data(dt) + .as_str() + .map(|s| s.to_string()) + .or_else(|| cv.get_data(dt).as_number().map(|n| n.to_string())) + .or_else(|| cv.get_data(dt).as_signed_number().map(|n| n.to_string())) +} + +fn data_i64(cv: &CommunicationValue, dt: DataType) -> Option { + cv.get_data(dt) + .as_number() + .and_then(|n| i64::try_from(n).ok()) + .or_else(|| { + cv.get_data(dt) + .as_signed_number() + .and_then(|n| i64::try_from(n).ok()) + }) + .or_else(|| cv.get_data(dt).as_str().and_then(|s| s.parse::().ok())) +} + +#[derive(Debug, Clone)] +struct ChatSecretRecipient { + user_id: String, + encrypted_secret: Vec, + kem_ciphertext: Vec, +} + +fn recipient_from_value(value: &DataValue) -> Option { + let tm = TypeMap::latest(); + let user_id = value + .get_field(DataType::UserId.to_id(&tm))? + .as_str() + .map(|s| s.to_string()) + .or_else(|| { + value + .get_field(DataType::UserId.to_id(&tm))? + .as_number() + .map(|n| n.to_string()) + })?; + let encrypted_secret = value + .get_field(DataType::EncryptedSecret.to_id(&tm))? + .as_bytes()?; + let kem_ciphertext = value + .get_field(DataType::KemCiphertext.to_id(&tm))? + .as_bytes()?; + + Some(ChatSecretRecipient { + user_id, + encrypted_secret, + kem_ciphertext, + }) +} + +fn chat_secret_recipients(cv: &CommunicationValue) -> Option> { + let recipients = cv.get_data(DataType::Recipients).as_array()?; + let parsed = recipients + .iter() + .map(recipient_from_value) + .collect::>>()?; + + if parsed.is_empty() { + None + } else { + Some(parsed) + } +} + +fn set_chat_secret_cv_for_recipient( + source: &CommunicationValue, + recipient: &ChatSecretRecipient, +) -> CommunicationValue { + let recipient_value = typed_container(vec![ + (DataType::UserId, DataValue::Str(recipient.user_id.clone())), + ( + DataType::EncryptedSecret, + DataValue::Bytes(recipient.encrypted_secret.clone()), + ), + ( + DataType::KemCiphertext, + DataValue::Bytes(recipient.kem_ciphertext.clone()), + ), + ]); + + CommunicationValue::new(CommunicationType::SetChatSecret) + .with_id(source.get_id()) + .with_sender(source.get_sender()) + .with_receiver(recipient.user_id.parse::().unwrap_or(0)) + .add_typed_default(DataType::ChatId, source.get_data(DataType::ChatId).clone()) + .add_typed_default( + DataType::SecretId, + source.get_data(DataType::SecretId).clone(), + ) + .add_typed_default( + DataType::VersionNumber, + source.get_data(DataType::VersionNumber).clone(), + ) + .add_typed_default( + DataType::WrappingScheme, + source.get_data(DataType::WrappingScheme).clone(), + ) + .add_typed_default( + DataType::CreatedAt, + source.get_data(DataType::CreatedAt).clone(), + ) + .add_typed_default( + DataType::Recipients, + DataValue::Array(vec![recipient_value]), + ) +} + +fn now_millis_i64() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64 +} + +fn error_response(request: &CommunicationValue, ty: CommunicationType) -> CommunicationValue { + CommunicationValue::new(ty) + .with_id(request.get_id()) + .with_receiver(request.get_sender()) +} + // ============================================================================ // Waiting Task System // ============================================================================ @@ -85,7 +223,7 @@ impl ClientConnection { } if let Some(sender) = self.sender.read().await.as_ref() { - sender.close().await; + sender.close(); } *self.sender.write().await = None; @@ -97,9 +235,12 @@ impl ClientConnection { async fn handle_ping(self: Arc, cv: CommunicationValue) { // Update our ping if provided if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) { - let current = now_millis_i64(); + let current = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(); let mut ping_guard = self.ping.write().await; - *ping_guard = current - *last_ping as i64; + *ping_guard = current as i64 - *last_ping as i64; } // Send pong response @@ -183,8 +324,70 @@ impl ClientConnection { } if cv.is_type(CommunicationType::GetChatSecret) { - self.send_message(&message_handlers::handle_get_chat_secret(&cv)) - .await; + let Some(user_id) = data_string(&cv, DataType::UserId) else { + self.send_message(&error_response(&cv, CommunicationType::ErrorInvalidData)) + .await; + return; + }; + let sender_id = cv.get_sender().to_string(); + if user_id != sender_id { + self.send_message(&error_response(&cv, CommunicationType::ErrorNotFound)) + .await; + return; + } + let Some(chat_id) = data_string(&cv, DataType::ChatId) else { + self.send_message(&error_response(&cv, CommunicationType::ErrorInvalidData)) + .await; + return; + }; + + match e2ee_storage::get_chat_secret(ChatSecretQuery { + user_id, + chat_id, + secret_id: data_string(&cv, DataType::SecretId), + }) { + Ok(Some(record)) => { + let response = CommunicationValue::new(CommunicationType::ChatSecretResponse) + .with_id(cv.get_id()) + .with_receiver(cv.get_sender()) + .add_typed_default(DataType::UserId, DataValue::Str(record.user_id)) + .add_typed_default(DataType::ChatId, DataValue::Str(record.chat_id)) + .add_typed_default(DataType::SecretId, DataValue::Str(record.secret_id)) + .add_typed_default( + DataType::VersionNumber, + DataValue::SignedNumber(record.version as i128), + ) + .add_typed_default( + DataType::EncryptedSecret, + DataValue::Bytes(record.encrypted_secret), + ) + .add_typed_default( + DataType::KemCiphertext, + DataValue::Bytes(record.kem_ciphertext), + ) + .add_typed_default( + DataType::WrappingScheme, + DataValue::Str(record.wrapping_scheme), + ) + .add_typed_default( + DataType::CreatedAt, + DataValue::SignedNumber(record.created_at as i128), + ) + .add_typed_default( + DataType::UpdatedAt, + DataValue::SignedNumber(record.updated_at as i128), + ); + self.send_message(&response).await; + } + Ok(None) => { + self.send_message(&error_response(&cv, CommunicationType::ErrorNotSet)) + .await; + } + Err(_) => { + self.send_message(&error_response(&cv, CommunicationType::ErrorInvalidData)) + .await; + } + } return; } @@ -231,20 +434,132 @@ impl ClientConnection { } if cv.is_type(CommunicationType::CreateApp) { - self.send_message(&message_handlers::handle_create_app(&cv)) - .await; + let sender_id = cv.get_sender() as i64; + let app_identifier = cv + .get_data(DataType::AppIdentifier) + .as_str() + .unwrap_or("") + .to_string(); + let app_public_key = cv + .get_data(DataType::AppPublicKey) + .as_str() + .unwrap_or("") + .to_string(); + + if !app_identifier.is_empty() && !app_public_key.is_empty() { + if let Some(mut user) = iota_storage::users::user_manager::get_user(sender_id) { + if !user.trusted_apps.contains_key(&app_identifier) { + user.trusted_apps.insert(app_identifier, app_public_key); + iota_storage::users::user_manager::update_user(user); + } + } + } + + let res = CommunicationValue::new(CommunicationType::CreateApp) + .with_id(cv.get_id()) + .with_receiver(sender_id as u64); + self.send_message(&res).await; return; } if cv.is_type(CommunicationType::DeleteApp) { - self.send_message(&message_handlers::handle_delete_app(&cv)) - .await; + let sender_id = cv.get_sender() as i64; + let app_identifier = cv + .get_data(DataType::AppIdentifier) + .as_str() + .unwrap_or("") + .to_string(); + + if !app_identifier.is_empty() { + if let Some(mut user) = iota_storage::users::user_manager::get_user(sender_id) { + if user.trusted_apps.contains_key(&app_identifier) { + user.trusted_apps.remove(&app_identifier); + iota_storage::users::user_manager::update_user(user); + } + } + } + + let res = CommunicationValue::new(CommunicationType::DeleteApp) + .with_id(cv.get_id()) + .with_receiver(sender_id as u64); + self.send_message(&res).await; return; } if cv.is_type(CommunicationType::ClientConnected) { - self.send_message(&message_handlers::handle_client_connected(&cv)) - .await; + let user_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0) as i64; + let _session_id = cv.get_data(DataType::SessionId).as_number().unwrap_or(0) as i64; + + let contacts = chats_util::get_users(user_id); + let mut contacts_array = Vec::new(); + + for (i, contact) in contacts.iter().enumerate() { + let mut contact_container = Vec::new(); + contact_container.push(( + DataType::UserId, + DataValue::SignedNumber(contact.user_id as i128), + )); + contact_container.push(( + DataType::LastMessageAt, + DataValue::SignedNumber(contact.last_message_at.unwrap_or(0) as i128), + )); + + if let Some(ref name) = contact.user_name { + contact_container.push((DataType::Username, DataValue::Str(name.clone()))); + } + + let amount = if i < 10 { 20 } else { 1 }; + let messages = chat_files::get_messages(user_id, contact.user_id, 0, amount); + + let mut msg_array = Vec::new(); + for m in messages.members() { + let message_time = m["message_time"].as_i64().unwrap_or(0); + let content = m["content"].as_str().unwrap_or("").to_string(); + let sent_by_self = m["sent_by_self"].as_bool().unwrap_or(false); + let height = m["height"].as_i64().unwrap_or(0); + let message_state = m["message_state"].as_str().unwrap_or("").to_string(); + + let mut msg_container = Vec::new(); + msg_container.push(( + DataType::SendTime, + DataValue::SignedNumber(message_time as i128), + )); + msg_container.push((DataType::Content, DataValue::Str(content.clone()))); + msg_container.push((DataType::MessageState, DataValue::Str(message_state))); + msg_container.push((DataType::Height, DataValue::SignedNumber(height as i128))); + msg_container.push(( + DataType::SenderId, + DataValue::UnsignedNumber(if sent_by_self { + user_id as u128 + } else { + contact.user_id as u128 + }), + )); + msg_array.push(typed_container(msg_container)); + + if msg_array.len() == 1 { + let sender_id = if sent_by_self { + user_id + } else { + contact.user_id + }; + let mut last_msg = Vec::new(); + last_msg.push((DataType::Content, DataValue::Str(content))); + last_msg.push(( + DataType::SenderId, + DataValue::SignedNumber(sender_id as i128), + )); + contact_container.push((DataType::LastMessage, typed_container(last_msg))); + } + } + contact_container.push((DataType::Messages, DataValue::Array(msg_array))); + contacts_array.push(typed_container(contact_container)); + } + + let resp = CommunicationValue::new(CommunicationType::ClientConnected) + .with_id(cv.get_id()) + .add_typed_default(DataType::Contacts, DataValue::Array(contacts_array)); + self.send_message(&resp).await; return; } @@ -253,32 +568,32 @@ impl ClientConnection { // ************************************************ // if cv.is_type(CommunicationType::MessageState) { - message_handlers::handle_message_state(&cv); - return; - } + let sender_id = &cv.get_sender(); + let receiver_id = match cv.get_data(DataType::ChatPartnerId).as_number() { + Some(id) => id, + _ => return, + }; - if cv.is_type(CommunicationType::MessageEdit) { - self.send_message(&message_handlers::handle_message_edit(&cv)) - .await; - return; - } + // Parse send_time robustly: accept numeric or string, fallback to current time + let send_time_val = cv.get_data(DataType::SendTime); + let now_i64 = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64; + let timestamp_i64 = if let Some(n) = send_time_val.as_number() { + n as i64 + } else if let Some(s) = send_time_val.as_str() { + s.parse::().unwrap_or(now_i64) + } else { + now_i64 + }; - if cv.is_type(CommunicationType::MessageReactionAdd) { - self.send_message(&message_handlers::handle_message_reaction(&cv, true)) - .await; - return; - } - - if cv.is_type(CommunicationType::MessageReactionRemove) { - self.send_message(&message_handlers::handle_message_reaction(&cv, false)) - .await; - return; - } - - if cv.is_type(CommunicationType::MessageDeleteLive) { - self.send_message(&message_handlers::handle_message_delete(&cv)) - .await; - return; + let _ = chat_files::change_message_state( + timestamp_i64, + receiver_id as i64, + *sender_id as i64, + MessageState::from_str(cv.get_data(DataType::MessageState).as_str().unwrap_or("")), + ); } // Incoming storsed message: store for the recipient, attempt local delivery, notify sender. @@ -294,7 +609,10 @@ impl ClientConnection { // parse send_time safely (number or string), fallback to now let send_time_val = cv.get_data(DataType::SendTime); - let now_i64 = now_millis_i64(); + let now_i64 = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64; let timestamp = if let Some(n) = send_time_val.as_number() { n as i64 } else if let Some(s) = send_time_val.as_str() { @@ -420,38 +738,186 @@ impl ClientConnection { } if cv.is_type(CommunicationType::MessagesGet) { - self.send_message(&message_handlers::handle_messages_get(&cv)) - .await; + let my_id = cv.get_sender(); + let partner_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0); + let offset = cv.get_data(DataType::Offset).as_number().unwrap_or(0); + let amount = cv.get_data(DataType::Amount).as_number().unwrap_or(0); + let messages = chat_files::get_messages( + my_id as i64, + partner_id as i64, + offset as i64, + amount as i64, + ); + let mut msg_array: Vec = Vec::new(); + for m in messages.members() { + let message_time: i64 = m["message_time"].as_i64().unwrap_or(0); + let content: String = m["content"].as_str().unwrap_or("").to_string(); + let sent_by_self: bool = m["sent_by_self"].as_bool().unwrap_or(false); + let height: i64 = m["height"].as_i64().unwrap_or(0); + let sender_id: i64 = if sent_by_self { + my_id as i64 + } else { + if let Some(n) = cv.get_data(DataType::ChatPartnerId).as_number() { + n as i64 + } else if let Some(s) = cv.get_data(DataType::ChatPartnerId).as_str() { + s.parse::().unwrap_or(partner_id as i64) + } else { + partner_id as i64 + } + }; + let message_state: String = m["message_state"].as_str().unwrap_or("").to_string(); + + let mut container = Vec::new(); + container.push(( + DataType::SendTime, + DataValue::SignedNumber(message_time as i128), + )); + container.push((DataType::Content, DataValue::Str(content))); + container.push(( + DataType::SenderId, + DataValue::SignedNumber(sender_id as i128), + )); + container.push((DataType::MessageState, DataValue::Str(message_state))); + container.push((DataType::Height, DataValue::SignedNumber(height as i128))); + container.push(( + DataType::SenderId, + DataValue::UnsignedNumber(if sent_by_self { + my_id as u128 + } else { + partner_id as u128 + }), + )); + msg_array.push(typed_container(container)); + } + + let resp = CommunicationValue::new(CommunicationType::MessagesGet) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default(DataType::Messages, DataValue::Array(msg_array)); + + self.send_message(&resp).await; return; } if cv.is_type(CommunicationType::GetChats) { - self.send_message(&message_handlers::handle_get_chats(&cv)) - .await; + let user_id = cv.get_sender(); + let users = chats_util::get_users(user_id as i64); + let mut user_array = Vec::new(); + for user in users { + let mut container = Vec::new(); + container.push(( + DataType::UserId, + DataValue::SignedNumber(user.user_id as i128), + )); + if let Some(name) = user.user_name { + container.push((DataType::Username, DataValue::Str(name))); + } + if let Some(ts) = user.last_message_at { + container.push((DataType::LastMessageAt, DataValue::SignedNumber(ts as i128))); + } + user_array.push(typed_container(container)); + } + let resp = CommunicationValue::new(CommunicationType::GetChats) + .with_id(cv.get_id()) + .with_receiver(user_id) + .add_typed_default(DataType::UserIds, DataValue::Array(user_array)); + self.send_message(&resp).await; return; } if cv.is_type(CommunicationType::AddConversation) { - self.send_message(&message_handlers::handle_add_conversation(&cv)) - .await; + let user_id = cv.get_sender(); + let other_id = match cv.get_data(DataType::ChatPartnerId).as_number() { + Some(n) => n as i64, + None => cv + .get_data(DataType::ChatPartnerId) + .as_str() + .unwrap_or("0") + .parse() + .unwrap_or(0), + }; + let mut contact = get_user(user_id as i64, other_id).unwrap_or(Contact::new(other_id)); + + if let Some(name) = cv.get_data(DataType::ChatPartnerName).as_str() { + contact.user_name = Some(name.to_string()); + } + + contact.set_last_message_at( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64, + ); + mod_user(user_id as i64, &contact); + let resp = CommunicationValue::new(CommunicationType::AddConversation) + .with_id(cv.get_id()) + .with_receiver(user_id); + self.send_message(&resp).await; return; } if cv.is_type(CommunicationType::AddCommunity) { - self.send_message(&message_handlers::handle_add_community(&cv)) - .await; + CommunitiesUtil::add_community( + cv.get_sender() as i64, + cv.get_data(DataType::CommunityAddress) + .as_str() + .unwrap() + .to_string(), + cv.get_data(DataType::CommunityTitle) + .as_str() + .unwrap() + .to_string(), + cv.get_data(DataType::Position) + .as_str() + .unwrap() + .to_string(), + ); + let resp = CommunicationValue::new(CommunicationType::AddCommunity) + .with_id(cv.get_id()) + .with_receiver(cv.get_sender()); + self.send_message(&resp).await; return; } if cv.is_type(CommunicationType::GetCommunities) { - self.send_message(&message_handlers::handle_get_communities(&cv)) - .await; + let mut comm_array = Vec::new(); + for c in CommunitiesUtil::get_communities(cv.get_sender() as i64) { + let mut container: Vec<(DataType, DataValue)> = Vec::new(); + if let Some(address) = c["address"].as_str() { + container.push(( + DataType::CommunityAddress, + DataValue::Str(address.to_string()), + )); + } + if let Some(title) = c["title"].as_str() { + container.push((DataType::CommunityTitle, DataValue::Str(title.to_string()))); + } + if let Some(position) = c["position"].as_str() { + container.push((DataType::Position, DataValue::Str(position.to_string()))); + } + comm_array.push(typed_container(container)); + } + + let resp = CommunicationValue::new(CommunicationType::GetCommunities) + .with_id(cv.get_id()) + .with_receiver(cv.get_sender()) + .add_typed_default(DataType::Communities, DataValue::Array(comm_array)); + self.send_message(&resp).await; return; } if cv.is_type(CommunicationType::RemoveCommunity) { - self.send_message(&message_handlers::handle_remove_community(&cv)) - .await; + CommunitiesUtil::remove_community( + cv.get_sender() as i64, + cv.get_data(DataType::CommunityAddress) + .as_str() + .unwrap() + .to_string(), + ); + let resp = CommunicationValue::new(CommunicationType::RemoveCommunity) + .with_id(cv.get_id()) + .with_receiver(cv.get_sender()); + self.send_message(&resp).await; return; } @@ -460,11 +926,10 @@ impl ClientConnection { let settings_name = cv.get_data(DataType::SettingsName).as_str().unwrap(); let settings_value = cv.get_data(DataType::Payload).as_str().unwrap(); - let _ = iota_storage::util::settings::save( - my_id as i64, - iota_storage::util::settings::GLOBAL_SESSION_ID, - settings_name, - settings_value, + save_file( + &format!("users/{}/settings/", my_id), + &format!("{}.settings", settings_name), + &settings_value, ); let response = CommunicationValue::new(CommunicationType::SettingsSave) @@ -478,14 +943,10 @@ impl ClientConnection { if cv.is_type(CommunicationType::SettingsLoad) { let my_id = cv.get_sender(); let settings_name = cv.get_data(DataType::SettingsName).as_string().unwrap(); - let settings_value_str = iota_storage::util::settings::load( - my_id as i64, - iota_storage::util::settings::GLOBAL_SESSION_ID, - &settings_name, - ) - .ok() - .flatten() - .unwrap_or_default(); + let settings_value_str = load_file( + &format!("users/{}/settings/", my_id), + &format!("{}.settings", settings_name), + ); let response = CommunicationValue::new(CommunicationType::SettingsLoad) .with_id(cv.get_id()) .with_receiver(my_id) @@ -498,12 +959,15 @@ impl ClientConnection { if cv.is_type(CommunicationType::SettingsList) { let my_id = cv.get_sender(); - let settings = iota_storage::util::settings::list( - my_id as i64, - iota_storage::util::settings::GLOBAL_SESSION_ID, - ) - .unwrap_or_default(); - let settings_json = settings.into_iter().map(DataValue::Str).collect(); + let settings = get_children(&format!("users/{}/settings/", my_id)); + let mut settings_json = Vec::new(); + for s in settings { + let s = s.replace(".settings", ""); + if s.is_empty() { + continue; + } + let _ = settings_json.push(DataValue::Str(s)); + } let response = CommunicationValue::new(CommunicationType::SettingsList) .with_id(cv.get_id()) .with_receiver(my_id) @@ -550,7 +1014,7 @@ impl ClientConnection { if !sender.is_open() { drop(sender_guard); if let Some(sender) = self.sender.write().await.take() { - sender.close().await; + sender.close(); } return Err("connection closed".to_string()); } diff --git a/iota-cli/src/elements/console_card.rs b/iota-cli/src/elements/console_card.rs index 820ae1e..6e9a7a0 100644 --- a/iota-cli/src/elements/console_card.rs +++ b/iota-cli/src/elements/console_card.rs @@ -444,7 +444,7 @@ pub async fn run_command(command: &str) { if let Some(user) = user_manager::get_user_by_username(username) { let msg = CommunicationValue::new(CommunicationType::DeleteUser) .with_sender(user.user_id as u64); - let _ = OMIKRON_CONNECTION.send_message(&msg).await; + OMIKRON_CONNECTION.send_message(&msg).await; user_manager::remove_user(user.user_id); log!("Removed user {}", user.user_id); } else { diff --git a/iota-connection/Cargo.toml b/iota-connection/Cargo.toml deleted file mode 100644 index 65eafad..0000000 --- a/iota-connection/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "iota-connection" -version = "0.1.0" -edition = "2024" - -[dependencies] -iota-storage = { path = "../iota-storage" } -iota-util = { path = "../iota-util" } -mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } diff --git a/iota-connection/src/connection_handler.rs b/iota-connection/src/connection_handler.rs deleted file mode 100644 index 6a20bb1..0000000 --- a/iota-connection/src/connection_handler.rs +++ /dev/null @@ -1,36 +0,0 @@ -use mtp::codec::CommunicationValue; -use std::future::Future; -use std::time::Duration; - -/// Unified interface for all connection types (Omikron, Direct, future modes). -/// -/// Provides the common messaging API that the rest of the codebase uses, -/// regardless of whether the connection goes through Omikron or is direct. -pub trait ConnectionHandler: Send + Sync { - /// Send a message to the remote end. - fn send_message( - &self, - cv: &CommunicationValue, - ) -> impl Future> + Send; - - /// Send a message and wait for a correlated response. - /// - /// The implementation correlates requests/responses by message ID and - /// enforces the given `timeout`. Returns an error on timeout or if the - /// connection drops while waiting. - fn await_response( - &self, - cv: &CommunicationValue, - timeout: Option, - ) -> impl Future> + Send; - - /// Returns `true` when the connection is alive and ready for traffic. - fn is_connected(&self) -> impl Future + Send; - - /// Returns `true` when the connection has completed identification / - /// registration and is fully operational. - fn is_identified(&self) -> impl Future + Send; - - /// Gracefully tear down the connection. - fn stop(&self) -> impl Future + Send; -} diff --git a/iota-connection/src/lib.rs b/iota-connection/src/lib.rs deleted file mode 100644 index 601ca7b..0000000 --- a/iota-connection/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod connection_handler; -pub mod message_common; -pub mod message_handlers; diff --git a/iota-connection/src/message_common.rs b/iota-connection/src/message_common.rs deleted file mode 100644 index 2ab9733..0000000 --- a/iota-connection/src/message_common.rs +++ /dev/null @@ -1,137 +0,0 @@ -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use mtp::type_map::TypeMap; -use std::time::{SystemTime, UNIX_EPOCH}; - -pub fn typed_container(items: Vec<(DataType, DataValue)>) -> DataValue { - use mtp::type_map::{DataTypeId, TypeMap}; - let tm = TypeMap::latest(); - DataValue::Container( - items - .into_iter() - .filter_map(|(dt, dv)| tm.data_id_enum(dt).map(|id| (DataTypeId(id), dv))) - .collect(), - ) -} - -pub fn data_string(cv: &CommunicationValue, dt: DataType) -> Option { - cv.get_data(dt) - .as_str() - .map(|s| s.to_string()) - .or_else(|| cv.get_data(dt).as_number().map(|n| n.to_string())) - .or_else(|| cv.get_data(dt).as_signed_number().map(|n| n.to_string())) -} - -pub fn data_i64(cv: &CommunicationValue, dt: DataType) -> Option { - cv.get_data(dt) - .as_number() - .and_then(|n| i64::try_from(n).ok()) - .or_else(|| { - cv.get_data(dt) - .as_signed_number() - .and_then(|n| i64::try_from(n).ok()) - }) - .or_else(|| cv.get_data(dt).as_str().and_then(|s| s.parse::().ok())) -} - -#[derive(Debug, Clone)] -pub struct ChatSecretRecipient { - pub user_id: String, - pub encrypted_secret: Vec, - pub kem_ciphertext: Vec, -} - -pub fn recipient_from_value(value: &DataValue) -> Option { - let tm = TypeMap::latest(); - let user_id = value - .get_field(DataType::UserId.try_to_id(&tm)?)? - .as_str() - .map(|s| s.to_string()) - .or_else(|| { - value - .get_field(DataType::UserId.try_to_id(&tm)?)? - .as_number() - .map(|n| n.to_string()) - })?; - let encrypted_secret = value - .get_field(DataType::EncryptedSecret.try_to_id(&tm)?)? - .as_bytes()?; - let kem_ciphertext = value - .get_field(DataType::KemCiphertext.try_to_id(&tm)?)? - .as_bytes()?; - - Some(ChatSecretRecipient { - user_id, - encrypted_secret, - kem_ciphertext, - }) -} - -pub fn chat_secret_recipients(cv: &CommunicationValue) -> Option> { - let recipients = cv.get_data(DataType::Recipients).as_array()?; - let parsed = recipients - .iter() - .map(recipient_from_value) - .collect::>>()?; - - if parsed.is_empty() { - None - } else { - Some(parsed) - } -} - -pub fn set_chat_secret_cv_for_recipient( - source: &CommunicationValue, - recipient: &ChatSecretRecipient, -) -> CommunicationValue { - let recipient_value = typed_container(vec![ - (DataType::UserId, DataValue::Str(recipient.user_id.clone())), - ( - DataType::EncryptedSecret, - DataValue::Bytes(recipient.encrypted_secret.clone()), - ), - ( - DataType::KemCiphertext, - DataValue::Bytes(recipient.kem_ciphertext.clone()), - ), - ]); - - CommunicationValue::new(CommunicationType::SetChatSecret) - .with_id(source.get_id()) - .with_sender(source.get_sender()) - .with_receiver(recipient.user_id.parse::().unwrap_or(0)) - .add_typed_default(DataType::ChatId, source.get_data(DataType::ChatId).clone()) - .add_typed_default( - DataType::SecretId, - source.get_data(DataType::SecretId).clone(), - ) - .add_typed_default( - DataType::VersionNumber, - source.get_data(DataType::VersionNumber).clone(), - ) - .add_typed_default( - DataType::WrappingScheme, - source.get_data(DataType::WrappingScheme).clone(), - ) - .add_typed_default( - DataType::CreatedAt, - source.get_data(DataType::CreatedAt).clone(), - ) - .add_typed_default( - DataType::Recipients, - DataValue::Array(vec![recipient_value]), - ) -} - -pub fn now_millis_i64() -> i64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_millis() as i64 -} - -pub fn error_response(request: &CommunicationValue, ty: CommunicationType) -> CommunicationValue { - CommunicationValue::new(ty) - .with_id(request.get_id()) - .with_receiver(request.get_sender()) -} diff --git a/iota-connection/src/message_handlers.rs b/iota-connection/src/message_handlers.rs deleted file mode 100644 index b3c08a9..0000000 --- a/iota-connection/src/message_handlers.rs +++ /dev/null @@ -1,766 +0,0 @@ -use crate::message_common::*; -use iota_storage::util::chat_files::{self, MessageState}; -use iota_storage::util::chats_util::{self, get_user, mod_user}; -use iota_storage::util::communities_util::CommunitiesUtil; -use iota_storage::util::e2ee_storage::{self, ChatSecretQuery}; -use iota_storage::util::settings; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; - -pub struct MessageMutation { - pub sender_id: i64, - pub partner_id: i64, - pub send_time: i64, -} - -pub fn message_mutation(cv: &CommunicationValue) -> Result { - let sender_id = i64::try_from(cv.get_sender()) - .map_err(|_| error_response(cv, CommunicationType::ErrorInvalidData))?; - let partner_id = data_i64(cv, DataType::ChatPartnerId) - .filter(|id| *id > 0) - .ok_or_else(|| error_response(cv, CommunicationType::ErrorInvalidData))?; - let send_time = data_i64(cv, DataType::SendTime) - .filter(|time| *time > 0) - .ok_or_else(|| error_response(cv, CommunicationType::ErrorInvalidData))?; - - Ok(MessageMutation { - sender_id, - partner_id, - send_time, - }) -} - -pub fn success_response(cv: &CommunicationValue) -> CommunicationValue { - error_response(cv, CommunicationType::Success) -} - -pub fn handle_message_edit(cv: &CommunicationValue) -> CommunicationValue { - let mutation = match message_mutation(cv) { - Ok(mutation) => mutation, - Err(response) => return response, - }; - let Some(content) = cv.get_data(DataType::Content).as_str() else { - return error_response(cv, CommunicationType::ErrorInvalidData); - }; - - match chat_files::edit_message( - mutation.sender_id, - mutation.partner_id, - mutation.send_time, - mutation.sender_id, - content, - ) { - Ok(()) => success_response(cv), - Err(_) => error_response(cv, CommunicationType::ErrorNotFound), - } -} - -pub fn handle_message_reaction(cv: &CommunicationValue, add: bool) -> CommunicationValue { - let mutation = match message_mutation(cv) { - Ok(mutation) => mutation, - Err(response) => return response, - }; - let Some(reaction) = cv.get_data(DataType::Reaction).as_str() else { - return error_response(cv, CommunicationType::ErrorInvalidData); - }; - if reaction.is_empty() || reaction.len() > 64 { - return error_response(cv, CommunicationType::ErrorInvalidData); - } - - let result = if add { - chat_files::add_reaction( - mutation.sender_id, - mutation.partner_id, - mutation.send_time, - mutation.sender_id, - reaction, - ) - } else { - chat_files::remove_reaction( - mutation.sender_id, - mutation.partner_id, - mutation.send_time, - mutation.sender_id, - reaction, - ) - }; - - match result { - Ok(()) => success_response(cv), - Err(_) => error_response(cv, CommunicationType::ErrorNotFound), - } -} - -pub fn handle_message_delete(cv: &CommunicationValue) -> CommunicationValue { - let mutation = match message_mutation(cv) { - Ok(mutation) => mutation, - Err(response) => return response, - }; - - match chat_files::delete_message(mutation.sender_id, mutation.partner_id, mutation.send_time) { - Ok(()) => success_response(cv), - Err(_) => error_response(cv, CommunicationType::ErrorNotFound), - } -} - -fn stored_message_value( - message: &chat_files::StoredMessage, - storage_owner: i64, - partner_id: i64, -) -> DataValue { - let mut fields = vec![ - ( - DataType::SendTime, - DataValue::SignedNumber(message.message_time as i128), - ), - (DataType::Content, DataValue::Str(message.content.clone())), - ( - DataType::MessageState, - DataValue::Str(message.message_state.clone()), - ), - ( - DataType::Height, - DataValue::SignedNumber(message.height as i128), - ), - ( - DataType::SenderId, - DataValue::UnsignedNumber(if message.sent_by_self { - storage_owner as u128 - } else { - partner_id as u128 - }), - ), - ]; - if message.edited { - fields.push((DataType::Edited, DataValue::Bool(true))); - } - if let Some(reply_to) = message.reply_to { - fields.push(( - DataType::ReplyId, - DataValue::UnsignedNumber(reply_to as u64 as u128), - )); - } - if !message.reactions.is_empty() { - let reactions = message - .reactions - .iter() - .map(|reaction| { - typed_container(vec![ - ( - DataType::Reaction, - DataValue::Str(reaction.reaction.clone()), - ), - ( - DataType::SenderId, - DataValue::SignedNumber(reaction.user_id as i128), - ), - ]) - }) - .collect(); - fields.push((DataType::Reactions, DataValue::Array(reactions))); - } - typed_container(fields) -} - -pub fn handle_get_chat_secret(cv: &CommunicationValue) -> CommunicationValue { - let Some(user_id) = data_string(cv, DataType::UserId) else { - return error_response(cv, CommunicationType::ErrorInvalidData); - }; - if user_id != cv.get_sender().to_string() { - return error_response(cv, CommunicationType::ErrorNotFound); - } - let Some(chat_id) = data_string(cv, DataType::ChatId) else { - return error_response(cv, CommunicationType::ErrorInvalidData); - }; - - match e2ee_storage::get_chat_secret(ChatSecretQuery { - user_id, - chat_id, - secret_id: data_string(cv, DataType::SecretId), - }) { - Ok(Some(record)) => CommunicationValue::new(CommunicationType::ChatSecretResponse) - .with_id(cv.get_id()) - .with_receiver(cv.get_sender()) - .add_typed_default(DataType::UserId, DataValue::Str(record.user_id)) - .add_typed_default(DataType::ChatId, DataValue::Str(record.chat_id)) - .add_typed_default(DataType::SecretId, DataValue::Str(record.secret_id)) - .add_typed_default( - DataType::VersionNumber, - DataValue::SignedNumber(record.version as i128), - ) - .add_typed_default( - DataType::EncryptedSecret, - DataValue::Bytes(record.encrypted_secret), - ) - .add_typed_default( - DataType::KemCiphertext, - DataValue::Bytes(record.kem_ciphertext), - ) - .add_typed_default( - DataType::WrappingScheme, - DataValue::Str(record.wrapping_scheme), - ) - .add_typed_default( - DataType::CreatedAt, - DataValue::SignedNumber(record.created_at as i128), - ) - .add_typed_default( - DataType::UpdatedAt, - DataValue::SignedNumber(record.updated_at as i128), - ), - Ok(None) => error_response(cv, CommunicationType::ErrorNotSet), - Err(_) => error_response(cv, CommunicationType::ErrorInvalidData), - } -} - -pub fn handle_create_app(cv: &CommunicationValue) -> CommunicationValue { - let sender_id = cv.get_sender() as i64; - let app_identifier = cv - .get_data(DataType::AppIdentifier) - .as_str() - .unwrap_or("") - .to_string(); - let app_public_key = cv - .get_data(DataType::AppPublicKey) - .as_str() - .unwrap_or("") - .to_string(); - - if !app_identifier.is_empty() && !app_public_key.is_empty() { - if let Some(mut user) = iota_storage::users::user_manager::get_user(sender_id) { - if !user.trusted_apps.contains_key(&app_identifier) { - user.trusted_apps.insert(app_identifier, app_public_key); - iota_storage::users::user_manager::update_user(user); - } - } - } - - CommunicationValue::new(CommunicationType::CreateApp) - .with_id(cv.get_id()) - .with_receiver(sender_id as u64) -} - -pub fn handle_delete_app(cv: &CommunicationValue) -> CommunicationValue { - let sender_id = cv.get_sender() as i64; - let app_identifier = cv - .get_data(DataType::AppIdentifier) - .as_str() - .unwrap_or("") - .to_string(); - - if !app_identifier.is_empty() { - if let Some(mut user) = iota_storage::users::user_manager::get_user(sender_id) { - if user.trusted_apps.contains_key(&app_identifier) { - user.trusted_apps.remove(&app_identifier); - iota_storage::users::user_manager::update_user(user); - } - } - } - - CommunicationValue::new(CommunicationType::DeleteApp) - .with_id(cv.get_id()) - .with_receiver(sender_id as u64) -} - -pub fn handle_client_connected(cv: &CommunicationValue) -> CommunicationValue { - let user_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0) as i64; - - let contacts = chats_util::get_users(user_id); - let mut contacts_array = Vec::new(); - - for (i, contact) in contacts.iter().enumerate() { - let mut contact_container = Vec::new(); - contact_container.push(( - DataType::UserId, - DataValue::SignedNumber(contact.user_id as i128), - )); - contact_container.push(( - DataType::LastMessageAt, - DataValue::SignedNumber(contact.last_message_at.unwrap_or(0) as i128), - )); - - if let Some(ref name) = contact.user_name { - contact_container.push((DataType::Username, DataValue::Str(name.clone()))); - } - - let amount = if i < 10 { 20 } else { 1 }; - let messages = chat_files::get_messages(user_id, contact.user_id, 0, amount); - - let mut msg_array = Vec::new(); - for m in &messages { - msg_array.push(stored_message_value(m, user_id, contact.user_id)); - - if msg_array.len() == 1 { - let sender_id = if m.sent_by_self { - user_id - } else { - contact.user_id - }; - let mut last_msg = Vec::new(); - last_msg.push((DataType::Content, DataValue::Str(m.content.clone()))); - last_msg.push(( - DataType::SenderId, - DataValue::SignedNumber(sender_id as i128), - )); - contact_container.push((DataType::LastMessage, typed_container(last_msg))); - } - } - contact_container.push((DataType::Messages, DataValue::Array(msg_array))); - contacts_array.push(typed_container(contact_container)); - } - - CommunicationValue::new(CommunicationType::ClientConnected) - .with_id(cv.get_id()) - .add_typed_default(DataType::Contacts, DataValue::Array(contacts_array)) -} - -pub fn handle_message_state(cv: &CommunicationValue) { - let sender_id = &cv.get_sender(); - let receiver_id = match cv.get_data(DataType::ChatPartnerId).as_number() { - Some(id) => id, - _ => return, - }; - - let timestamp_i64 = if let Some(n) = cv.get_data(DataType::SendTime).as_number() { - n as i64 - } else if let Some(s) = cv.get_data(DataType::SendTime).as_str() { - s.parse::().unwrap_or_else(|_| now_millis_i64()) - } else { - now_millis_i64() - }; - - let _ = chat_files::change_message_state( - timestamp_i64, - receiver_id as i64, - *sender_id as i64, - MessageState::from_str(cv.get_data(DataType::MessageState).as_str().unwrap_or("")), - ); -} - -pub fn handle_messages_get(cv: &CommunicationValue) -> CommunicationValue { - let my_id = cv.get_sender(); - let partner_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0); - let offset = cv.get_data(DataType::Offset).as_number().unwrap_or(0); - let amount = cv.get_data(DataType::Amount).as_number().unwrap_or(0); - let messages = chat_files::get_messages( - my_id as i64, - partner_id as i64, - offset as i64, - amount as i64, - ); - let mut msg_array: Vec = Vec::new(); - for m in &messages { - msg_array.push(stored_message_value(m, my_id as i64, partner_id as i64)); - } - - CommunicationValue::new(CommunicationType::MessagesGet) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default(DataType::Messages, DataValue::Array(msg_array)) -} - -pub fn handle_get_chats(cv: &CommunicationValue) -> CommunicationValue { - let user_id = cv.get_sender(); - let users = chats_util::get_users(user_id as i64); - let mut user_array = Vec::new(); - for user in users { - let mut container = Vec::new(); - container.push(( - DataType::UserId, - DataValue::SignedNumber(user.user_id as i128), - )); - if let Some(name) = user.user_name { - container.push((DataType::Username, DataValue::Str(name))); - } - if let Some(ts) = user.last_message_at { - container.push((DataType::LastMessageAt, DataValue::SignedNumber(ts as i128))); - } - user_array.push(typed_container(container)); - } - CommunicationValue::new(CommunicationType::GetChats) - .with_id(cv.get_id()) - .with_receiver(user_id) - .add_typed_default(DataType::UserIds, DataValue::Array(user_array)) -} - -pub fn handle_add_conversation(cv: &CommunicationValue) -> CommunicationValue { - let user_id = cv.get_sender(); - let other_id = match cv.get_data(DataType::ChatPartnerId).as_number() { - Some(n) => n as i64, - None => cv - .get_data(DataType::ChatPartnerId) - .as_str() - .unwrap_or("0") - .parse() - .unwrap_or(0), - }; - let mut contact = get_user(user_id as i64, other_id) - .unwrap_or(iota_storage::users::contact::Contact::new(other_id)); - - if let Some(name) = cv.get_data(DataType::ChatPartnerName).as_str() { - contact.user_name = Some(name.to_string()); - } - - contact.set_last_message_at(now_millis_i64()); - mod_user(user_id as i64, &contact); - CommunicationValue::new(CommunicationType::AddConversation) - .with_id(cv.get_id()) - .with_receiver(user_id) -} - -pub fn handle_add_community(cv: &CommunicationValue) -> CommunicationValue { - CommunitiesUtil::add_community( - cv.get_sender() as i64, - cv.get_data(DataType::CommunityAddress) - .as_str() - .unwrap() - .to_string(), - cv.get_data(DataType::CommunityTitle) - .as_str() - .unwrap() - .to_string(), - cv.get_data(DataType::Position) - .as_str() - .unwrap() - .to_string(), - ); - CommunicationValue::new(CommunicationType::AddCommunity) - .with_id(cv.get_id()) - .with_receiver(cv.get_sender()) -} - -pub fn handle_get_communities(cv: &CommunicationValue) -> CommunicationValue { - let mut comm_array = Vec::new(); - for c in CommunitiesUtil::get_communities(cv.get_sender() as i64) { - let mut container: Vec<(DataType, DataValue)> = Vec::new(); - container.push(( - DataType::CommunityAddress, - DataValue::Str(c.address.clone()), - )); - container.push((DataType::CommunityTitle, DataValue::Str(c.title.clone()))); - container.push((DataType::Position, DataValue::Str(c.position.clone()))); - comm_array.push(typed_container(container)); - } - - CommunicationValue::new(CommunicationType::GetCommunities) - .with_id(cv.get_id()) - .with_receiver(cv.get_sender()) - .add_typed_default(DataType::Communities, DataValue::Array(comm_array)) -} - -pub fn handle_remove_community(cv: &CommunicationValue) -> CommunicationValue { - CommunitiesUtil::remove_community( - cv.get_sender() as i64, - cv.get_data(DataType::CommunityAddress) - .as_str() - .unwrap() - .to_string(), - ); - CommunicationValue::new(CommunicationType::RemoveCommunity) - .with_id(cv.get_id()) - .with_receiver(cv.get_sender()) -} - -pub fn handle_global_settings_save(cv: &CommunicationValue) -> CommunicationValue { - let my_id = cv.get_sender(); - let Some(settings_value) = cv.get_data(DataType::Payload).as_str() else { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Missing settings payload".to_string()), - ); - }; - - if settings::save_global(my_id as i64, settings_value).is_err() { - return error_response(cv, CommunicationType::ErrorInvalidData); - } - - let mut response = CommunicationValue::new(CommunicationType::GlobalSettingsSave) - .with_receiver(my_id) - .with_id(cv.get_id()); - - if let Some(session_id) = cv.get_data(DataType::SessionId).as_number() { - response = response.add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - } - - response -} - -pub fn handle_global_settings_load(cv: &CommunicationValue) -> CommunicationValue { - let my_id = cv.get_sender(); - let Ok(settings_value) = settings::load_global(my_id as i64) else { - return error_response(cv, CommunicationType::ErrorInvalidData); - }; - let Some(settings_value_str) = settings_value else { - let mut response = CommunicationValue::new(CommunicationType::ErrorNotFound) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Path, - DataValue::Str("global.settings".to_string()), - ); - - if let Some(session_id) = cv.get_data(DataType::SessionId).as_number() { - response = response.add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - } - - return response; - }; - - let mut response = CommunicationValue::new(CommunicationType::GlobalSettingsLoad) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default(DataType::Payload, DataValue::Str(settings_value_str)); - - if let Some(session_id) = cv.get_data(DataType::SessionId).as_number() { - response = response.add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - } - - response -} - -pub fn handle_settings_save( - cv: &CommunicationValue, - _expected_session_id: i128, -) -> CommunicationValue { - let my_id = cv.get_sender(); - let Some(session_id) = cv.get_data(DataType::SessionId).as_number() else { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Missing session_id".to_string()), - ); - }; - if session_id == 0 || session_id > 1_000_000 { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Invalid session_id".to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - }; - let Some(settings_name) = cv.get_data(DataType::SettingsName).as_str() else { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Missing settings_name".to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - }; - let Some(settings_value) = cv.get_data(DataType::Payload).as_str() else { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Missing settings payload".to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - }; - - if !settings_name - .chars() - .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.') - || settings_name.contains("..") - { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Invalid settings_name".to_string()), - ) - .add_typed_default( - DataType::SettingsName, - DataValue::Str(settings_name.to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - } - - if settings::save( - my_id as i64, - session_id as i64, - settings_name, - settings_value, - ) - .is_err() - { - return error_response(cv, CommunicationType::ErrorInvalidData); - } - - CommunicationValue::new(CommunicationType::SettingsSave) - .with_receiver(my_id) - .with_id(cv.get_id()) - .add_typed_default( - DataType::SettingsName, - DataValue::Str(settings_name.to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ) -} - -pub fn handle_settings_load( - cv: &CommunicationValue, - _expected_session_id: i128, -) -> CommunicationValue { - let my_id = cv.get_sender(); - let Some(session_id) = cv.get_data(DataType::SessionId).as_number() else { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Missing session_id".to_string()), - ); - }; - if session_id == 0 || session_id > 1_000_000 { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Invalid session_id".to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - } - let Some(settings_name) = cv.get_data(DataType::SettingsName).as_str() else { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Missing settings_name".to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - }; - - if !settings_name - .chars() - .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.') - || settings_name.contains("..") - { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Invalid settings_name".to_string()), - ) - .add_typed_default( - DataType::SettingsName, - DataValue::Str(settings_name.to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - } - - let Ok(settings_value) = settings::load(my_id as i64, session_id as i64, settings_name) else { - return error_response(cv, CommunicationType::ErrorInvalidData); - }; - let Some(settings_value_str) = settings_value else { - return CommunicationValue::new(CommunicationType::ErrorNotFound) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::SettingsName, - DataValue::Str(settings_name.to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - }; - - CommunicationValue::new(CommunicationType::SettingsLoad) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default(DataType::Payload, DataValue::Str(settings_value_str)) - .add_typed_default( - DataType::SettingsName, - DataValue::Str(settings_name.to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ) -} - -pub fn handle_settings_list( - cv: &CommunicationValue, - _expected_session_id: i128, -) -> CommunicationValue { - let my_id = cv.get_sender(); - let Some(session_id) = cv.get_data(DataType::SessionId).as_number() else { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Missing session_id".to_string()), - ); - }; - if session_id == 0 || session_id > 1_000_000 { - return CommunicationValue::new(CommunicationType::ErrorInvalidData) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default( - DataType::Message, - DataValue::Str("Invalid session_id".to_string()), - ) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ); - } - - let Ok(settings) = settings::list(my_id as i64, session_id as i64) else { - return error_response(cv, CommunicationType::ErrorInvalidData); - }; - let settings_json = settings.into_iter().map(DataValue::Str).collect(); - CommunicationValue::new(CommunicationType::SettingsList) - .with_id(cv.get_id()) - .with_receiver(my_id) - .add_typed_default(DataType::Settings, DataValue::Array(settings_json)) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id as i128), - ) -} diff --git a/iota-core/src/main.rs b/iota-core/src/main.rs index fd794e6..2baf39b 100644 --- a/iota-core/src/main.rs +++ b/iota-core/src/main.rs @@ -88,9 +88,6 @@ async fn main() { if let Err(_) = user_manager::load_users().await { log_t!("user_load_failed"); } - if let Err(e) = iota_storage::util::settings::migrate_legacy_files() { - log!("Failed to migrate legacy settings: {}", e); - } let mut sb = "".to_string(); @@ -105,11 +102,7 @@ async fn main() { } log!( "IOTA ID: {}", - CONFIG - .load() - .iota_id - .map(|id| id.to_string()) - .unwrap_or_else(|| "N/A".to_string()) + CONFIG.load().iota_id.map(|id| id.to_string()).unwrap_or_else(|| "N/A".to_string()) ); log!("User IDS: {}", sb); @@ -128,7 +121,7 @@ async fn main() { sb1 = sb1 + ","; } log!("Community IDS: {}", sb1); */ - let port = CONFIG.load().port; + let _port = CONFIG.load().port; let mut _ip = "0.0.0.0".to_string(); for iface in pnet::datalink::interfaces() { let iface: NetworkInterface = iface; @@ -140,6 +133,17 @@ async fn main() { } } } + /* Community port activation is used for activating the port for communities. + * Code is currently commented because communities have not been implemented yet. + if start(port).await { + log_t!("community_active", ip, port.to_string()); + } else { + if port < 1024 { + log_t!("community_start_error_admin", port.to_string()); + } else { + log_t!("community_start_error", port.to_string()); + } + } */ if !has_dir("web") { download_and_extract_zip( "https://omega.tensamin.net/api/download/iota_frontend", @@ -147,9 +151,6 @@ async fn main() { ) .await; } - if !web_server::start(port).await { - log!("Failed to start the MTP web server on port {}", port); - } let _ = omikron::omikron_connection::get_omikron_connection().await; log_t!("setup_completed"); @@ -161,9 +162,7 @@ async fn main() { if OMIKRON_CONNECTION.has_auth_failure().await { if let Some(reason) = OMIKRON_CONNECTION.get_auth_failure().await { log!("Authentication failed: {}", reason); - log!( - "Use /reconnect to try again or /regenerate private-key to create a new key pair" - ); + log!("Use /reconnect to try again or /regenerate private-key to create a new key pair"); OMIKRON_CONNECTION.clear_auth_failure().await; } } diff --git a/iota-storage/Cargo.toml b/iota-storage/Cargo.toml index 24d7603..78359be 100644 --- a/iota-storage/Cargo.toml +++ b/iota-storage/Cargo.toml @@ -17,11 +17,8 @@ hkdf = "0.12.4" json = "*" arc-swap = "1" once_cell = "1.21.3" -r2d2 = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" -serde_yaml = "0.9" -thiserror = "2" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } ratatui = "0.30.0" diff --git a/iota-storage/src/lib.rs b/iota-storage/src/lib.rs index a0e5061..7f0e8ef 100644 --- a/iota-storage/src/lib.rs +++ b/iota-storage/src/lib.rs @@ -1,3 +1,2 @@ -pub mod storage_error; pub mod users; pub mod util; diff --git a/iota-storage/src/storage_error.rs b/iota-storage/src/storage_error.rs deleted file mode 100644 index 100bf08..0000000 --- a/iota-storage/src/storage_error.rs +++ /dev/null @@ -1,13 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum StorageError { - #[error("Database error: {0}")] - Db(#[from] rusqlite::Error), - #[error("Connection pool error: {0}")] - Pool(String), - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - #[error("{0}")] - Other(String), -} diff --git a/iota-storage/src/users/contact.rs b/iota-storage/src/users/contact.rs index 50d224d..632cea3 100644 --- a/iota-storage/src/users/contact.rs +++ b/iota-storage/src/users/contact.rs @@ -1,3 +1,4 @@ +use json::{self, JsonValue, number::Number}; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Debug, Clone)] @@ -32,4 +33,29 @@ impl Contact { pub fn set_last_message_at(&mut self, p0: i64) { self.last_message_at = Option::from(p0); } + + pub fn to_json(&self) -> JsonValue { + let mut obj = JsonValue::new_object(); + obj["user_id"] = JsonValue::Number(Number::from(self.user_id)); + if let Some(name) = &self.user_name { + obj["user_name"] = JsonValue::from(name.as_str()); + } + if let Some(ts) = &self.last_message_at { + obj["last_message_at"] = JsonValue::Number(Number::from(*ts)); + } + obj + } + pub fn from_json(o: &JsonValue) -> Contact { + let user_id = o["user_id"].as_i64().unwrap_or(0); + + let user_name = o["user_name"].as_str().map(|s| s.to_string()); + + let last_message_at = o["last_message_at"].as_i64(); + + Contact { + user_id, + user_name, + last_message_at, + } + } } diff --git a/iota-storage/src/users/mod.rs b/iota-storage/src/users/mod.rs index cd4fa45..aef5a02 100644 --- a/iota-storage/src/users/mod.rs +++ b/iota-storage/src/users/mod.rs @@ -1,3 +1,4 @@ pub mod contact; +pub mod user_community_util; pub mod user_manager; pub mod user_profile; diff --git a/iota-storage/src/users/user_community_util.rs b/iota-storage/src/users/user_community_util.rs new file mode 100644 index 0000000..89e0fb6 --- /dev/null +++ b/iota-storage/src/users/user_community_util.rs @@ -0,0 +1,58 @@ +use iota_util::file_util::{load_file, save_file}; +use json::{self, Array, JsonValue}; + +pub struct UserCommunityUtil; + +impl UserCommunityUtil { + pub fn add_community(storage_owner: i64, address: String, title: String, position: String) { + let file_path = format!("users/{}/", storage_owner); + let mut communities = Self::load_array(&file_path, "communities.json"); + + let mut community = JsonValue::new_object(); + community["title"] = JsonValue::String(title); + community["address"] = JsonValue::String(address); + community["position"] = JsonValue::String(position); + + communities.push(community); + + save_file( + &file_path, + "communities.json", + &JsonValue::Array(communities).to_string(), + ); + } + + pub fn remove_community(storage_owner: i64, community_address: String) { + let file_path = format!("users/{}/", storage_owner); + let communities = Self::load_array(&file_path, "communities.json"); + + let filtered: Array = communities + .iter() + .filter(|entry| entry["address"].as_str() != Some(&community_address)) + .cloned() + .collect(); + save_file( + &file_path, + "communities.json", + &JsonValue::Array(filtered).to_string(), + ); + } + + pub fn get_communities(storage_owner: i64) -> Array { + let file_path = format!("users/{}/", storage_owner); + Self::load_array(&file_path, "communities.json") + } + + fn load_array(dir: &str, name: &str) -> Array { + let content = load_file(dir, name); + if content.is_empty() { + return Array::new(); + } + + let parsed = json::parse(&content); + match parsed { + Ok(JsonValue::Array(arr)) => arr, + _ => Array::new(), + } + } +} diff --git a/iota-storage/src/users/user_manager.rs b/iota-storage/src/users/user_manager.rs index 67c4e6a..bf1dc00 100644 --- a/iota-storage/src/users/user_manager.rs +++ b/iota-storage/src/users/user_manager.rs @@ -1,214 +1,15 @@ use crate::users::user_profile::UserProfile; -use crate::util::db; use base64::{Engine as _, engine::general_purpose::STANDARD}; use iota_util::crypto_helper::{self, hex_hash, keyring_from_base64, public_key_bundle_to_base64}; use iota_util::file_util::{load_file, save_file}; +use json::JsonValue; +use once_cell::sync::Lazy; use rand_core::{OsRng, RngCore}; -use rusqlite::params; +use std::io::{self}; +use std::sync::Mutex; -pub fn add_user(user: UserProfile) { - if let Err(e) = db::with_db(|conn| { - conn.execute( - r#" - INSERT INTO users (user_id, username, public_key, private_key_hash, reset_token, created_at, display_name) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) - ON CONFLICT(user_id) DO UPDATE SET - username = excluded.username, - public_key = excluded.public_key, - private_key_hash = excluded.private_key_hash, - reset_token = excluded.reset_token, - display_name = excluded.display_name - "#, - params![ - user.user_id, - user.username, - user.public_key, - user.private_key_hash, - user.reset_token, - user.created_at, - user.display_name, - ], - )?; - - for (app_id, app_secret) in &user.trusted_apps { - conn.execute( - r#" - INSERT OR REPLACE INTO trusted_apps (user_id, app_id, app_secret) - VALUES (?1, ?2, ?3) - "#, - params![user.user_id, app_id, app_secret], - )?; - } - Ok(()) - }) { - eprintln!("Failed to add_user: {}", e); - } -} - -pub fn update_user(user: UserProfile) { - add_user(user); -} - -pub fn get_user_by_username(username: &str) -> Option { - match db::with_db(|conn| { - match conn.query_row( - "SELECT user_id, username, public_key, private_key_hash, reset_token, created_at, display_name FROM users WHERE username = ?1 LIMIT 1", - params![username], - |r| { - let user_id: i64 = r.get(0)?; - Ok(UserProfile { - user_id, - username: r.get(1)?, - display_name: r.get(6)?, - public_key: r.get(2)?, - private_key_hash: r.get(3)?, - created_at: r.get(5)?, - reset_token: r.get(4)?, - trusted_apps: load_trusted_apps(user_id), - }) - }, - ) { - Ok(user) => Ok(Some(user)), - Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), - Err(e) => Err(e.into()), - } - }) { - Ok(opt) => opt, - Err(e) => { - eprintln!("Error querying user by username: {}", e); - None - } - } -} - -pub fn get_user(user_id: i64) -> Option { - match db::with_db(|conn| { - match conn.query_row( - "SELECT user_id, username, public_key, private_key_hash, reset_token, created_at, display_name FROM users WHERE user_id = ?1 LIMIT 1", - params![user_id], - |r| { - let user_id: i64 = r.get(0)?; - Ok(UserProfile { - user_id, - username: r.get(1)?, - display_name: r.get(6)?, - public_key: r.get(2)?, - private_key_hash: r.get(3)?, - created_at: r.get(5)?, - reset_token: r.get(4)?, - trusted_apps: load_trusted_apps(user_id), - }) - }, - ) { - Ok(user) => Ok(Some(user)), - Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), - Err(e) => Err(e.into()), - } - }) { - Ok(opt) => opt, - Err(e) => { - eprintln!("Error querying user: {}", e); - None - } - } -} - -pub fn get_users() -> Vec { - match db::with_db(|conn| { - let mut stmt = conn.prepare( - r#" - SELECT user_id, username, public_key, private_key_hash, reset_token, created_at, display_name - FROM users - ORDER BY username - "#, - )?; - - let rows = stmt.query_map([], |r| { - let user_id: i64 = r.get(0)?; - let username: String = r.get(1)?; - let public_key: String = r.get(2)?; - let private_key_hash: String = r.get(3)?; - let reset_token: String = r.get(4)?; - let created_at: i64 = r.get(5)?; - let display_name: Option = r.get(6)?; - - Ok(UserProfile { - user_id, - username, - display_name, - public_key, - private_key_hash, - created_at, - reset_token, - trusted_apps: std::collections::HashMap::new(), - }) - })?; - - let mut out = Vec::new(); - for row in rows { - match row { - Ok(mut user) => { - user.trusted_apps = load_trusted_apps(user.user_id); - out.push(user); - } - Err(e) => eprintln!("Failed to read user row: {}", e), - } - } - Ok(out) - }) { - Ok(v) => v, - Err(e) => { - eprintln!("Failed to query users: {}", e); - Vec::new() - } - } -} - -fn load_trusted_apps(user_id: i64) -> std::collections::HashMap { - match db::with_db(|conn| { - let mut stmt = - conn.prepare("SELECT app_id, app_secret FROM trusted_apps WHERE user_id = ?1")?; - let rows = stmt.query_map(params![user_id], |r| { - Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)) - })?; - - let mut map = std::collections::HashMap::new(); - for row in rows { - if let Ok((k, v)) = row { - map.insert(k, v); - } - } - Ok(map) - }) { - Ok(m) => m, - Err(e) => { - eprintln!("Failed to load trusted apps: {}", e); - std::collections::HashMap::new() - } - } -} - -pub fn remove_user(user_id: i64) { - if let Err(e) = db::with_db(|conn| { - conn.execute( - "DELETE FROM trusted_apps WHERE user_id = ?1", - params![user_id], - )?; - conn.execute("DELETE FROM users WHERE user_id = ?1", params![user_id])?; - Ok(()) - }) { - eprintln!("Failed to remove_user: {}", e); - } -} - -pub fn clear() { - if let Err(e) = db::with_db(|conn| { - conn.execute_batch("DELETE FROM trusted_apps; DELETE FROM users;")?; - Ok(()) - }) { - eprintln!("Failed to clear users: {}", e); - } -} +static USERS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); +static UNIQUE: Lazy> = Lazy::new(|| Mutex::new(false)); #[allow(dead_code)] pub async fn load_from_tu(username: &str) -> Result<(), ()> { @@ -234,38 +35,91 @@ pub async fn load_from_tu(username: &str) -> Result<(), ()> { hex_hash(&keyring_b64), reset_token, ); - add_user(user_profile); + USERS.lock().unwrap().push(user_profile); Ok(()) } -pub fn save_users() { - // No-op: users are auto-saved via SQLite. +pub fn add_user(user: UserProfile) { + USERS.lock().unwrap().push(user); } -pub async fn load_users() -> std::io::Result<()> { - // Users are loaded from SQLite on demand. This function is kept for API compat. - // If we need to migrate from a legacy users.json file, we can do so here. +pub fn update_user(user: UserProfile) { + let mut users = USERS.lock().unwrap(); + if let Some(pos) = users.iter().position(|u| u.user_id == user.user_id) { + users[pos] = user; + } + *UNIQUE.lock().unwrap() = true; +} +pub fn get_user_by_username(username: &str) -> Option { + USERS + .lock() + .unwrap() + .iter() + .cloned() + .find(|u| u.username == username) +} + +pub fn get_user(user_id: i64) -> Option { + USERS + .lock() + .unwrap() + .iter() + .cloned() + .find(|u| u.user_id == user_id) +} + +pub fn get_users() -> Vec { + USERS.lock().unwrap().clone() +} + +pub fn remove_user(user_id: i64) { + let mut users = USERS.lock().unwrap(); + users.retain(|u| u.user_id != user_id); + *UNIQUE.lock().unwrap() = true; +} + +pub fn save_users() { + *UNIQUE.lock().unwrap() = false; + let users = USERS.lock().unwrap(); + let arr: Vec = users.iter().map(|u| u.to_json()).collect(); + let json_str = JsonValue::Array(arr).dump(); + + save_file("", "users.json", &json_str); +} + +pub fn clear() { + let mut users = USERS.lock().unwrap(); + users.clear(); + *UNIQUE.lock().unwrap() = true; +} + +pub async fn load_users() -> io::Result<()> { let content = load_file("", "users.json"); if content.trim().is_empty() { return Ok(()); } - if let Ok(parsed) = json::parse(&content) { - if let json::JsonValue::Array(arr) = parsed { - for j in arr.iter() { - if let Some(up) = UserProfile::from_json(j) { - add_user(up); - } + + let parsed = + json::parse(&content).map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + if let JsonValue::Array(arr) = parsed { + let mut users = USERS.lock().unwrap(); + for j in arr.iter() { + if let Some(up) = UserProfile::from_json(j).await { + users.push(up); } } } - // Rename the old file so we don't re-import - let _ = std::fs::rename( - std::path::PathBuf::from(iota_util::file_util::get_directory()).join("users.json"), - std::path::PathBuf::from(iota_util::file_util::get_directory()).join("users.json.imported"), - ); + if *UNIQUE.lock().unwrap() { + save_users(); + } Ok(()) } +#[allow(dead_code)] +pub fn set_unique(val: bool) { + *UNIQUE.lock().unwrap() = val; +} + pub fn save_app_data(user_id: i64, app_identifier: &str, data: &str) { let path = format!("users/{}/apps", user_id); let name = format!("{}.json", app_identifier); diff --git a/iota-storage/src/users/user_profile.rs b/iota-storage/src/users/user_profile.rs index 2ecb9cc..c0ae20e 100644 --- a/iota-storage/src/users/user_profile.rs +++ b/iota-storage/src/users/user_profile.rs @@ -5,9 +5,9 @@ use iota_util::file_util::{has_file, load_file, used_dir_space}; use json::{JsonValue, object}; use rand::Rng; use rand::rngs::OsRng; -use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Serialize, Deserialize)] +// --- UserProfile --- +#[derive(Clone, Debug)] pub struct UserProfile { pub user_id: i64, pub username: String, @@ -43,6 +43,26 @@ impl UserProfile { } } + pub fn to_json(&self) -> JsonValue { + let mut trusted_apps_obj = json::JsonValue::new_object(); + for (k, v) in &self.trusted_apps { + trusted_apps_obj[k] = v.clone().into(); + } + + let mut obj = object! { + "uuid" => self.user_id, + "username" => self.username.clone(), + "public_key" => self.public_key.clone(), + "private_key_hash" => self.private_key_hash.clone(), + "created_at" => self.created_at, + "reset_token" => self.reset_token.clone(), + "trusted_apps" => trusted_apps_obj, + }; + if let Some(d) = &self.display_name { + obj["display_name"] = d.clone().into(); + } + obj + } pub fn frontend(&self) -> JsonValue { let mut obj = object! { "uuid" => self.user_id, @@ -58,11 +78,10 @@ impl UserProfile { if has_file("", &format!("{}.tu", self.username.clone())) { obj["tu"] = load_file("", &format!("{}.tu", self.username.clone())).into(); } + obj } - - /// Legacy JSON import - used when migrating from users.json to SQLite. - pub fn from_json(j: &JsonValue) -> Option { + pub async fn from_json(j: &JsonValue) -> Option { let user_id = j["uuid"].as_i64()?; let username = j["username"].as_str()?.to_string(); let public_key = j["public_key"].as_str()?.to_string(); @@ -80,7 +99,7 @@ impl UserProfile { } } - Some(UserProfile { + let up = UserProfile { user_id, username, display_name, @@ -89,15 +108,22 @@ impl UserProfile { created_at, reset_token, trusted_apps, - }) - } + }; - pub fn from_yaml(s: &str) -> Result { - serde_yaml::from_str(s) - } + // TODO: Migrate to Omikron / Wss + /* if j.has_key("migrate") + || j.has_key("migrating") + || j.has_key("changing") + || j.has_key("move") + || j.has_key("moving") + { + if auth_connector::migrate_user(&mut up).await { + log_message(format!("[INFO] Migration triggered for {}", up.username)); + user_manager::set_unique(true); + } + } */ - pub fn to_yaml(&self) -> Result { - serde_yaml::to_string(self) + Some(up) } #[allow(dead_code)] diff --git a/iota-storage/src/util/chat_files.rs b/iota-storage/src/util/chat_files.rs index b09d750..a5b50fe 100644 --- a/iota-storage/src/util/chat_files.rs +++ b/iota-storage/src/util/chat_files.rs @@ -1,7 +1,9 @@ -use crate::storage_error::StorageError; use crate::util::db; use iota_logger::log; +use json::{JsonValue, array, object}; use rusqlite::params; +use std::io; +use std::sync::{Arc, LazyLock, Mutex}; #[derive(PartialEq, Debug, Clone)] pub enum MessageState { @@ -43,327 +45,11 @@ impl MessageState { } } -#[derive(Debug, Clone)] -pub struct StoredMessage { - pub id: i64, - pub message_time: i64, - pub content: String, - pub edited: bool, - pub sent_by_self: bool, - pub message_state: String, - pub height: i64, - pub reply_to: Option, - pub reactions: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StoredReaction { - pub reaction: String, - pub user_id: i64, -} - -/* - * Each edit is recorded in message_edits with the before/after content and a - * timestamp. Only the original sender (sent_by_self = 1) may edit. - */ -pub fn edit_message( - storage_owner: i64, - external_user: i64, - message_time: i64, - editor_id: i64, - new_content: &str, -) -> Result<(), StorageError> { - update_message_content( - storage_owner, - external_user, - message_time, - editor_id, - new_content, - true, - ) -} - -/* Applies an edit received from the message sender to the recipient's copy. */ -pub fn apply_remote_edit( - storage_owner: i64, - external_user: i64, - message_time: i64, - editor_id: i64, - new_content: &str, -) -> Result<(), StorageError> { - if editor_id != external_user { - return Err(StorageError::Other( - "Remote editor does not match chat partner".into(), - )); - } - - update_message_content( - storage_owner, - external_user, - message_time, - editor_id, - new_content, - false, - ) -} - -fn update_message_content( - storage_owner: i64, - external_user: i64, - message_time: i64, - editor_id: i64, - new_content: &str, - require_sent_by_self: bool, -) -> Result<(), StorageError> { - db::with_db(|conn| { - let msg = conn.query_row( - r#" - SELECT id, content, sent_by_self - FROM messages - WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3 - ORDER BY id DESC LIMIT 1 - "#, - params![storage_owner, external_user, message_time], - |row| { - Ok(( - row.get::<_, i64>(0)?, - row.get::<_, String>(1)?, - row.get::<_, i64>(2)?, - )) - }, - )?; - - let (msg_id, old_content, sent_by_self) = msg; - if require_sent_by_self && sent_by_self != 1 { - return Err(StorageError::Other( - "Only the original sender can edit this message".into(), - )); - } - if !require_sent_by_self && sent_by_self != 0 { - return Err(StorageError::Other( - "Remote edits may only update received messages".into(), - )); - } - - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() as i64; - - conn.execute( - r#" - INSERT INTO message_edits (message_id, content_before, content_after, edited_at, edited_by) - VALUES (?1, ?2, ?3, ?4, ?5) - "#, - params![msg_id, old_content, new_content, now, editor_id], - )?; - - conn.execute( - r#" - UPDATE messages - SET content = ?1, edited_count = edited_count + 1 - WHERE id = ?2 - "#, - params![new_content, msg_id], - )?; - - Ok(()) - }) -} - -pub fn hard_delete_message( - storage_owner: i64, - external_user: i64, - message_time: i64, -) -> Result<(), StorageError> { - db::with_db(|conn| { - let msg_id: i64 = conn.query_row( - r#" - SELECT id FROM messages - WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3 - ORDER BY id DESC LIMIT 1 - "#, - params![storage_owner, external_user, message_time], - |row| row.get(0), - )?; - - conn.execute( - "DELETE FROM message_edits WHERE message_id = ?1", - params![msg_id], - )?; - conn.execute( - "DELETE FROM reactions WHERE message_id = ?1", - params![msg_id], - )?; - conn.execute("DELETE FROM messages WHERE id = ?1", params![msg_id])?; - Ok(()) - }) -} - -/* Deletes a message from the sender's local copy after checking ownership. */ -pub fn delete_message( - storage_owner: i64, - external_user: i64, - message_time: i64, -) -> Result<(), StorageError> { - ensure_message_direction(storage_owner, external_user, message_time, true)?; - hard_delete_message(storage_owner, external_user, message_time) -} - -/* Flags the recipient's local copy after validating its sender, preserving its history. */ -pub fn apply_remote_delete( - storage_owner: i64, - external_user: i64, - message_time: i64, - sender_id: i64, -) -> Result<(), StorageError> { - if sender_id != external_user { - return Err(StorageError::Other( - "Remote sender does not match chat partner".into(), - )); - } - ensure_message_direction(storage_owner, external_user, message_time, false)?; - flag_deleted_by_external(storage_owner, external_user, message_time) -} - -fn ensure_message_direction( - storage_owner: i64, - external_user: i64, - message_time: i64, - expected_sent_by_self: bool, -) -> Result<(), StorageError> { - db::with_db(|conn| { - let sent_by_self: i64 = conn.query_row( - r#" - SELECT sent_by_self FROM messages - WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3 - ORDER BY id DESC LIMIT 1 - "#, - params![storage_owner, external_user, message_time], - |row| row.get(0), - )?; - if (sent_by_self != 0) != expected_sent_by_self { - return Err(StorageError::Other( - "Message sender is not authorized".into(), - )); - } - Ok(()) - }) -} - -/* - * Marks a message as deleted by the external user rather than removing the row, - * so the storage owner still sees a tombstone in the UI. - */ -pub fn flag_deleted_by_external( - storage_owner: i64, - external_user: i64, - message_time: i64, -) -> Result<(), StorageError> { - db::with_db(|conn| { - let affected = conn.execute( - r#" - UPDATE messages - SET deleted_by_external = 1 - WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3 - "#, - params![storage_owner, external_user, message_time], - )?; - if affected == 0 { - return Err(StorageError::Other("Message not found".into())); - } - Ok(()) - }) -} - -/* - * Removes the edit trail but keeps the message with edited_count > 0 so - * the UI still shows the "edited" indicator. Only the own user should - * call this. - */ -pub fn delete_edit_history( - storage_owner: i64, - external_user: i64, - message_time: i64, -) -> Result<(), StorageError> { - db::with_db(|conn| { - let msg_id: i64 = conn.query_row( - r#" - SELECT id FROM messages - WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3 - ORDER BY id DESC LIMIT 1 - "#, - params![storage_owner, external_user, message_time], - |row| row.get(0), - )?; - - conn.execute( - "DELETE FROM message_edits WHERE message_id = ?1", - params![msg_id], - )?; - Ok(()) - }) -} - -pub fn add_reaction( - storage_owner: i64, - external_user: i64, - message_time: i64, - user_id: i64, - reaction: &str, -) -> Result<(), StorageError> { - db::with_db(|conn| { - let msg_id: i64 = conn.query_row( - r#" - SELECT id FROM messages - WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3 - ORDER BY id DESC LIMIT 1 - "#, - params![storage_owner, external_user, message_time], - |row| row.get(0), - )?; - - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() as i64; - - conn.execute( - r#" - INSERT OR IGNORE INTO reactions (message_id, user_id, reaction, created_at) - VALUES (?1, ?2, ?3, ?4) - "#, - params![msg_id, user_id, reaction, now], - )?; - Ok(()) - }) -} - -pub fn remove_reaction( - storage_owner: i64, - external_user: i64, - message_time: i64, - user_id: i64, - reaction: &str, -) -> Result<(), StorageError> { - db::with_db(|conn| { - let msg_id: i64 = conn.query_row( - r#" - SELECT id FROM messages - WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3 - ORDER BY id DESC LIMIT 1 - "#, - params![storage_owner, external_user, message_time], - |row| row.get(0), - )?; - - conn.execute( - "DELETE FROM reactions WHERE message_id = ?1 AND user_id = ?2 AND reaction = ?3", - params![msg_id, user_id, reaction], - )?; - Ok(()) - }) -} +// Shared DB created via helper. +// The db helper constructs the messages sqlite file and ensures PRAGMAs and schema exist. +static MESSAGES_DB: LazyLock>> = LazyLock::new(|| { + db::create_general_messages_db().expect("Failed to create or initialize general messages DB") +}); pub fn add_message( send_time: u128, @@ -382,12 +68,19 @@ pub fn add_message( } }; - if let Err(e) = db::with_db(|conn| { + // Insert the message into the DB + let insert_result = db::with_conn(&MESSAGES_DB, |conn| { conn.execute( r#" INSERT INTO messages ( - storage_owner, external_user, message_time, content, - sent_by_self, message_state, height, reply_to + storage_owner, + external_user, + message_time, + content, + sent_by_self, + message_state, + height, + reply_to ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) "#, params![ @@ -406,13 +99,18 @@ pub fn add_message( ], )?; Ok(()) - }) { + }); + + if let Err(e) = insert_result { log!("Failed to insert message into sqlite: {}", e); return; } + // Update contacts table to reflect that this conversation exists and has a recent message. + // Use the Contact helper to set last_message_at to the message timestamp. let mut contact = crate::users::contact::Contact::new(external_user); contact.set_last_message_at(message_time); + // This will insert or update the contact for the storage owner. crate::util::chats_util::mod_user(storage_owner, &contact); } @@ -421,21 +119,25 @@ pub fn change_message_state( storage_owner: i64, external_user: i64, new_state: MessageState, -) -> std::io::Result<()> { - db::with_db(|conn| { +) -> io::Result<()> { + // Run the SELECT and UPDATE inside with_conn to centralize connection access. + let res: Result<(), String> = db::with_conn(&MESSAGES_DB, |conn| { let current: Option = match conn.query_row( r#" SELECT message_state FROM messages - WHERE storage_owner = ?1 AND external_user = ?2 AND message_time = ?3 - ORDER BY id DESC LIMIT 1 + WHERE storage_owner = ?1 + AND external_user = ?2 + AND message_time = ?3 + ORDER BY id DESC + LIMIT 1 "#, params![storage_owner, external_user, timestamp], |row| row.get(0), ) { Ok(state) => Some(state), Err(rusqlite::Error::QueryReturnedNoRows) => None, - Err(e) => return Err(e.into()), + Err(e) => return Err(e), }; let Some(current_state_raw) = current else { @@ -452,58 +154,24 @@ pub fn change_message_state( UPDATE messages SET message_state = ?1 WHERE id = ( - SELECT id FROM messages - WHERE storage_owner = ?2 AND external_user = ?3 AND message_time = ?4 - ORDER BY id DESC LIMIT 1 + SELECT id + FROM messages + WHERE storage_owner = ?2 + AND external_user = ?3 + AND message_time = ?4 + ORDER BY id DESC + LIMIT 1 ) "#, params![upgraded, storage_owner, external_user, timestamp], )?; Ok(()) - }) - .map_err(|e: StorageError| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) -} + }); -fn load_reactions( - conn: &rusqlite::Connection, - msg_ids: &[i64], -) -> std::collections::HashMap> { - if msg_ids.is_empty() { - return std::collections::HashMap::new(); + match res { + Ok(_) => Ok(()), + Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), } - - let placeholders: Vec = msg_ids - .iter() - .enumerate() - .map(|(i, _)| format!("?{}", i + 1)) - .collect(); - let query = format!( - "SELECT message_id, reaction, user_id FROM reactions WHERE message_id IN ({}) ORDER BY created_at ASC, id ASC", - placeholders.join(", ") - ); - - let mut map: std::collections::HashMap> = - std::collections::HashMap::new(); - if let Ok(mut stmt) = conn.prepare(&query) { - let params: Vec<&dyn rusqlite::types::ToSql> = msg_ids - .iter() - .map(|id| id as &dyn rusqlite::types::ToSql) - .collect(); - if let Ok(rows) = stmt.query_map(params.as_slice(), |row| { - Ok(( - row.get::<_, i64>(0)?, - StoredReaction { - reaction: row.get(1)?, - user_id: row.get(2)?, - }, - )) - }) { - for row in rows.flatten() { - map.entry(row.0).or_default().push(row.1); - } - } - } - map } pub fn get_messages( @@ -511,17 +179,26 @@ pub fn get_messages( external_user: i64, loaded_messages: i64, amount: i64, -) -> Vec { +) -> JsonValue { + let messages = array![]; + if amount <= 0 || loaded_messages < 0 { - return Vec::new(); + return messages; } - match db::with_db(|conn| { + let res: Result = db::with_conn(&MESSAGES_DB, |conn| { let mut stmt = conn.prepare( r#" - SELECT id, message_time, content, sent_by_self, message_state, height, reply_to, edited_count + SELECT + message_time, + content, + sent_by_self, + message_state, + height, + reply_to FROM messages - WHERE storage_owner = ?1 AND external_user = ?2 AND deleted_by_external = 0 + WHERE storage_owner = ?1 + AND external_user = ?2 ORDER BY message_time DESC, id DESC LIMIT ?3 OFFSET ?4 "#, @@ -530,40 +207,49 @@ pub fn get_messages( let rows = stmt.query_map( params![storage_owner, external_user, amount, loaded_messages], |row| { - Ok(StoredMessage { - id: row.get(0)?, - message_time: row.get(1)?, - content: row.get(2)?, - sent_by_self: row.get::<_, i64>(3)? != 0, - message_state: row.get(4)?, - height: row.get(5).unwrap_or(0), - reply_to: row.get(6).ok().flatten(), - edited: row.get::<_, i64>(7).unwrap_or(0) > 0, - reactions: Vec::new(), - }) + let message_time: i64 = row.get(0)?; + let content: String = row.get(1)?; + let sent_by_self: i64 = row.get(2)?; + let message_state: String = row.get(3)?; + let height: i64 = row.get(4).unwrap_or(0); + let reply_to: Option = row.get(5).ok().flatten(); + Ok((message_time, content, sent_by_self, message_state, height, reply_to)) }, )?; - let mut out = Vec::new(); + let mut out = array![]; for row in rows { match row { - Ok(msg) => out.push(msg), - Err(e) => log!("Failed to read row from sqlite: {}", e), + Ok((message_time, content, sent_by_self, message_state, height, reply_to)) => { + let mut msg = object! { + "message_time" => message_time, + "content" => content, + "sent_by_self" => (sent_by_self != 0), + "message_state" => message_state, + "height" => height + }; + if let Some(rt) = reply_to { + let _ = msg.insert("reply_to", rt); + } + if let Err(e) = out.push(msg) { + // out.push returns a JsonError; log it instead of using `?` to avoid + // incompatible error conversions inside the DB closure. + log!("Failed to append message to output array: {:?}", e); + } + } + Err(e) => { + log!("Failed to read row from sqlite: {}", e); + } } } - - let msg_ids: Vec = out.iter().map(|m| m.id).collect(); - let reaction_map = load_reactions(conn, &msg_ids); - for msg in &mut out { - msg.reactions = reaction_map.get(&msg.id).cloned().unwrap_or_default(); - } - Ok(out) - }) { + }); + + match res { Ok(v) => v, Err(e) => { log!("Failed to query messages: {}", e); - Vec::new() + messages } } } diff --git a/iota-storage/src/util/chats_util.rs b/iota-storage/src/util/chats_util.rs index eb8f069..b8e0ad2 100644 --- a/iota-storage/src/util/chats_util.rs +++ b/iota-storage/src/util/chats_util.rs @@ -1,13 +1,24 @@ use crate::users::contact::Contact; use crate::util::db; use rusqlite::params; +use std::sync::{Arc, LazyLock, Mutex}; +/// Shared DB connection for contacts/messages (created by db helper). +static MESSAGES_DB: LazyLock>> = LazyLock::new(|| { + db::create_general_messages_db().expect("Failed to create or initialize general messages DB") +}); + +/// Insert or update a contact for the given storage owner. pub fn mod_user(storage_owner: i64, contact: &Contact) { - if let Err(e) = db::with_db(|conn| { + if let Err(e) = db::with_conn(&MESSAGES_DB, |conn| { conn.execute( r#" - INSERT INTO contacts (storage_owner, user_id, user_name, last_message_at) - VALUES (?1, ?2, ?3, ?4) + INSERT INTO contacts ( + storage_owner, + user_id, + user_name, + last_message_at + ) VALUES (?1, ?2, ?3, ?4) ON CONFLICT(storage_owner, user_id) DO UPDATE SET user_name = excluded.user_name, last_message_at = excluded.last_message_at @@ -16,7 +27,7 @@ pub fn mod_user(storage_owner: i64, contact: &Contact) { storage_owner, contact.user_id, contact.user_name.clone(), - contact.last_message_at, + contact.last_message_at ], )?; Ok(()) @@ -25,8 +36,9 @@ pub fn mod_user(storage_owner: i64, contact: &Contact) { } } +/// Retrieve a single contact for storage_owner/user_id. pub fn get_user(storage_owner: i64, user_id: i64) -> Option { - match db::with_db(|conn| { + let res: Result, String> = db::with_conn(&MESSAGES_DB, |conn| { match conn.query_row( r#" SELECT user_id, user_name, last_message_at @@ -36,18 +48,23 @@ pub fn get_user(storage_owner: i64, user_id: i64) -> Option { "#, params![storage_owner, user_id], |r| { + let user_id: i64 = r.get(0)?; + let user_name: Option = r.get(1)?; + let last_message_at: Option = r.get(2)?; Ok(Contact { - user_id: r.get(0)?, - user_name: r.get(1)?, - last_message_at: r.get(2)?, + user_id, + user_name, + last_message_at, }) }, ) { Ok(c) => Ok(Some(c)), Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), - Err(e) => Err(e.into()), + Err(e) => Err(e), } - }) { + }); + + match res { Ok(opt) => opt, Err(e) => { eprintln!("Error querying user in get_user: {}", e); @@ -56,8 +73,11 @@ pub fn get_user(storage_owner: i64, user_id: i64) -> Option { } } +/// Retrieve all contacts for a storage owner, ordered by last_message_at desc / user_id asc. pub fn get_users(storage_owner: i64) -> Vec { - match db::with_db(|conn| { + let contacts_out = Vec::new(); + + let res: Result, String> = db::with_conn(&MESSAGES_DB, |conn| { let mut stmt = conn.prepare( r#" SELECT user_id, user_name, last_message_at @@ -71,10 +91,13 @@ pub fn get_users(storage_owner: i64) -> Vec { )?; let rows = stmt.query_map(params![storage_owner], |r| { + let user_id: i64 = r.get(0)?; + let user_name: Option = r.get(1)?; + let last_message_at: Option = r.get(2)?; Ok(Contact { - user_id: r.get(0)?, - user_name: r.get(1)?, - last_message_at: r.get(2)?, + user_id, + user_name, + last_message_at, }) })?; @@ -86,11 +109,13 @@ pub fn get_users(storage_owner: i64) -> Vec { } } Ok(out) - }) { + }); + + match res { Ok(v) => v, Err(e) => { eprintln!("Failed to query contacts in get_users: {}", e); - Vec::new() + contacts_out } } } diff --git a/iota-storage/src/util/communities_util.rs b/iota-storage/src/util/communities_util.rs index c560056..ed8b7ae 100644 --- a/iota-storage/src/util/communities_util.rs +++ b/iota-storage/src/util/communities_util.rs @@ -1,22 +1,25 @@ use crate::util::db; +use json::Array; use rusqlite::params; +use std::sync::{Arc, LazyLock, Mutex}; -#[derive(Debug, Clone)] -pub struct StoredCommunity { - pub address: String, - pub title: String, - pub position: String, -} +static MESSAGES_DB: LazyLock>> = LazyLock::new(|| { + db::create_general_messages_db().expect("Failed to create or initialize general messages DB") +}); pub struct CommunitiesUtil; impl CommunitiesUtil { pub fn add_community(storage_owner: i64, address: String, title: String, position: String) { - if let Err(e) = db::with_db(|conn| { + if let Err(e) = db::with_conn(&MESSAGES_DB, |conn| { conn.execute( r#" - INSERT INTO communities (storage_owner, address, title, position) - VALUES (?1, ?2, ?3, ?4) + INSERT INTO communities ( + storage_owner, + address, + title, + position + ) VALUES (?1, ?2, ?3, ?4) ON CONFLICT(storage_owner, address) DO UPDATE SET title = excluded.title, position = excluded.position @@ -30,7 +33,7 @@ impl CommunitiesUtil { } pub fn remove_community(storage_owner: i64, community_address: String) { - if let Err(e) = db::with_db(|conn| { + if let Err(e) = db::with_conn(&MESSAGES_DB, |conn| { conn.execute( "DELETE FROM communities WHERE storage_owner = ?1 AND address = ?2", params![storage_owner, community_address], @@ -41,8 +44,10 @@ impl CommunitiesUtil { } } - pub fn get_communities(storage_owner: i64) -> Vec { - match db::with_db(|conn| { + pub fn get_communities(storage_owner: i64) -> Array { + let communities_out = Array::new(); + + let res: Result = db::with_conn(&MESSAGES_DB, |conn| { let mut stmt = conn.prepare( r#" SELECT address, title, position @@ -52,26 +57,33 @@ impl CommunitiesUtil { )?; let rows = stmt.query_map(params![storage_owner], |r| { - Ok(StoredCommunity { - address: r.get(0)?, - title: r.get(1)?, - position: r.get(2)?, - }) + let address: String = r.get(0)?; + let title: String = r.get(1)?; + let position: String = r.get(2)?; + Ok((address, title, position)) })?; - let mut out = Vec::new(); + let mut out = Array::new(); for row in rows { match row { - Ok(community) => out.push(community), + Ok((address, title, position)) => { + let mut community = json::JsonValue::new_object(); + community["title"] = json::JsonValue::String(title); + community["address"] = json::JsonValue::String(address); + community["position"] = json::JsonValue::String(position); + out.push(community); + } Err(e) => eprintln!("Failed to read community row: {}", e), } } Ok(out) - }) { - Ok(v) => v, + }); + + match res { + Ok(arr) => arr, Err(e) => { eprintln!("Failed to query communities in get_communities: {}", e); - Vec::new() + communities_out } } } diff --git a/iota-storage/src/util/config_util.rs b/iota-storage/src/util/config_util.rs index 82fbbaa..450ffb4 100644 --- a/iota-storage/src/util/config_util.rs +++ b/iota-storage/src/util/config_util.rs @@ -51,17 +51,17 @@ impl Default for IotaConfig { } pub fn load_config() { - let s = load_file("", "config.yaml"); + let s = load_file("", "config.json"); if s.is_empty() { return; } - match serde_yaml::from_str::(&s) { + match serde_json::from_str::(&s) { Ok(parsed) => { CONFIG.store(Arc::new(parsed)); } Err(e) => { - eprintln!("Failed to parse config.yaml: {}. Content: '{}'", e, s); + eprintln!("Failed to parse config.json: {}. Content: '{}'", e, s); } } } @@ -72,8 +72,8 @@ pub fn clear_config() { } pub fn save_config() { - if let Ok(yaml) = serde_yaml::to_string(&**CONFIG.load()) { - save_file("", "config.yaml", &yaml); + if let Ok(json) = serde_json::to_string(&**CONFIG.load()) { + save_file("", "config.json", &json); } } diff --git a/iota-storage/src/util/db.rs b/iota-storage/src/util/db.rs index dfe9d2d..c39a4bf 100644 --- a/iota-storage/src/util/db.rs +++ b/iota-storage/src/util/db.rs @@ -1,247 +1,88 @@ +//! Database helper utilities. +//! +//! This module provides small helpers to open/init sqlite databases and to +//! create a shared (Arc>) connection wrapper callers can +//! reuse. The goal is to centralize the "open and initialize" logic and +//! provide small convenience helpers used by other util modules. + use iota_util::file_util::get_directory; -use once_cell::sync::Lazy; -use r2d2::ManageConnection; -use rusqlite::Connection; +use rusqlite::{Connection, Error as RusqliteError}; use std::path::PathBuf; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::Duration; -use crate::storage_error::StorageError; - -const DB_NAME: &str = "messages"; - -/// A simple r2d2 manager for rusqlite connections. -pub struct SqliteManager; - -impl ManageConnection for SqliteManager { - type Connection = Connection; - type Error = rusqlite::Error; - - fn connect(&self) -> Result { - let path = db_file_path(DB_NAME); - let conn = Connection::open(path)?; - conn.execute_batch("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;")?; - conn.busy_timeout(Duration::from_millis(250))?; - Ok(conn) - } - - fn is_valid(&self, conn: &mut Connection) -> Result<(), rusqlite::Error> { - conn.execute_batch("SELECT 1") - } - - fn has_broken(&self, _conn: &mut Connection) -> bool { - false - } -} - -static POOL: Lazy>> = Lazy::new(|| { - let manager = SqliteManager; - let pool = r2d2::Pool::builder() - .max_size(8) - .build(manager) - .expect("Failed to create database connection pool"); - run_migrations(&pool).expect("Failed to run database migrations"); - Arc::new(pool) -}); - -pub fn pool() -> Arc> { - POOL.clone() -} - -pub fn with_db(f: F) -> Result -where - F: FnOnce(&Connection) -> Result, -{ - let conn = POOL.get().map_err(|e| StorageError::Pool(e.to_string()))?; - f(&conn) -} - -fn db_file_path(db_name: &str) -> String { +/// Returns the file path for a named DB inside the application's data directory. +/// +/// Arguments: +/// - `db_name` : name of the DB (without extension). Example: `"messages"`. +pub fn db_file_path(db_name: &str) -> String { let mut p = PathBuf::from(get_directory()); p.push(format!("{db_name}.sqlite3")); p.to_string_lossy().to_string() } -fn run_migrations(pool: &r2d2::Pool) -> Result<(), StorageError> { - let conn = pool.get().map_err(|e| StorageError::Pool(e.to_string()))?; - run_migrations_on_connection(&conn) -} - -/* - * Older builds could apply a schema change without advancing user_version. - * Check each added column so those databases can resume upgrading. - */ -fn add_column_if_missing( - conn: &Connection, - column: &str, - definition: &str, -) -> Result<(), StorageError> { - let mut statement = - conn.prepare("SELECT 1 FROM pragma_table_info('messages') WHERE name = ?1")?; - let exists = statement.exists([column])?; - - if !exists { - conn.execute_batch(&format!("ALTER TABLE messages ADD COLUMN {definition};"))?; - } - - Ok(()) -} - -fn run_migrations_on_connection(conn: &Connection) -> Result<(), StorageError> { - let current_version: i64 = conn - .pragma_query_value(None, "user_version", |r| r.get(0)) - .unwrap_or(0); - - if current_version < 1 { - conn.execute_batch( - r#" - CREATE TABLE IF NOT EXISTS messages ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - storage_owner INTEGER NOT NULL, - external_user INTEGER NOT NULL, - message_time INTEGER NOT NULL, - content TEXT NOT NULL, - sent_by_self INTEGER NOT NULL, - message_state TEXT NOT NULL - ); - CREATE INDEX IF NOT EXISTS idx_messages_lookup - ON messages (storage_owner, external_user, message_time DESC); - - CREATE TABLE IF NOT EXISTS contacts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - storage_owner INTEGER NOT NULL, - user_id INTEGER NOT NULL, - user_name TEXT, - last_message_at INTEGER, - UNIQUE(storage_owner, user_id) - ); - CREATE INDEX IF NOT EXISTS idx_contacts_owner - ON contacts (storage_owner, last_message_at DESC, user_id ASC); - - CREATE TABLE IF NOT EXISTS communities ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - storage_owner INTEGER NOT NULL, - address TEXT NOT NULL, - title TEXT NOT NULL, - position TEXT NOT NULL, - UNIQUE(storage_owner, address) - ); - CREATE INDEX IF NOT EXISTS idx_communities_owner - ON communities (storage_owner); - - CREATE TABLE IF NOT EXISTS users ( - user_id INTEGER PRIMARY KEY, - username TEXT NOT NULL UNIQUE, - public_key TEXT NOT NULL, - private_key_hash TEXT NOT NULL, - reset_token TEXT NOT NULL, - created_at INTEGER NOT NULL, - display_name TEXT - ); - - CREATE TABLE IF NOT EXISTS trusted_apps ( - user_id INTEGER NOT NULL, - app_id TEXT NOT NULL, - app_secret TEXT NOT NULL, - PRIMARY KEY (user_id, app_id) - ); - - PRAGMA user_version = 1; - "#, - )?; - } - - if current_version < 2 { - add_column_if_missing(conn, "height", "height INTEGER NOT NULL DEFAULT 0")?; - conn.execute_batch("PRAGMA user_version = 2;")?; - } - - if current_version < 3 { - add_column_if_missing(conn, "reply_to", "reply_to INTEGER")?; - conn.execute_batch("PRAGMA user_version = 3;")?; - } - - if current_version < 4 { - add_column_if_missing( - conn, - "edited_count", - "edited_count INTEGER NOT NULL DEFAULT 0", - )?; - add_column_if_missing( - conn, - "deleted_by_external", - "deleted_by_external INTEGER NOT NULL DEFAULT 0", - )?; - conn.execute_batch( - r#" - CREATE TABLE IF NOT EXISTS message_edits ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - message_id INTEGER NOT NULL REFERENCES messages(id), - content_before TEXT NOT NULL, - content_after TEXT NOT NULL, - edited_at INTEGER NOT NULL, - edited_by INTEGER NOT NULL - ); - CREATE INDEX IF NOT EXISTS idx_message_edits_msg - ON message_edits (message_id, edited_at DESC); - - CREATE TABLE IF NOT EXISTS reactions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - message_id INTEGER NOT NULL REFERENCES messages(id), - user_id INTEGER NOT NULL, - reaction TEXT NOT NULL, - created_at INTEGER NOT NULL, - UNIQUE(message_id, user_id, reaction) - ); - CREATE INDEX IF NOT EXISTS idx_reactions_msg - ON reactions (message_id, reaction); - - PRAGMA user_version = 4; - "#, - )?; - } - - if current_version < 5 { - conn.execute_batch( - r#" - CREATE TABLE IF NOT EXISTS settings ( - user_id INTEGER NOT NULL, - session_id INTEGER NOT NULL, - name TEXT NOT NULL, - payload TEXT NOT NULL, - PRIMARY KEY (user_id, session_id, name) - ); - CREATE INDEX IF NOT EXISTS idx_settings_lookup - ON settings (user_id, session_id, name); - - PRAGMA user_version = 5; - "#, - )?; - } - - Ok(()) -} - -pub fn open_connection(db_name: &str) -> Result { +/// Open a sqlite connection to the named DB file (no initialization). +/// +/// Arguments: +/// - `db_name`: name of the DB (without extension). +pub fn open_connection(db_name: &str) -> Result { let path = db_file_path(db_name); Connection::open(path) } +/// Open a connection and immediately run `init_sql` via `execute_batch`. +/// +/// Arguments: +/// - `db_name`: name of the DB (without extension). +/// - `init_sql`: SQL statements to initialize schema & PRAGMAs (can be multiple). +pub fn open_and_init(db_name: &str, init_sql: &str) -> Result { + let conn = open_connection(db_name)?; + conn.execute_batch(init_sql)?; + Ok(conn) +} + +/// Create a shared, Arc> initialized with the given SQL. +/// +/// This is a convenience wrapper that returns an owned Arc> +/// so caller modules can store it in a `static` or pass it around. +/// +/// Arguments: +/// - `db_name`: DB name (without extension). +/// - `init_sql`: init SQL (eg PRAGMA + CREATE TABLE statements). pub fn create_shared_connection( db_name: &str, init_sql: &str, -) -> Result>, String> { - let path = db_file_path(db_name); - let conn = Connection::open(path).map_err(|e| e.to_string())?; - conn.execute_batch(init_sql).map_err(|e| e.to_string())?; - let _ = conn.busy_timeout(Duration::from_millis(250)); - Ok(Arc::new(std::sync::Mutex::new(conn))) +) -> Result>, String> { + match open_and_init(db_name, init_sql) { + Ok(conn) => { + // Configure some sensible defaults for concurrency + // Attempt to set a busy timeout to reduce SQLITE_BUSY failures. + let _ = conn.busy_timeout(Duration::from_millis(250)); + Ok(Arc::new(Mutex::new(conn))) + } + Err(e) => Err(format!("Failed to open/init DB '{}': {}", db_name, e)), + } } -pub fn with_conn(shared: &Arc>, f: F) -> Result +/// Acquire the Connection from an Arc> and run the provided +/// closure. Converts rusqlite::Error into a String on error. +/// +/// Arguments: +/// - `shared`: Arc> +/// - `f`: closure that receives &Connection and returns Result +/// +/// Returns Ok(T) or Err(String). +pub fn with_conn(shared: &Arc>, f: F) -> Result where - F: FnOnce(&Connection) -> Result, + F: FnOnce(&Connection) -> Result, { + // When invoked from within an async runtime (such as Tokio), taking a blocking + // std::sync::Mutex lock on the runtime thread can cause deadlocks or permanent + // awaits. Detect whether we're running inside a Tokio runtime and, if so, + // execute the blocking lock + database closure using Tokio's blocking helper. + // + // The blocking section returns Result so we can propagate errors + // in the same form as before. if tokio::runtime::Handle::try_current().is_ok() { tokio::task::block_in_place(|| { let guard = shared @@ -257,44 +98,97 @@ where } } -/// Legacy - kept for e2ee_storage which uses its own DB. -pub fn create_general_messages_db() -> Result>, String> { - create_shared_connection(DB_NAME, "") -} +/// Initialize a general-purpose messages+contacts DB and return a shared +/// connection. This helper creates a single DB file that can contain multiple +/// tables (messages, contacts, ...). The SQL here is conservative and intended +/// to be safe if called multiple times. +/// +/// Callers may prefer to call `create_shared_connection("messages", INIT_SQL)` +/// directly, but this convenience is useful for code that expects both tables. +pub fn create_general_messages_db() -> Result>, String> { + // Keep PRAGMA and schema in one multi-statement string so callers only + // need to call a single execute_batch. + const INIT_SQL: &str = r#" + PRAGMA journal_mode = WAL; + PRAGMA synchronous = NORMAL; -#[cfg(test)] -mod tests { - use super::*; + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + storage_owner INTEGER NOT NULL, + external_user INTEGER NOT NULL, + message_time INTEGER NOT NULL, + content TEXT NOT NULL, + sent_by_self INTEGER NOT NULL, + message_state TEXT NOT NULL, + height INTEGER NOT NULL DEFAULT 0 + ); - #[test] - fn resumes_migration_when_height_exists_before_its_version() -> Result<(), StorageError> { - let conn = Connection::open_in_memory()?; - conn.execute_batch( - r#" - CREATE TABLE messages ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - storage_owner INTEGER NOT NULL, - external_user INTEGER NOT NULL, - message_time INTEGER NOT NULL, - content TEXT NOT NULL, - sent_by_self INTEGER NOT NULL, - message_state TEXT NOT NULL - ); - ALTER TABLE messages ADD COLUMN height INTEGER NOT NULL DEFAULT 0; - PRAGMA user_version = 1; - "#, - )?; + CREATE INDEX IF NOT EXISTS idx_messages_lookup + ON messages (storage_owner, external_user, message_time DESC); - run_migrations_on_connection(&conn)?; + CREATE TABLE IF NOT EXISTS contacts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + storage_owner INTEGER NOT NULL, + user_id INTEGER NOT NULL, + user_name TEXT, + last_message_at INTEGER, + UNIQUE(storage_owner, user_id) + ); - let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?; - assert_eq!(version, 5); - for column in ["height", "reply_to", "edited_count", "deleted_by_external"] { - let mut statement = - conn.prepare("SELECT 1 FROM pragma_table_info('messages') WHERE name = ?1")?; - assert!(statement.exists([column])?); + CREATE INDEX IF NOT EXISTS idx_contacts_owner + ON contacts (storage_owner, last_message_at DESC, user_id ASC); + + CREATE TABLE IF NOT EXISTS communities ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + storage_owner INTEGER NOT NULL, + address TEXT NOT NULL, + title TEXT NOT NULL, + position TEXT NOT NULL, + UNIQUE(storage_owner, address) + ); + + CREATE INDEX IF NOT EXISTS idx_communities_owner + ON communities (storage_owner); + "#; + + match create_shared_connection("messages", INIT_SQL) { + Ok(shared_conn) => { + // Attempt to add the height column for backwards compatibility. + // This will fail if the column already exists, which is expected. + let _ = with_conn(&shared_conn, |conn| { + let _ = conn.execute( + "ALTER TABLE messages ADD COLUMN height INTEGER NOT NULL DEFAULT 0", + [], + ); + Ok(()) + }); + // Attempt to add the reply_to column for backwards compatibility. + // This will fail if the column already exists, which is expected. + let _ = with_conn(&shared_conn, |conn| { + let _ = conn.execute( + "ALTER TABLE messages ADD COLUMN reply_to INTEGER", + [], + ); + Ok(()) + }); + Ok(shared_conn) } - - Ok(()) + Err(e) => Err(e), } } + +/* +Example usage: + +// In some util module (at init time, e.g. lazy_static or LazyLock) +static MESSAGES_DB: LazyLock>> = LazyLock::new(|| { + create_general_messages_db().expect("failed to create messages DB") +}); + +// Later, to run a query: +let res: Result, String> = with_conn(&MESSAGES_DB, |conn| { + let mut stmt = conn.prepare("SELECT ...")?; + let rows = stmt.query_map(...)?; + // collect and return Ok(...) +}); +*/ diff --git a/iota-storage/src/util/e2ee_storage.rs b/iota-storage/src/util/e2ee_storage.rs index ad12661..d26a092 100644 --- a/iota-storage/src/util/e2ee_storage.rs +++ b/iota-storage/src/util/e2ee_storage.rs @@ -1,5 +1,5 @@ use crate::util::db; -use rusqlite::{OptionalExtension, params}; +use rusqlite::{params, OptionalExtension}; use std::sync::{Arc, LazyLock, Mutex}; pub type StorageError = String; @@ -226,7 +226,9 @@ fn chat_secret_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result) -> rusqlite::Result { +fn pending_forward_from_row( + row: &rusqlite::Row<'_>, +) -> rusqlite::Result { Ok(PendingChatSecretForward { recipient_user_id: row.get(0)?, chat_id: row.get(1)?, diff --git a/iota-storage/src/util/mod.rs b/iota-storage/src/util/mod.rs index 2fa8220..d5b2e4d 100644 --- a/iota-storage/src/util/mod.rs +++ b/iota-storage/src/util/mod.rs @@ -4,4 +4,3 @@ pub mod communities_util; pub mod config_util; pub mod db; pub mod e2ee_storage; -pub mod settings; diff --git a/iota-storage/src/util/settings.rs b/iota-storage/src/util/settings.rs deleted file mode 100644 index 6fae52b..0000000 --- a/iota-storage/src/util/settings.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::storage_error::StorageError; -use crate::util::db; -use iota_util::file_util::get_directory; -use rusqlite::{OptionalExtension, params}; -use std::fs; -use std::path::Path; - -pub const GLOBAL_SESSION_ID: i64 = 0; -const GLOBAL_SETTINGS_NAME: &str = "__global__"; - -pub fn save(user_id: i64, session_id: i64, name: &str, payload: &str) -> Result<(), StorageError> { - db::with_db(|conn| { - conn.execute( - "INSERT INTO settings (user_id, session_id, name, payload) VALUES (?1, ?2, ?3, ?4)\n ON CONFLICT(user_id, session_id, name) DO UPDATE SET payload = excluded.payload", - params![user_id, session_id, name, payload], - )?; - Ok(()) - }) -} - -pub fn load(user_id: i64, session_id: i64, name: &str) -> Result, StorageError> { - db::with_db(|conn| { - conn.query_row( - "SELECT payload FROM settings WHERE user_id = ?1 AND session_id = ?2 AND name = ?3", - params![user_id, session_id, name], - |row| row.get(0), - ) - .optional() - .map_err(StorageError::from) - }) -} - -pub fn list(user_id: i64, session_id: i64) -> Result, StorageError> { - db::with_db(|conn| { - let mut statement = conn.prepare( - "SELECT name FROM settings WHERE user_id = ?1 AND session_id = ?2 ORDER BY name", - )?; - let rows = statement.query_map(params![user_id, session_id], |row| row.get(0))?; - rows.collect::, _>>() - .map_err(StorageError::from) - }) -} - -pub fn save_global(user_id: i64, payload: &str) -> Result<(), StorageError> { - save(user_id, GLOBAL_SESSION_ID, GLOBAL_SETTINGS_NAME, payload) -} - -pub fn load_global(user_id: i64) -> Result, StorageError> { - load(user_id, GLOBAL_SESSION_ID, GLOBAL_SETTINGS_NAME) -} - -pub fn migrate_legacy_files() -> Result<(), StorageError> { - let users_dir = Path::new(&get_directory()).join("users"); - let Ok(users) = fs::read_dir(users_dir) else { - return Ok(()); - }; - - for user_entry in users { - let user_entry = user_entry?; - let Ok(user_id) = user_entry.file_name().to_string_lossy().parse::() else { - continue; - }; - let user_dir = user_entry.path(); - - migrate_file_if_missing( - user_id, - GLOBAL_SESSION_ID, - GLOBAL_SETTINGS_NAME, - &user_dir.join("global.settings"), - )?; - - let settings_dir = user_dir.join("settings"); - let Ok(settings_entries) = fs::read_dir(settings_dir) else { - continue; - }; - for settings_entry in settings_entries { - let settings_entry = settings_entry?; - let path = settings_entry.path(); - if path.is_file() { - if let Some(name) = setting_name(&path) { - migrate_file_if_missing(user_id, GLOBAL_SESSION_ID, &name, &path)?; - } - continue; - } - - let Ok(session_id) = settings_entry.file_name().to_string_lossy().parse::() else { - continue; - }; - let Ok(device_settings) = fs::read_dir(path) else { - continue; - }; - for setting_entry in device_settings { - let setting_entry = setting_entry?; - let path = setting_entry.path(); - if let Some(name) = setting_name(&path) { - migrate_file_if_missing(user_id, session_id, &name, &path)?; - } - } - } - } - - Ok(()) -} - -fn setting_name(path: &Path) -> Option { - (path.extension()?.to_str()? == "settings").then(|| { - path.file_stem() - .and_then(|name| name.to_str()) - .unwrap_or_default() - .to_string() - }) -} - -fn migrate_file_if_missing( - user_id: i64, - session_id: i64, - name: &str, - path: &Path, -) -> Result<(), StorageError> { - if !path.is_file() || load(user_id, session_id, name)?.is_some() { - return Ok(()); - } - let payload = fs::read_to_string(path)?; - save(user_id, session_id, name, &payload) -} - -#[cfg(test)] -mod tests { - use super::*; - use rusqlite::Connection; - - #[test] - fn settings_schema_supports_user_and_session_keys() -> Result<(), StorageError> { - let conn = Connection::open_in_memory()?; - conn.execute_batch( - "CREATE TABLE settings ( - user_id INTEGER NOT NULL, - session_id INTEGER NOT NULL, - name TEXT NOT NULL, - payload TEXT NOT NULL, - PRIMARY KEY (user_id, session_id, name) - );", - )?; - - conn.execute( - "INSERT INTO settings VALUES (?1, ?2, ?3, ?4)", - params![7, 11, "theme", "dark"], - )?; - conn.execute( - "INSERT INTO settings VALUES (?1, ?2, ?3, ?4)", - params![7, 12, "theme", "light"], - )?; - - let payload: String = conn.query_row( - "SELECT payload FROM settings WHERE user_id = 7 AND session_id = 11 AND name = 'theme'", - [], - |row| row.get(0), - )?; - assert_eq!(payload, "dark"); - - Ok(()) - } -} diff --git a/iota-util/Cargo.toml b/iota-util/Cargo.toml index 23ed0b7..e88cebf 100644 --- a/iota-util/Cargo.toml +++ b/iota-util/Cargo.toml @@ -4,9 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [ - "crypto" -] } +mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } +mtp-crypto = { git = "https://git.methanium.net/Methanium/mtp.git", features = ["pqc"] } reqwest = "0.13.2" tokio = { version = "1.50.0", features = ["full"] } diff --git a/iota-util/src/crypto_helper.rs b/iota-util/src/crypto_helper.rs index 9375eb4..2909c04 100644 --- a/iota-util/src/crypto_helper.rs +++ b/iota-util/src/crypto_helper.rs @@ -1,5 +1,5 @@ use base64::{Engine as _, engine::general_purpose::STANDARD}; -use mtp::crypto::{Keyring, PublicKeyBundle}; +use mtp_crypto::{Keyring, PublicKeyBundle}; pub fn generate_keyring() -> Keyring { Keyring::generate() @@ -24,5 +24,5 @@ pub fn public_key_bundle_from_base64(s: &str) -> Option { } pub fn hex_hash(input: &str) -> String { - hex::encode(mtp::crypto::sha256(input.as_bytes())) + hex::encode(mtp_crypto::sha256(input.as_bytes())) } diff --git a/iota-util/src/crypto_util.rs b/iota-util/src/crypto_util.rs index 010ceae..defc41c 100644 --- a/iota-util/src/crypto_util.rs +++ b/iota-util/src/crypto_util.rs @@ -1,5 +1,5 @@ use base64::{Engine as _, engine::general_purpose::STANDARD}; -use mtp::crypto::{EncryptionType, Keyring, PublicKeyBundle, decrypt_with, encrypt_for}; +use mtp_crypto::{EncryptionType, Keyring, PublicKeyBundle, encrypt_for, decrypt_with}; #[derive(Clone, Copy, Debug)] pub enum DataFormat { @@ -22,8 +22,13 @@ pub fn encrypt( .map_err(|e| format!("encryption error: {:?}", e)) } -pub fn decrypt(ciphertext: &[u8], aad: &[u8], keyring: &Keyring) -> Result, String> { - decrypt_with(ciphertext, keyring, aad).map_err(|e| format!("decryption error: {:?}", e)) +pub fn decrypt( + ciphertext: &[u8], + aad: &[u8], + keyring: &Keyring, +) -> Result, String> { + decrypt_with(ciphertext, keyring, aad) + .map_err(|e| format!("decryption error: {:?}", e)) } pub fn encrypt_challenge( @@ -34,10 +39,12 @@ pub fn encrypt_challenge( Ok(STANDARD.encode(&blob)) } -pub fn decrypt_challenge(encrypted: &str, keyring: &Keyring) -> Result { - let blob = STANDARD - .decode(encrypted) - .map_err(|e| format!("base64 decode error: {}", e))?; +pub fn decrypt_challenge( + encrypted: &str, + keyring: &Keyring, +) -> Result { + let blob = + STANDARD.decode(encrypted).map_err(|e| format!("base64 decode error: {}", e))?; let pt = decrypt(&blob, b"challenge", keyring)?; String::from_utf8(pt).map_err(|e| format!("utf8 decode error: {}", e)) } diff --git a/omikron-connector/Cargo.toml b/omikron-connector/Cargo.toml index f083794..caaab62 100644 --- a/omikron-connector/Cargo.toml +++ b/omikron-connector/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] -iota-connection = { path = "../iota-connection" } iota-logger = { path = "../iota-logger" } iota-state = { path = "../iota-state" } iota-storage = { path = "../iota-storage" } diff --git a/omikron-connector/src/omikron_connection.rs b/omikron-connector/src/omikron_connection.rs index e68a12f..19ed8da 100755 --- a/omikron-connector/src/omikron_connection.rs +++ b/omikron-connector/src/omikron_connection.rs @@ -1,27 +1,159 @@ use dashmap::DashMap; use iota_logger::{log, log_cv_in, log_cv_out, log_t}; use iota_state::{ACTIVE_TASKS, SHUTDOWN}; +use iota_storage::users::contact::Contact; use iota_storage::util::chat_files::{self, MessageState, change_message_state}; +use iota_storage::util::chats_util::{self, get_user, mod_user}; +use iota_storage::util::communities_util::CommunitiesUtil; use iota_storage::util::config_util::{CONFIG, modify_config}; -use iota_storage::util::e2ee_storage::{self, PendingChatSecretForward, StoredChatSecret}; +use iota_storage::util::e2ee_storage::{ + self, ChatSecretQuery, PendingChatSecretForward, StoredChatSecret, +}; use iota_util::crypto_helper::{self, keyring_from_base64}; use iota_util::crypto_util::{self}; +use iota_util::file_util::{get_children, has_file, load_file, save_file}; use mtp::client::{Client, ClientConfig, Policy, Receiver, SendMode, Sender}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use mtp::crypto::{Keyring, PublicKeyBundle}; +use mtp::type_map::TypeMap; +use std::collections::HashMap; use std::env; -use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, LazyLock}; -use std::time::{Duration, Instant}; -use tokio::sync::{Mutex, RwLock, Semaphore, oneshot, watch}; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use tokio::sync::{Mutex, RwLock, mpsc, watch}; use tokio::task::JoinHandle; use tokio::time::sleep; use uuid::Uuid; use crate::omega_discovery; -use iota_connection::message_common::*; -use iota_connection::message_handlers; +fn typed_container(items: Vec<(DataType, DataValue)>) -> DataValue { + use mtp::type_map::{DataTypeId, TypeMap}; + let tm = TypeMap::latest(); + DataValue::Container( + items + .into_iter() + .filter_map(|(dt, dv)| tm.data_id_enum(dt).map(|id| (DataTypeId(id), dv))) + .collect(), + ) +} + +fn data_string(cv: &CommunicationValue, dt: DataType) -> Option { + cv.get_data(dt) + .as_str() + .map(|s| s.to_string()) + .or_else(|| cv.get_data(dt).as_number().map(|n| n.to_string())) + .or_else(|| cv.get_data(dt).as_signed_number().map(|n| n.to_string())) +} + +fn data_i64(cv: &CommunicationValue, dt: DataType) -> Option { + cv.get_data(dt) + .as_number() + .and_then(|n| i64::try_from(n).ok()) + .or_else(|| { + cv.get_data(dt) + .as_signed_number() + .and_then(|n| i64::try_from(n).ok()) + }) + .or_else(|| cv.get_data(dt).as_str().and_then(|s| s.parse::().ok())) +} + +#[derive(Debug, Clone)] +struct ChatSecretRecipient { + user_id: String, + encrypted_secret: Vec, + kem_ciphertext: Vec, +} + +fn recipient_from_value(value: &DataValue) -> Option { + let tm = TypeMap::latest(); + let user_id = value + .get_field(DataType::UserId.to_id(&tm))? + .as_str() + .map(|s| s.to_string()) + .or_else(|| { + value + .get_field(DataType::UserId.to_id(&tm))? + .as_number() + .map(|n| n.to_string()) + })?; + let encrypted_secret = value + .get_field(DataType::EncryptedSecret.to_id(&tm))? + .as_bytes()?; + let kem_ciphertext = value + .get_field(DataType::KemCiphertext.to_id(&tm))? + .as_bytes()?; + + Some(ChatSecretRecipient { + user_id, + encrypted_secret, + kem_ciphertext, + }) +} + +fn chat_secret_recipients(cv: &CommunicationValue) -> Option> { + let recipients = cv.get_data(DataType::Recipients).as_array()?; + let parsed = recipients + .iter() + .map(recipient_from_value) + .collect::>>()?; + + if parsed.is_empty() { + None + } else { + Some(parsed) + } +} + +fn set_chat_secret_cv_for_recipient( + source: &CommunicationValue, + recipient: &ChatSecretRecipient, +) -> CommunicationValue { + let recipient_value = typed_container(vec![ + (DataType::UserId, DataValue::Str(recipient.user_id.clone())), + ( + DataType::EncryptedSecret, + DataValue::Bytes(recipient.encrypted_secret.clone()), + ), + ( + DataType::KemCiphertext, + DataValue::Bytes(recipient.kem_ciphertext.clone()), + ), + ]); + + CommunicationValue::new(CommunicationType::SetChatSecret) + .with_id(source.get_id()) + .with_sender(source.get_sender()) + .with_receiver(recipient.user_id.parse::().unwrap_or(0)) + .add_typed_default(DataType::ChatId, source.get_data(DataType::ChatId).clone()) + .add_typed_default( + DataType::SecretId, + source.get_data(DataType::SecretId).clone(), + ) + .add_typed_default( + DataType::VersionNumber, + source.get_data(DataType::VersionNumber).clone(), + ) + .add_typed_default( + DataType::WrappingScheme, + source.get_data(DataType::WrappingScheme).clone(), + ) + .add_typed_default( + DataType::CreatedAt, + source.get_data(DataType::CreatedAt).clone(), + ) + .add_typed_default( + DataType::Recipients, + DataValue::Array(vec![recipient_value]), + ) +} + +fn now_millis_i64() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64 +} fn pending_chat_secret_forward_from_cv( cv: &CommunicationValue, @@ -80,6 +212,12 @@ fn chat_secret_forward_cv(record: &PendingChatSecretForward) -> CommunicationVal .add_typed_default(DataType::Recipients, DataValue::Array(vec![recipient])) } +fn error_response(request: &CommunicationValue, ty: CommunicationType) -> CommunicationValue { + CommunicationValue::new(ty) + .with_id(request.get_id()) + .with_receiver(request.get_sender()) +} + // Helper function to check if read receipts are enabled globally async fn is_read_receipts_enabled() -> bool { CONFIG.load().read_receipts_enabled @@ -97,15 +235,13 @@ const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); const TASK_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); const TASK_MAX_AGE: Duration = Duration::from_secs(60); -const MAX_MISSED_PONGS: u32 = 3; -const MAX_CONCURRENT_HANDLERS: usize = 20; // ============================================================================ // Waiting Task System // ============================================================================ pub struct WaitingTask { - pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub task: Box, CommunicationValue) -> bool + Send + Sync>, pub inserted_at: Instant, } @@ -148,7 +284,6 @@ impl ConnectionState { #[allow(dead_code)] // message_send_times is unused. pub struct OmikronConnection { state: Arc>, - state_watch_tx: watch::Sender, sender: Arc>>>, connection_loop_handle: Arc>>>, pub last_ping: Arc>, @@ -157,20 +292,16 @@ pub struct OmikronConnection { shutdown_tx: Arc>>>, reconnect_on_close: Arc>, auth_failure: Arc>>, - pub app_challenges: Arc>, - pub app_sessions: Arc>, - pub(crate) missed_pongs: Arc, - handler_semaphore: Arc, + pub app_challenges: Arc>>, + pub app_sessions: Arc>>, } impl OmikronConnection { pub fn new() -> Self { let (shutdown_tx, _) = watch::channel(false); - let (state_watch_tx, _) = watch::channel(ConnectionState::Disconnected); OmikronConnection { state: Arc::new(RwLock::new(ConnectionState::Disconnected)), - state_watch_tx, sender: Arc::new(RwLock::new(None)), connection_loop_handle: Arc::new(Mutex::new(None)), last_ping: Arc::new(Mutex::new(-1)), @@ -179,18 +310,11 @@ impl OmikronConnection { shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))), reconnect_on_close: Arc::new(RwLock::new(true)), auth_failure: Arc::new(RwLock::new(None)), - app_challenges: Arc::new(DashMap::new()), - app_sessions: Arc::new(DashMap::new()), - missed_pongs: Arc::new(AtomicU32::new(0)), - handler_semaphore: Arc::new(Semaphore::new(MAX_CONCURRENT_HANDLERS)), + app_challenges: Arc::new(RwLock::new(HashMap::new())), + app_sessions: Arc::new(RwLock::new(HashMap::new())), } } - async fn set_state(&self, new_state: ConnectionState) { - *self.state.write().await = new_state; - let _ = self.state_watch_tx.send(new_state); - } - // ------------------------------------------------------------------------- // Connection Management // ------------------------------------------------------------------------- @@ -240,7 +364,7 @@ impl OmikronConnection { sender.close(); } - self.set_state(ConnectionState::Disconnected).await; + *self.state.write().await = ConnectionState::Disconnected; *self.sender.write().await = None; } @@ -294,7 +418,7 @@ impl OmikronConnection { } async fn connect_once(self: Arc) -> Result<(), String> { - self.set_state(ConnectionState::Connecting).await; + *self.state.write().await = ConnectionState::Connecting; log_t!("omikron_connecting"); let keyring = self.load_or_migrate_keyring().await; @@ -313,7 +437,6 @@ impl OmikronConnection { .with_policy(Policy { send_mode: SendMode::SingleStreamPerMessage, max_message_size: 1_000_000_000, - handshake_max_message_size: 1_000_000_000, close_frame_len: u32::MAX, application_close_code: 0, open_stream_timeout: Duration::from_millis(2_000), @@ -323,11 +446,9 @@ impl OmikronConnection { keep_alive_interval: Some(Duration::from_secs(6)), max_idle_timeout: Some(Duration::from_secs(30)), force_close_delay: Duration::from_millis(300), + max_transient_recv_errors: 20, + transient_recv_backoff: Duration::from_millis(100), receiver_queue_capacity: 1000, - max_concurrent_stream_tasks: 10, - persistent_stream_max_retries: 5, - persistent_stream_retry_backoff: Duration::from_secs(5), - max_frames_per_stream: None, }); let connection = match Client::auth_connect_or_register( @@ -346,7 +467,7 @@ impl OmikronConnection { ); *self.reconnect_on_close.write().await = false; *self.auth_failure.write().await = Some(reason.clone()); - self.set_state(ConnectionState::Disconnected).await; + *self.state.write().await = ConnectionState::Disconnected; return Err(reason); } Err(e) => return Err(format!("Connection failed: {}", e)), @@ -361,8 +482,7 @@ impl OmikronConnection { let sender_arc = Arc::new(connection.sender); *self.sender.write().await = Some(sender_arc.clone()); - self.set_state(ConnectionState::Connected { identified: true }) - .await; + *self.state.write().await = ConnectionState::Connected { identified: true }; // Start read loop let mut receiver = connection.receiver; @@ -387,7 +507,7 @@ impl OmikronConnection { // Wait for read loop to complete let result = read_handle.await; *self.sender.write().await = None; - self.set_state(ConnectionState::Disconnected).await; + *self.state.write().await = ConnectionState::Disconnected; { ACTIVE_TASKS.remove("Omikron Listener"); } @@ -420,25 +540,16 @@ impl OmikronConnection { * that still read it directly. */ async fn load_or_migrate_keyring(&self) -> Keyring { - if let Ok(kr) = mtp::files::load_keyring_raw(IOTA_KEYRING_PATH) { + if let Ok(kr) = mtp::files::load_keyring(IOTA_KEYRING_PATH) { return kr; } let legacy = CONFIG.load().keyring.clone(); let keyring = legacy .and_then(|b64| keyring_from_base64(&b64)) - .unwrap_or_else(|| { - log!( - "WARNING: No existing keyring found. Neither {} nor config.json \ - contain a keyring; generating a new identity. If you already had \ - an Iota identity, restore {} from a backup to avoid losing access.", - IOTA_KEYRING_PATH, - IOTA_KEYRING_PATH - ); - crypto_helper::generate_keyring() - }); + .unwrap_or_else(crypto_helper::generate_keyring); - if let Err(e) = mtp::files::save_keyring_raw(&keyring, IOTA_KEYRING_PATH) { + if let Err(e) = mtp::files::save_keyring(&keyring, IOTA_KEYRING_PATH) { log!("Failed to persist {}: {}", IOTA_KEYRING_PATH, e); } @@ -561,23 +672,7 @@ impl OmikronConnection { let result = receiver.receive().await; match result { Ok(cv) => { - let msg_id = cv.get_id(); - if let Some((_, task)) = WAITING_TASKS.remove(&msg_id) { - if (task.task)(self.clone(), cv.clone()) { - continue; - } - } - if cv.is_type(CommunicationType::Pong) { - self.handle_pong(&cv).await; - continue; - } - - let permit = self.handler_semaphore.clone().acquire_owned().await; - let self_clone = self.clone(); - tokio::spawn(async move { - let _permit = permit; - self_clone.handle_message_impl(cv).await; - }); + self.clone().handle_message(cv).await; } Err(e) => { self.fail_all_waiting_tasks(format!( @@ -615,17 +710,6 @@ impl OmikronConnection { break; } - if self.missed_pongs.load(Ordering::Relaxed) > MAX_MISSED_PONGS { - log!( - "Connection appears dead ({} consecutive missed pongs), closing sender", - self.missed_pongs.load(Ordering::Relaxed) - ); - if let Some(sender) = self.sender.read().await.as_ref() { - sender.close(); - } - break; - } - self.flush_pending_chat_secret_forwards().await; self.send_ping().await; } @@ -670,151 +754,8 @@ impl OmikronConnection { } } - async fn forward_message_live( - &self, - message_id: u32, - receiver_id: u64, - sender_id: i64, - timestamp: i64, - content: &str, - height: i64, - reply_to: Option, - ) -> Option { - let mut msg_fields = vec![ - (DataType::Content, DataValue::Str(content.to_string())), - ( - DataType::SendTime, - DataValue::SignedNumber(timestamp as i128), - ), - (DataType::Height, DataValue::SignedNumber(height as i128)), - ]; - if let Some(rt) = reply_to { - msg_fields.push(( - DataType::ReplyId, - DataValue::UnsignedNumber(rt as u64 as u128), - )); - } - - let user_forward = CommunicationValue::new(CommunicationType::MessageLive) - .with_id(message_id) - .with_receiver(receiver_id) - .add_typed_default( - DataType::SenderId, - DataValue::SignedNumber(sender_id as i128), - ) - .add_typed_default(DataType::Message, typed_container(msg_fields)); - - match self - .await_response(&user_forward, Some(Duration::from_secs(3))) - .await - { - Ok(user_resp) => { - let ms_raw = user_resp - .get_data(DataType::MessageState) - .as_string() - .unwrap_or_else(|| "".to_string()); - Some(MessageState::from_str(&ms_raw).upgrade(MessageState::Received)) - } - Err(_) => None, - } - } - - async fn forward_to_remote_iota( - &self, - cv: &CommunicationValue, - sender_id: i64, - receiver_id: i64, - timestamp: i64, - content: &str, - height: i64, - reply_to: Option, - ) { - let mut fw_msg = CommunicationValue::new(CommunicationType::MessageOtherIota) - .with_id(cv.get_id()) - .with_receiver(receiver_id as u64) - .with_sender(sender_id as u64) - .add_typed_default(DataType::Height, DataValue::SignedNumber(height as i128)) - .add_typed_default(DataType::Content, DataValue::Str(content.to_string())) - .add_typed_default( - DataType::SendTime, - DataValue::SignedNumber(timestamp as i128), - ); - if let Some(rt) = reply_to { - fw_msg = fw_msg.add_typed_default( - DataType::ReplyId, - DataValue::UnsignedNumber(rt as u64 as u128), - ); - } - - match self - .await_response(&fw_msg, Some(Duration::from_secs(10))) - .await - { - Ok(resp) => { - let ms_raw = resp - .get_data(DataType::MessageState) - .as_string() - .unwrap_or_else(|| "".to_string()); - let ms = MessageState::from_str(&ms_raw).upgrade(MessageState::Received); - - let _ = - chat_files::change_message_state(timestamp, sender_id, receiver_id, ms.clone()); - - let _ = self - .send_message( - &CommunicationValue::new(CommunicationType::MessageState) - .with_id(cv.get_id()) - .with_receiver(sender_id as u64) - .with_sender(receiver_id as u64) - .add_typed_default( - DataType::ChatPartnerId, - DataValue::SignedNumber(receiver_id as i128), - ) - .add_typed_default( - DataType::SendTime, - DataValue::SignedNumber(timestamp as i128), - ) - .add_typed_default( - DataType::MessageState, - DataValue::Str(ms.as_str().to_string()), - ), - ) - .await; - } - Err(_) => { - let _ = chat_files::change_message_state( - timestamp, - sender_id, - receiver_id, - MessageState::Sent, - ); - - let _ = self - .send_message( - &CommunicationValue::new(CommunicationType::MessageState) - .with_id(cv.get_id()) - .with_receiver(sender_id as u64) - .with_sender(receiver_id as u64) - .add_typed_default( - DataType::ChatPartnerId, - DataValue::SignedNumber(receiver_id as i128), - ) - .add_typed_default( - DataType::SendTime, - DataValue::SignedNumber(timestamp as i128), - ) - .add_typed_default( - DataType::MessageState, - DataValue::Str(MessageState::Sent.as_str().to_string()), - ), - ) - .await; - } - } - } - // ------------------------------------------------------------------------- - // Message Handling — Dispatch + // Message Handling (Preserved from original) // ------------------------------------------------------------------------- pub async fn handle_message(self: Arc, cv: CommunicationValue) { @@ -824,6 +765,7 @@ impl OmikronConnection { let msg_id = cv.get_id(); + // Dispatch waiting task for this message id if let Some((_, task)) = WAITING_TASKS.remove(&msg_id) { if (task.task)(self.clone(), cv.clone()) { return; @@ -835,796 +777,687 @@ impl OmikronConnection { return; } - self.clone().handle_message_impl(cv).await; - } - - async fn handle_message_impl(self: Arc, cv: CommunicationValue) { - macro_rules! dispatch { - ($ty:ident, $method:ident) => { - if cv.is_type(CommunicationType::$ty) { - self.clone().$method(&cv).await; - return; - } - }; - } - - dispatch!(SetChatSecret, handle_set_chat_secret); - dispatch!(GetChatSecret, handle_get_chat_secret); - dispatch!(ChatSecretForward, handle_chat_secret_forward); - dispatch!(AppIdentification, handle_app_identification); - dispatch!(AppChallengeResponse, handle_app_challenge_response); - dispatch!(SaveAppData, handle_save_app_data); - dispatch!(LoadAppData, handle_load_app_data); - dispatch!(CreateApp, handle_create_app); - dispatch!(DeleteApp, handle_delete_app); - dispatch!(ClientConnected, handle_client_connected); - dispatch!(MessageState, handle_message_state); - dispatch!(MessageSend, handle_message_send); - dispatch!(MessageEdit, handle_message_edit); - dispatch!(MessageEditLive, handle_message_edit_live); - dispatch!(MessageReactionAdd, handle_message_reaction_add); - dispatch!(MessageReactionRemove, handle_message_reaction_remove); - dispatch!(MessageReactionLive, handle_message_reaction_live); - dispatch!(MessageDeleteLive, handle_message_delete_live); - dispatch!(MessageOtherIota, handle_message_other_iota); - dispatch!(MessagesGet, handle_messages_get); - dispatch!(GetChats, handle_get_chats); - dispatch!(AddConversation, handle_add_conversation); - dispatch!(AddCommunity, handle_add_community); - dispatch!(GetCommunities, handle_get_communities); - dispatch!(RemoveCommunity, handle_remove_community); - dispatch!(GlobalSettingsSave, handle_global_settings_save); - dispatch!(GlobalSettingsLoad, handle_global_settings_load); - dispatch!(SettingsSave, handle_settings_save); - dispatch!(SettingsLoad, handle_settings_load); - dispatch!(SettingsList, handle_settings_list); - } - - // ------------------------------------------------------------------------- - // Message Handlers - // ------------------------------------------------------------------------- - - async fn handle_set_chat_secret(self: Arc, cv: &CommunicationValue) { - let sender_id = cv.get_sender().to_string(); - let recipients = match chat_secret_recipients(cv) { - Some(recipients) => recipients, - None => { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) - .await; - return; - } - }; - let now = now_millis_i64(); - let chat_id = data_string(cv, DataType::ChatId); - let secret_id = data_string(cv, DataType::SecretId); - let version = data_i64(cv, DataType::VersionNumber); - let wrapping_scheme = data_string(cv, DataType::WrappingScheme); - let created_at = data_i64(cv, DataType::CreatedAt).unwrap_or(now); - - let Some((((chat_id, secret_id), version), wrapping_scheme)) = - chat_id.zip(secret_id).zip(version).zip(wrapping_scheme) - else { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) - .await; - return; - }; - - let mut non_local_forwards: Vec = Vec::new(); - - for recipient in &recipients { - let recipient_id = recipient.user_id.parse::().unwrap_or(0); - let is_local = iota_storage::users::user_manager::get_user(recipient_id).is_some(); - - if is_local { - if e2ee_storage::put_chat_secret(StoredChatSecret { - user_id: recipient.user_id.clone(), - chat_id: chat_id.clone(), - secret_id: secret_id.clone(), - version, - encrypted_secret: recipient.encrypted_secret.clone(), - kem_ciphertext: recipient.kem_ciphertext.clone(), - wrapping_scheme: wrapping_scheme.clone(), - created_at, - updated_at: now, - }) - .is_err() - { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) + if cv.is_type(CommunicationType::SetChatSecret) { + let sender_id = cv.get_sender().to_string(); + let recipients = match chat_secret_recipients(&cv) { + Some(recipients) => recipients, + None => { + self.send_message(&error_response(&cv, CommunicationType::ErrorInvalidData)) .await; return; } - continue; - } + }; + let now = now_millis_i64(); + let chat_id = data_string(&cv, DataType::ChatId); + let secret_id = data_string(&cv, DataType::SecretId); + let version = data_i64(&cv, DataType::VersionNumber); + let wrapping_scheme = data_string(&cv, DataType::WrappingScheme); + let created_at = data_i64(&cv, DataType::CreatedAt).unwrap_or(now); - if recipient.user_id != sender_id { - non_local_forwards.push(set_chat_secret_cv_for_recipient(cv, recipient)); - } - } + let Some((((chat_id, secret_id), version), wrapping_scheme)) = + chat_id.zip(secret_id).zip(version).zip(wrapping_scheme) + else { + self.send_message(&error_response(&cv, CommunicationType::ErrorInvalidData)) + .await; + return; + }; - if !non_local_forwards.is_empty() { - let mut handles = Vec::new(); - for forward in &non_local_forwards { - let self_clone = self.clone(); - let fwd = forward.clone(); - handles.push(tokio::spawn(async move { - self_clone.forward_chat_secret(&fwd).await - })); - } + for recipient in &recipients { + let recipient_id = recipient.user_id.parse::().unwrap_or(0); + let is_local = iota_storage::users::user_manager::get_user(recipient_id).is_some(); - for (forward, handle) in non_local_forwards.into_iter().zip(handles) { - match handle.await { - Ok(true) => {} - _ => self.store_pending_chat_secret_forward(&forward).await, + if is_local { + if e2ee_storage::put_chat_secret(StoredChatSecret { + user_id: recipient.user_id.clone(), + chat_id: chat_id.clone(), + secret_id: secret_id.clone(), + version, + encrypted_secret: recipient.encrypted_secret.clone(), + kem_ciphertext: recipient.kem_ciphertext.clone(), + wrapping_scheme: wrapping_scheme.clone(), + created_at, + updated_at: now, + }) + .is_err() + { + self.send_message(&error_response( + &cv, + CommunicationType::ErrorInvalidData, + )) + .await; + return; + } + continue; + } + + if recipient.user_id != sender_id { + let forward = set_chat_secret_cv_for_recipient(&cv, recipient); + if !self.forward_chat_secret(&forward).await { + self.store_pending_chat_secret_forward(&forward).await; + } } } - } - let _ = self - .send_message(&error_response(cv, CommunicationType::Success)) - .await; - } - - async fn handle_get_chat_secret(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_get_chat_secret(cv)) - .await; - } - - async fn handle_chat_secret_forward(self: Arc, cv: &CommunicationValue) { - let sender_id = cv.get_sender().to_string(); - let recipient_user_id = data_string(cv, DataType::RecipientUserId).unwrap_or_default(); - if data_string(cv, DataType::SenderUserId).as_deref() != Some(sender_id.as_str()) - || recipient_user_id.is_empty() - || pending_chat_secret_forward_from_cv(cv).is_none() - { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) + self.send_message(&error_response(&cv, CommunicationType::Success)) .await; return; } - let forward = cv - .clone() - .with_receiver(recipient_user_id.parse::().unwrap_or(0)); - if self.forward_chat_secret(&forward).await { - let _ = self - .send_message(&error_response(cv, CommunicationType::Success)) - .await; - } else { - self.store_pending_chat_secret_forward(cv).await; - let _ = self - .send_message(&error_response(cv, CommunicationType::Success)) - .await; - } - } + if cv.is_type(CommunicationType::GetChatSecret) { + let Some(user_id) = data_string(&cv, DataType::UserId) else { + self.send_message(&error_response(&cv, CommunicationType::ErrorInvalidData)) + .await; + return; + }; + if user_id != cv.get_sender().to_string() { + self.send_message(&error_response(&cv, CommunicationType::ErrorNotFound)) + .await; + return; + } + let Some(chat_id) = data_string(&cv, DataType::ChatId) else { + self.send_message(&error_response(&cv, CommunicationType::ErrorInvalidData)) + .await; + return; + }; - async fn handle_app_identification(self: Arc, cv: &CommunicationValue) { - let sender_id = cv.get_sender(); - let app_identifier = cv - .get_data(DataType::AppIdentifier) - .as_str() - .unwrap_or("") - .to_string(); - let app_public_key = cv - .get_data(DataType::AppPublicKey) - .as_str() - .unwrap_or("") - .to_string(); - let user_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0) as i64; - - let mut trusted = false; - if let Some(user) = iota_storage::users::user_manager::get_user(user_id) { - if let Some(pub_k) = user.trusted_apps.get(&app_identifier) { - if pub_k == &app_public_key { - trusted = true; + match e2ee_storage::get_chat_secret(ChatSecretQuery { + user_id, + chat_id, + secret_id: data_string(&cv, DataType::SecretId), + }) { + Ok(Some(record)) => { + let response = CommunicationValue::new(CommunicationType::ChatSecretResponse) + .with_id(cv.get_id()) + .with_receiver(cv.get_sender()) + .add_typed_default(DataType::UserId, DataValue::Str(record.user_id)) + .add_typed_default(DataType::ChatId, DataValue::Str(record.chat_id)) + .add_typed_default(DataType::SecretId, DataValue::Str(record.secret_id)) + .add_typed_default( + DataType::VersionNumber, + DataValue::SignedNumber(record.version as i128), + ) + .add_typed_default( + DataType::EncryptedSecret, + DataValue::Bytes(record.encrypted_secret), + ) + .add_typed_default( + DataType::KemCiphertext, + DataValue::Bytes(record.kem_ciphertext), + ) + .add_typed_default( + DataType::WrappingScheme, + DataValue::Str(record.wrapping_scheme), + ) + .add_typed_default( + DataType::CreatedAt, + DataValue::SignedNumber(record.created_at as i128), + ) + .add_typed_default( + DataType::UpdatedAt, + DataValue::SignedNumber(record.updated_at as i128), + ); + self.send_message(&response).await; + } + Ok(None) => { + self.send_message(&error_response(&cv, CommunicationType::ErrorNotSet)) + .await + } + Err(_) => { + self.send_message(&error_response(&cv, CommunicationType::ErrorInvalidData)) + .await } } + return; } - if trusted { - let challenge = Uuid::new_v4().to_string(); - - self.app_challenges.insert(sender_id, challenge.clone()); - self.app_sessions - .insert(sender_id, (user_id, app_identifier.clone())); - - if let Some(app_pub_bundle) = - iota_util::crypto_helper::public_key_bundle_from_base64(&app_public_key) + if cv.is_type(CommunicationType::ChatSecretForward) { + let sender_id = cv.get_sender().to_string(); + let recipient_user_id = data_string(&cv, DataType::RecipientUserId).unwrap_or_default(); + if data_string(&cv, DataType::SenderUserId).as_deref() != Some(sender_id.as_str()) + || recipient_user_id.is_empty() + || pending_chat_secret_forward_from_cv(&cv).is_none() { - let kr_str = CONFIG.load().keyring.clone().unwrap_or_default(); + self.send_message(&error_response(&cv, CommunicationType::ErrorInvalidData)) + .await; + return; + } - if let Some(keyring) = keyring_from_base64(&kr_str) { - if let Ok(encrypted_challenge) = - crypto_util::encrypt_challenge(&challenge, &app_pub_bundle) - { - let bundle = keyring.public_key_bundle(); - let pub_k_b64 = crypto_helper::public_key_bundle_to_base64(&bundle); + let forward = cv + .clone() + .with_receiver(recipient_user_id.parse::().unwrap_or(0)); + if self.forward_chat_secret(&forward).await { + self.send_message(&error_response(&cv, CommunicationType::Success)) + .await; + } else { + self.store_pending_chat_secret_forward(&cv).await; + self.send_message(&error_response(&cv, CommunicationType::Success)) + .await; + } + return; + } - let res = CommunicationValue::new(CommunicationType::AppChallenge) - .with_id(cv.get_id()) - .with_receiver(sender_id) - .add_typed_default(DataType::PublicKey, DataValue::Str(pub_k_b64)) - .add_typed_default( - DataType::Challenge, - DataValue::Str(encrypted_challenge), - ); + if cv.is_type(CommunicationType::AppIdentification) { + let sender_id = cv.get_sender(); + let app_identifier = cv + .get_data(DataType::AppIdentifier) + .as_str() + .unwrap_or("") + .to_string(); + let app_public_key = cv + .get_data(DataType::AppPublicKey) + .as_str() + .unwrap_or("") + .to_string(); + let user_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0) as i64; - let _ = self.send_message(&res).await; + let mut trusted = false; + if let Some(user) = iota_storage::users::user_manager::get_user(user_id) { + if let Some(pub_k) = user.trusted_apps.get(&app_identifier) { + if pub_k == &app_public_key { + trusted = true; + } + } + } + + if trusted { + let challenge = Uuid::new_v4().to_string(); + + self.app_challenges + .write() + .await + .insert(sender_id, challenge.clone()); + self.app_sessions + .write() + .await + .insert(sender_id, (user_id, app_identifier.clone())); + + if let Some(app_pub_bundle) = + iota_util::crypto_helper::public_key_bundle_from_base64(&app_public_key) + { + let kr_str = CONFIG.load().keyring.clone().unwrap_or_default(); + + if let Some(keyring) = keyring_from_base64(&kr_str) { + if let Ok(encrypted_challenge) = + crypto_util::encrypt_challenge(&challenge, &app_pub_bundle) + { + let bundle = keyring.public_key_bundle(); + let pub_k_b64 = crypto_helper::public_key_bundle_to_base64(&bundle); + + let res = CommunicationValue::new(CommunicationType::AppChallenge) + .with_id(cv.get_id()) + .with_receiver(sender_id) + .add_typed_default(DataType::PublicKey, DataValue::Str(pub_k_b64)) + .add_typed_default( + DataType::Challenge, + DataValue::Str(encrypted_challenge), + ); + + self.send_message(&res).await; + return; + } + } + } + } + + let res = CommunicationValue::new(CommunicationType::ErrorInvalidChallenge) + .with_id(cv.get_id()) + .with_receiver(sender_id); + self.send_message(&res).await; + return; + } + + if cv.is_type(CommunicationType::AppChallengeResponse) { + let sender_id = cv.get_sender(); + let mut challenges = self.app_challenges.write().await; + if let Some(expected) = challenges.remove(&sender_id) { + if let DataValue::Str(response) = cv.get_data(DataType::Challenge) { + if expected == *response { + let res = + CommunicationValue::new(CommunicationType::AppIdentificationResponse) + .with_id(cv.get_id()) + .with_receiver(sender_id); + self.send_message(&res).await; return; } } } + let res = CommunicationValue::new(CommunicationType::ErrorInvalidChallenge) + .with_id(cv.get_id()) + .with_receiver(sender_id); + self.send_message(&res).await; + return; } - let res = CommunicationValue::new(CommunicationType::ErrorInvalidChallenge) - .with_id(cv.get_id()) - .with_receiver(sender_id); - let _ = self.send_message(&res).await; - } + if cv.is_type(CommunicationType::SaveAppData) { + let sender_id = cv.get_sender(); + let app_data = cv + .get_data(DataType::AppData) + .as_str() + .unwrap_or("") + .to_string(); - async fn handle_app_challenge_response(self: Arc, cv: &CommunicationValue) { - let sender_id = cv.get_sender(); - if let Some((_, expected_challenge)) = self.app_challenges.remove(&sender_id) { - if let DataValue::Str(response) = cv.get_data(DataType::Challenge) { - if expected_challenge == *response { - let res = CommunicationValue::new(CommunicationType::AppIdentificationResponse) - .with_id(cv.get_id()) - .with_receiver(sender_id); - let _ = self.send_message(&res).await; - return; + let sessions = self.app_sessions.read().await; + if let Some((user_id, app_identifier)) = sessions.get(&sender_id) { + iota_storage::users::user_manager::save_app_data( + *user_id, + app_identifier, + &app_data, + ); + } + + let res = CommunicationValue::new(CommunicationType::SaveAppData) + .with_id(cv.get_id()) + .with_receiver(sender_id); + self.send_message(&res).await; + return; + } + + if cv.is_type(CommunicationType::LoadAppData) { + let sender_id = cv.get_sender(); + let mut app_data = String::new(); + + let sessions = self.app_sessions.read().await; + if let Some((user_id, app_identifier)) = sessions.get(&sender_id) { + app_data = + iota_storage::users::user_manager::load_app_data(*user_id, app_identifier); + } + + let res = CommunicationValue::new(CommunicationType::LoadAppData) + .with_id(cv.get_id()) + .with_receiver(sender_id) + .add_typed_default(DataType::AppData, DataValue::Str(app_data)); + self.send_message(&res).await; + return; + } + + if cv.is_type(CommunicationType::CreateApp) { + let sender_id = cv.get_sender() as i64; + let app_identifier = cv + .get_data(DataType::AppIdentifier) + .as_str() + .unwrap_or("") + .to_string(); + let app_public_key = cv + .get_data(DataType::AppPublicKey) + .as_str() + .unwrap_or("") + .to_string(); + + if !app_identifier.is_empty() && !app_public_key.is_empty() { + if let Some(mut user) = iota_storage::users::user_manager::get_user(sender_id) { + if !user.trusted_apps.contains_key(&app_identifier) { + user.trusted_apps.insert(app_identifier, app_public_key); + iota_storage::users::user_manager::update_user(user); + } } } - } - let res = CommunicationValue::new(CommunicationType::ErrorInvalidChallenge) - .with_id(cv.get_id()) - .with_receiver(sender_id); - let _ = self.send_message(&res).await; - } - async fn handle_save_app_data(self: Arc, cv: &CommunicationValue) { - let sender_id = cv.get_sender(); - let app_data = cv - .get_data(DataType::AppData) - .as_str() - .unwrap_or("") - .to_string(); - - if let Some(session) = self.app_sessions.get(&sender_id) { - let (user_id, app_identifier) = session.value(); - iota_storage::users::user_manager::save_app_data(*user_id, app_identifier, &app_data); - } - - let res = CommunicationValue::new(CommunicationType::SaveAppData) - .with_id(cv.get_id()) - .with_receiver(sender_id); - let _ = self.send_message(&res).await; - } - - async fn handle_load_app_data(self: Arc, cv: &CommunicationValue) { - let sender_id = cv.get_sender(); - let mut app_data = String::new(); - - if let Some(session) = self.app_sessions.get(&sender_id) { - let (user_id, app_identifier) = session.value(); - app_data = iota_storage::users::user_manager::load_app_data(*user_id, app_identifier); - } - - let res = CommunicationValue::new(CommunicationType::LoadAppData) - .with_id(cv.get_id()) - .with_receiver(sender_id) - .add_typed_default(DataType::AppData, DataValue::Str(app_data)); - let _ = self.send_message(&res).await; - } - - async fn handle_create_app(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_create_app(cv)) - .await; - } - - async fn handle_delete_app(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_delete_app(cv)) - .await; - } - - async fn handle_client_connected(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_client_connected(cv)) - .await; - } - - async fn handle_message_state(self: Arc, cv: &CommunicationValue) { - message_handlers::handle_message_state(cv); - } - - fn mutation_live_message( - ty: CommunicationType, - request: &CommunicationValue, - mutation: &message_handlers::MessageMutation, - extra: Vec<(DataType, DataValue)>, - ) -> CommunicationValue { - let mut message = CommunicationValue::new(ty) - .with_id(request.get_id()) - .with_sender(mutation.sender_id as u64) - .with_receiver(mutation.partner_id as u64) - .add_typed_default( - DataType::ChatPartnerId, - DataValue::SignedNumber(mutation.sender_id as i128), - ) - .add_typed_default( - DataType::SendTime, - DataValue::SignedNumber(mutation.send_time as i128), - ); - for (data_type, value) in extra { - message = message.add_typed_default(data_type, value); - } - message - } - - async fn persist_and_deliver_remote_edit(&self, cv: &CommunicationValue) { - let sender_id = match i64::try_from(cv.get_sender()) { - Ok(sender_id) => sender_id, - Err(_) => return, - }; - let receiver_id = match i64::try_from(cv.get_receiver()) { - Ok(receiver_id) if receiver_id > 0 => receiver_id, - _ => return, - }; - let Some(send_time) = data_i64(cv, DataType::SendTime).filter(|time| *time > 0) else { - return; - }; - let Some(content) = cv.get_data(DataType::Content).as_str() else { - return; - }; - if chat_files::apply_remote_edit(receiver_id, sender_id, send_time, sender_id, content) - .is_ok() - { - let _ = self.send_message(cv).await; - } - } - - async fn persist_and_deliver_remote_reaction(&self, cv: &CommunicationValue, add: bool) { - let sender_id = match i64::try_from(cv.get_sender()) { - Ok(sender_id) => sender_id, - Err(_) => return, - }; - let receiver_id = match i64::try_from(cv.get_receiver()) { - Ok(receiver_id) if receiver_id > 0 => receiver_id, - _ => return, - }; - let Some(send_time) = data_i64(cv, DataType::SendTime).filter(|time| *time > 0) else { - return; - }; - let Some(reaction) = cv.get_data(DataType::Reaction).as_str() else { - return; - }; - if reaction.is_empty() || reaction.len() > 64 { + let res = CommunicationValue::new(CommunicationType::CreateApp) + .with_id(cv.get_id()) + .with_receiver(sender_id as u64); + self.send_message(&res).await; return; } - let result = if add { - chat_files::add_reaction(receiver_id, sender_id, send_time, sender_id, reaction) - } else { - chat_files::remove_reaction(receiver_id, sender_id, send_time, sender_id, reaction) - }; - if result.is_ok() { - let _ = self.send_message(cv).await; - } - } - async fn persist_and_deliver_remote_delete(&self, cv: &CommunicationValue) { - let sender_id = match i64::try_from(cv.get_sender()) { - Ok(sender_id) => sender_id, - Err(_) => return, - }; - let receiver_id = match i64::try_from(cv.get_receiver()) { - Ok(receiver_id) if receiver_id > 0 => receiver_id, - _ => return, - }; - let Some(send_time) = data_i64(cv, DataType::SendTime).filter(|time| *time > 0) else { - return; - }; - if chat_files::apply_remote_delete(receiver_id, sender_id, send_time, sender_id).is_ok() { - let _ = self.send_message(cv).await; - } - } + if cv.is_type(CommunicationType::DeleteApp) { + let sender_id = cv.get_sender() as i64; + let app_identifier = cv + .get_data(DataType::AppIdentifier) + .as_str() + .unwrap_or("") + .to_string(); - async fn handle_message_edit(self: Arc, cv: &CommunicationValue) { - let response = message_handlers::handle_message_edit(cv); - if !response.is_type(CommunicationType::Success) { - let _ = self.send_message(&response).await; - return; - } - let Ok(mutation) = message_handlers::message_mutation(cv) else { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) - .await; - return; - }; - let Some(content) = cv.get_data(DataType::Content).as_str() else { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) - .await; - return; - }; - let live = Self::mutation_live_message( - CommunicationType::MessageEditLive, - cv, - &mutation, - vec![(DataType::Content, DataValue::Str(content.to_string()))], - ); - if iota_storage::users::user_manager::get_user(mutation.partner_id).is_some() - && chat_files::apply_remote_edit( - mutation.partner_id, - mutation.sender_id, - mutation.send_time, - mutation.sender_id, - content, - ) - .is_err() - { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorNotFound)) - .await; - return; - } - let _ = self.send_message(&response).await; - let _ = self.send_message(&live).await; - } - - async fn handle_message_edit_live(self: Arc, cv: &CommunicationValue) { - self.persist_and_deliver_remote_edit(cv).await; - } - - async fn handle_message_reaction_add(self: Arc, cv: &CommunicationValue) { - self.handle_message_reaction(cv, true).await; - } - - async fn handle_message_reaction_remove(self: Arc, cv: &CommunicationValue) { - self.handle_message_reaction(cv, false).await; - } - - async fn handle_message_reaction(self: Arc, cv: &CommunicationValue, add: bool) { - let response = message_handlers::handle_message_reaction(cv, add); - if !response.is_type(CommunicationType::Success) { - let _ = self.send_message(&response).await; - return; - } - let Ok(mutation) = message_handlers::message_mutation(cv) else { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) - .await; - return; - }; - let Some(reaction) = cv.get_data(DataType::Reaction).as_str() else { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) - .await; - return; - }; - let live = Self::mutation_live_message( - CommunicationType::MessageReactionLive, - cv, - &mutation, - vec![ - (DataType::Reaction, DataValue::Str(reaction.to_string())), - ( - DataType::SenderId, - DataValue::SignedNumber(mutation.sender_id as i128), - ), - (DataType::Accepted, DataValue::Bool(add)), - ], - ); - if iota_storage::users::user_manager::get_user(mutation.partner_id).is_some() { - let result = if add { - chat_files::add_reaction( - mutation.partner_id, - mutation.sender_id, - mutation.send_time, - mutation.sender_id, - reaction, - ) - } else { - chat_files::remove_reaction( - mutation.partner_id, - mutation.sender_id, - mutation.send_time, - mutation.sender_id, - reaction, - ) - }; - if result.is_err() { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorNotFound)) - .await; - return; + if !app_identifier.is_empty() { + if let Some(mut user) = iota_storage::users::user_manager::get_user(sender_id) { + if user.trusted_apps.contains_key(&app_identifier) { + user.trusted_apps.remove(&app_identifier); + iota_storage::users::user_manager::update_user(user); + } + } } - } - let _ = self.send_message(&response).await; - let _ = self.send_message(&live).await; - } - async fn handle_message_reaction_live(self: Arc, cv: &CommunicationValue) { - let add = cv.get_data(DataType::Accepted).as_bool().unwrap_or(true); - self.persist_and_deliver_remote_reaction(cv, add).await; - } - - async fn handle_message_delete_live(self: Arc, cv: &CommunicationValue) { - let sender_id = match i64::try_from(cv.get_sender()) { - Ok(sender_id) => sender_id, - Err(_) => return, - }; - if iota_storage::users::user_manager::get_user(sender_id).is_none() { - self.persist_and_deliver_remote_delete(cv).await; + let res = CommunicationValue::new(CommunicationType::DeleteApp) + .with_id(cv.get_id()) + .with_receiver(sender_id as u64); + self.send_message(&res).await; return; } - let response = message_handlers::handle_message_delete(cv); - if !response.is_type(CommunicationType::Success) { - let _ = self.send_message(&response).await; + if cv.is_type(CommunicationType::ClientConnected) { + let user_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0) as i64; + let _session_id = cv.get_data(DataType::SessionId).as_number().unwrap_or(0) as i64; + + let contacts = chats_util::get_users(user_id); + let mut contacts_array = Vec::new(); + + for (i, contact) in contacts.iter().enumerate() { + let mut contact_container = Vec::new(); + contact_container.push(( + DataType::UserId, + DataValue::SignedNumber(contact.user_id as i128), + )); + contact_container.push(( + DataType::LastMessageAt, + DataValue::SignedNumber(contact.last_message_at.unwrap_or(0) as i128), + )); + + if let Some(ref name) = contact.user_name { + contact_container.push((DataType::Username, DataValue::Str(name.clone()))); + } + + let amount = if i < 10 { 20 } else { 1 }; + let messages = chat_files::get_messages(user_id, contact.user_id, 0, amount); + + let mut msg_array = Vec::new(); + for m in messages.members() { + let message_time = m["message_time"].as_i64().unwrap_or(0); + let content = m["content"].as_str().unwrap_or("").to_string(); + let sent_by_self = m["sent_by_self"].as_bool().unwrap_or(false); + let height = m["height"].as_i64().unwrap_or(0); + let message_state = m["message_state"].as_str().unwrap_or("").to_string(); + + let mut msg_container = Vec::new(); + msg_container.push(( + DataType::SendTime, + DataValue::SignedNumber(message_time as i128), + )); + msg_container.push((DataType::Content, DataValue::Str(content.clone()))); + msg_container.push((DataType::MessageState, DataValue::Str(message_state))); + msg_container.push((DataType::Height, DataValue::SignedNumber(height as i128))); + msg_container.push(( + DataType::SenderId, + DataValue::UnsignedNumber(if sent_by_self { + user_id as u128 + } else { + contact.user_id as u128 + }), + )); + msg_array.push(typed_container(msg_container)); + + if msg_array.len() == 1 { + let sender_id = if sent_by_self { + user_id + } else { + contact.user_id + }; + let mut last_msg = Vec::new(); + last_msg.push((DataType::Content, DataValue::Str(content))); + last_msg.push(( + DataType::SenderId, + DataValue::SignedNumber(sender_id as i128), + )); + contact_container.push((DataType::LastMessage, typed_container(last_msg))); + } + } + contact_container.push((DataType::Messages, DataValue::Array(msg_array))); + contacts_array.push(typed_container(contact_container)); + } + + let resp = CommunicationValue::new(CommunicationType::ClientConnected) + .with_id(cv.get_id()) + .add_typed_default(DataType::Contacts, DataValue::Array(contacts_array)); + self.send_message(&resp).await; return; } - let Ok(mutation) = message_handlers::message_mutation(cv) else { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorInvalidData)) - .await; - return; - }; - let live = Self::mutation_live_message( - CommunicationType::MessageDeleteLive, - cv, - &mutation, - Vec::new(), - ); - if iota_storage::users::user_manager::get_user(mutation.partner_id).is_some() - && chat_files::apply_remote_delete( - mutation.partner_id, - mutation.sender_id, - mutation.send_time, - mutation.sender_id, - ) - .is_err() - { - let _ = self - .send_message(&error_response(cv, CommunicationType::ErrorNotFound)) - .await; - return; - } - let _ = self.send_message(&response).await; - let _ = self.send_message(&live).await; - } - async fn handle_message_send(self: Arc, cv: &CommunicationValue) { - let sender_id: u64 = cv.get_sender(); + // ************************************************ // + // Direct messages // + // ************************************************ // - let receiver_id: i64 = if let Some(n) = cv.get_data(DataType::ReceiverId).as_number() { - n as i64 - } else if let Some(s) = cv.get_data(DataType::ReceiverId).as_str() { - s.parse::().unwrap_or(0) - } else { - 0 - }; + if cv.is_type(CommunicationType::MessageState) { + let sender_id = &cv.get_sender(); + let receiver_id = match cv.get_data(DataType::ChatPartnerId).as_number() { + Some(id) => id, + _ => return, + }; - let timestamp_i64 = if let Some(n) = cv.get_data(DataType::SendTime).as_number() { - n as i64 - } else if let Some(s) = cv.get_data(DataType::SendTime).as_str() { - s.parse::().unwrap_or_else(|_| now_millis_i64()) - } else { - now_millis_i64() - }; - let timestamp_u128 = timestamp_i64 as u128; + // Parse send_time robustly: accept numeric or string, fallback to current time + let send_time_val = cv.get_data(DataType::SendTime); + let now_i64 = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64; + let timestamp_i64 = if let Some(n) = send_time_val.as_number() { + n as i64 + } else if let Some(s) = send_time_val.as_str() { + s.parse::().unwrap_or(now_i64) + } else { + now_i64 + }; - let content = cv - .get_data(DataType::Content) - .as_str() - .unwrap_or("") - .to_string(); - - let height = cv.get_data(DataType::Height).as_number().unwrap_or(0) as i64; - let reply_to = cv.get_data(DataType::ReplyId).as_number().map(|n| n as i64); - - let is_local = iota_storage::users::user_manager::get_user(receiver_id).is_some(); - - if is_local { - chat_files::add_message( - timestamp_u128, - false, + let _ = chat_files::change_message_state( + timestamp_i64, receiver_id as i64, - sender_id as i64, - &content, - height, - reply_to, + *sender_id as i64, + MessageState::from_str(cv.get_data(DataType::MessageState).as_str().unwrap_or("")), ); } - chat_files::add_message( - timestamp_u128, - true, - sender_id as i64, - receiver_id as i64, - &content, - height, - reply_to, - ); + // Incoming storsed message: store for the recipient, attempt local delivery, notify sender. + if cv.is_type(CommunicationType::MessageSend) { + let sender_id: u64 = cv.get_sender(); - let conf_msg = CommunicationValue::new(CommunicationType::MessageSend) - .with_id(cv.get_id()) - .with_receiver(sender_id as u64); - let _ = self.send_message(&conf_msg).await; + // parse receiver_id (the storage owner for this incoming message) + let receiver_id: i64 = if let Some(n) = cv.get_data(DataType::ReceiverId).as_number() { + n as i64 + } else if let Some(s) = cv.get_data(DataType::ReceiverId).as_str() { + s.parse::().unwrap_or(0) + } else { + 0 + }; - if !is_local { - self.forward_to_remote_iota( - cv, - sender_id as i64, - receiver_id, - timestamp_i64, - &content, - height, - reply_to, - ) - .await; - } else { - match self - .forward_message_live( - cv.get_id(), - receiver_id as u64, + // parse send_time robustly (number or string), fallback to now + let send_time_val = cv.get_data(DataType::SendTime); + let now_i64 = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64; + let timestamp_i64 = if let Some(n) = send_time_val.as_number() { + n as i64 + } else if let Some(s) = send_time_val.as_str() { + s.parse::().unwrap_or(now_i64) + } else { + now_i64 + }; + let timestamp_u128 = timestamp_i64 as u128; + + // content may be missing; default to empty string + let content = cv + .get_data(DataType::Content) + .as_str() + .unwrap_or("") + .to_string(); + + let height = cv.get_data(DataType::Height).as_number().unwrap_or(0) as i64; + let reply_to = cv.get_data(DataType::ReplyId).as_number().map(|n| n as i64); + + let is_local = iota_storage::users::user_manager::get_user(receiver_id).is_some(); + + if is_local { + // persist message for the receiver (storage_owner = receiver_id) + chat_files::add_message( + timestamp_u128, + false, + receiver_id as i64, sender_id as i64, - timestamp_i64, &content, height, reply_to, - ) - .await - { - Some(ms) => { - let _ = chat_files::change_message_state( - timestamp_i64, - receiver_id, - sender_id as i64, - ms.clone(), + ); + } + + // persist message for the sender (storage_owner = sender_id) + chat_files::add_message( + timestamp_u128, + true, + sender_id as i64, + receiver_id as i64, + &content, + height, + reply_to, + ); + + // send confirmation back to sender + let conf_msg = CommunicationValue::new(CommunicationType::MessageSend) + .with_id(cv.get_id()) + .with_receiver(sender_id as u64); + self.send_message(&conf_msg).await; + + if !is_local { + let mut fw_msg = CommunicationValue::new(CommunicationType::MessageOtherIota) + .with_id(cv.get_id()) + .with_receiver(receiver_id as u64) + .with_sender(sender_id as u64) + .add_typed_default(DataType::Height, DataValue::SignedNumber(height as i128)) + .add_typed_default(DataType::Content, DataValue::Str(content)) + .add_typed_default( + DataType::SendTime, + DataValue::SignedNumber(timestamp_i64 as i128), ); - let _ = chat_files::change_message_state( - timestamp_i64, - sender_id as i64, - receiver_id, - ms.clone(), + if let Some(rt) = reply_to { + fw_msg = fw_msg.add_typed_default( + DataType::ReplyId, + DataValue::UnsignedNumber(rt as u64 as u128), ); - if is_read_receipts_enabled().await { - let _ = self - .send_message( - &CommunicationValue::new(CommunicationType::MessageState) - .with_id(cv.get_id()) - .with_receiver(sender_id as u64) - .with_sender(receiver_id as u64) - .add_typed_default( - DataType::ChatPartnerId, - DataValue::SignedNumber(receiver_id as i128), - ) - .add_typed_default( - DataType::SendTime, - DataValue::SignedNumber(timestamp_i64 as i128), - ) - .add_typed_default( - DataType::MessageState, - DataValue::Str(ms.as_str().to_string()), - ), - ) - .await; - } } - None => { - let _ = chat_files::change_message_state( - timestamp_i64, - receiver_id, - sender_id as i64, - MessageState::Sent, - ); + + let other_iota_resp = self + .clone() + .await_response(&fw_msg, Some(Duration::from_secs(10))) + .await; + + if let Ok(resp) = other_iota_resp { + let ms_raw = resp + .get_data(DataType::MessageState) + .as_string() + .unwrap_or_else(|| "".to_string()); + let ms = MessageState::from_str(&ms_raw).upgrade(MessageState::Received); + let _ = chat_files::change_message_state( timestamp_i64, sender_id as i64, - receiver_id, + receiver_id as i64, + ms.clone(), + ); + + self.send_message( + &CommunicationValue::new(CommunicationType::MessageState) + .with_id(cv.get_id()) + .with_receiver(sender_id as u64) + .with_sender(receiver_id as u64) + .add_typed_default( + DataType::ChatPartnerId, + DataValue::SignedNumber(receiver_id as i128), + ) + .add_typed_default( + DataType::SendTime, + DataValue::SignedNumber(timestamp_i64 as i128), + ) + .add_typed_default( + DataType::MessageState, + DataValue::Str(ms.as_str().to_string()), + ), + ) + .await; + } else { + let _ = chat_files::change_message_state( + timestamp_i64, + sender_id as i64, + receiver_id as i64, MessageState::Sent, ); - let push_msg = CommunicationValue::new(CommunicationType::PushNotification) - .with_receiver(receiver_id as u64) - .add_typed_default( - DataType::SenderId, - DataValue::SignedNumber(sender_id as i128), - ); - let _ = self.send_message(&push_msg).await; - let _ = self - .send_message( + + self.send_message( + &CommunicationValue::new(CommunicationType::MessageState) + .with_id(cv.get_id()) + .with_receiver(sender_id as u64) + .with_sender(receiver_id as u64) + .add_typed_default( + DataType::ChatPartnerId, + DataValue::SignedNumber(receiver_id as i128), + ) + .add_typed_default( + DataType::SendTime, + DataValue::SignedNumber(timestamp_i64 as i128), + ) + .add_typed_default( + DataType::MessageState, + DataValue::Str(MessageState::Sent.as_str().to_string()), + ), + ) + .await; + } + return; + } else { + // Build a live-delivery message for the local client (recipient) + let user_forward = CommunicationValue::new(CommunicationType::MessageLive) + .with_id(cv.get_id()) + .with_receiver(receiver_id as u64) + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(sender_id as i128), + ) + .add_typed_default(DataType::Message, { + let mut msg_fields = vec![ + (DataType::Content, DataValue::Str(content.clone())), + ( + DataType::SendTime, + DataValue::SignedNumber(timestamp_i64 as i128), + ), + (DataType::Height, DataValue::SignedNumber(height as i128)), + ]; + if let Some(rt) = reply_to { + msg_fields.push(( + DataType::ReplyId, + DataValue::UnsignedNumber(rt as u64 as u128), + )); + } + typed_container(msg_fields) + }); + + // Attempt delivery and await a response from the local client + let user_resp = self + .clone() + .await_response(&user_forward, Some(Duration::from_secs(3))) + .await; + + if let Ok(user_resp) = user_resp { + let ms_raw = user_resp + .get_data(DataType::MessageState) + .as_string() + .unwrap_or_else(|| "".to_string()); + let ms = MessageState::from_str(&ms_raw).upgrade(MessageState::Received); + + // update stored message state for receiver + let _ = chat_files::change_message_state( + timestamp_i64, + receiver_id as i64, + sender_id as i64, + ms.clone(), + ); + + // update stored message state for sender + let _ = chat_files::change_message_state( + timestamp_i64, + sender_id as i64, + receiver_id as i64, + ms.clone(), + ); + + // notify original sender about the delivered/read state (if read receipts are enabled) + if is_read_receipts_enabled().await { + self.send_message( &CommunicationValue::new(CommunicationType::MessageState) .with_id(cv.get_id()) .with_receiver(sender_id as u64) .with_sender(receiver_id as u64) - .add_typed_default( - DataType::SendTime, - DataValue::SignedNumber(timestamp_i64 as i128), - ) .add_typed_default( DataType::ChatPartnerId, DataValue::SignedNumber(receiver_id as i128), ) - .add_typed_default( - DataType::MessageState, - DataValue::Str(MessageState::Sent.as_str().to_string()), - ), - ) - .await; - } - } - } - } - - async fn handle_message_other_iota(self: Arc, cv: &CommunicationValue) { - let sender_id = &cv.get_sender(); - let receiver_id = &cv.get_receiver(); - - let timestamp = if let Some(n) = cv.get_data(DataType::SendTime).as_number() { - n as i64 - } else if let Some(s) = cv.get_data(DataType::SendTime).as_str() { - s.parse::().unwrap_or_else(|_| now_millis_i64()) - } else { - now_millis_i64() - }; - - let content = cv - .get_data(DataType::Content) - .as_str() - .unwrap_or("") - .to_string(); - - let height = cv.get_data(DataType::Height).as_number().unwrap_or(0) as i64; - let reply_to = cv.get_data(DataType::ReplyId).as_number().map(|n| n as i64); - - chat_files::add_message( - timestamp as u128, - false, - *receiver_id as i64, - *sender_id as i64, - &content, - height, - reply_to, - ); - - match self - .forward_message_live( - cv.get_id(), - *receiver_id, - *sender_id as i64, - timestamp, - &content, - height, - reply_to, - ) - .await - { - Some(ms) => { - let _ = change_message_state( - timestamp, - *receiver_id as i64, - *sender_id as i64, - ms.clone(), - ); - - if is_read_receipts_enabled().await { - let _ = self - .send_message( - &CommunicationValue::new(CommunicationType::MessageState) - .with_id(cv.get_id()) - .with_receiver(*sender_id) - .with_sender(*receiver_id) .add_typed_default( DataType::SendTime, - DataValue::SignedNumber(timestamp as i128), - ) - .add_typed_default( - DataType::ChatPartnerId, - DataValue::SignedNumber(*sender_id as i128), + DataValue::SignedNumber(timestamp_i64 as i128), ) .add_typed_default( DataType::MessageState, @@ -1632,26 +1465,143 @@ impl OmikronConnection { ), ) .await; + } + } else { + // Delivery failed or timed out; mark as Sent + let _ = chat_files::change_message_state( + timestamp_i64, + receiver_id as i64, + sender_id as i64, + MessageState::Sent, + ); + + let _ = chat_files::change_message_state( + timestamp_i64, + sender_id as i64, + receiver_id as i64, + MessageState::Sent, + ); + + // Send push notification to Omega since user is offline + let push_msg = CommunicationValue::new(CommunicationType::PushNotification) + .with_receiver(receiver_id as u64) + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(sender_id as i128), + ); + self.send_message(&push_msg).await; + + // notify sender + self.send_message( + &CommunicationValue::new(CommunicationType::MessageState) + .with_id(cv.get_id()) + .with_receiver(sender_id as u64) + .with_sender(receiver_id as u64) + .add_typed_default( + DataType::SendTime, + DataValue::SignedNumber(timestamp_i64 as i128), + ) + .add_typed_default( + DataType::ChatPartnerId, + DataValue::SignedNumber(receiver_id as i128), + ) + .add_typed_default( + DataType::MessageState, + DataValue::Str(MessageState::Sent.as_str().to_string()), + ), + ) + .await; } + return; } - None => { - let _ = chat_files::change_message_state( + } + + if cv.is_type(CommunicationType::MessageOtherIota) { + let sender_id = &cv.get_sender(); + let receiver_id = &cv.get_receiver(); + + // parse send_time safely (number or string), fallback to now + let send_time_val = cv.get_data(DataType::SendTime); + let now_i64 = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64; + let timestamp = if let Some(n) = send_time_val.as_number() { + n as i64 + } else if let Some(s) = send_time_val.as_str() { + s.parse::().unwrap_or(now_i64) + } else { + now_i64 + }; + + // content may be missing or non-string; default to empty string + let content = cv + .get_data(DataType::Content) + .as_str() + .unwrap_or("") + .to_string(); + + let height = cv.get_data(DataType::Height).as_number().unwrap_or(0) as i64; + let reply_to = cv.get_data(DataType::ReplyId).as_number().map(|n| n as i64); + + chat_files::add_message( + timestamp as u128, + false, + *receiver_id as i64, + *sender_id as i64, + &content, + height, + reply_to, + ); + + // Build user_forward using the parsed numeric timestamp and safe content string + let user_forward = CommunicationValue::new(CommunicationType::MessageLive) + .with_id(cv.get_id()) + .with_receiver(*receiver_id) + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(*sender_id as i128), + ) + .add_typed_default(DataType::Message, { + let mut msg_fields = vec![ + (DataType::Content, DataValue::Str(content.clone())), + ( + DataType::SendTime, + DataValue::SignedNumber(timestamp as i128), + ), + (DataType::Height, DataValue::SignedNumber(height as i128)), + ]; + if let Some(rt) = reply_to { + msg_fields.push(( + DataType::ReplyId, + DataValue::UnsignedNumber(rt as u64 as u128), + )); + } + typed_container(msg_fields) + }); + + let user_resp = self + .clone() + .await_response(&user_forward, Some(Duration::from_secs(3))) + .await; + + if let Ok(user_resp) = user_resp { + let ms_raw = user_resp + .get_data(DataType::MessageState) + .as_string() + .unwrap_or_else(|| "".to_string()); + let ms = MessageState::from_str(&ms_raw).upgrade(MessageState::Received); + + let _ = change_message_state( timestamp, *receiver_id as i64, *sender_id as i64, - MessageState::Sent, + ms.clone(), ); - let push_msg = CommunicationValue::new(CommunicationType::PushNotification) - .with_receiver(*receiver_id) - .add_typed_default( - DataType::SenderId, - DataValue::SignedNumber(*sender_id as i128), - ); - let _ = self.send_message(&push_msg).await; - - let _ = self - .send_message( + // notify original sender about the delivered/read state (if read receipts are enabled) + if is_read_receipts_enabled().await { + self.send_message( &CommunicationValue::new(CommunicationType::MessageState) .with_id(cv.get_id()) .with_receiver(*sender_id) @@ -1662,89 +1612,547 @@ impl OmikronConnection { ) .add_typed_default( DataType::ChatPartnerId, - DataValue::SignedNumber(*receiver_id as i128), + DataValue::SignedNumber(*sender_id as i128), ) .add_typed_default( DataType::MessageState, - DataValue::Str(MessageState::Sent.as_str().to_string()), + DataValue::Str(ms.as_str().to_string()), ), ) .await; + } + } else { + // Delivery timed out/failed — update stored state and notify sender with numeric timestamp + let _ = chat_files::change_message_state( + timestamp, + *receiver_id as i64, + *sender_id as i64, + MessageState::Sent, + ); + + // Send push notification to Omega since user is offline + let push_msg = CommunicationValue::new(CommunicationType::PushNotification) + .with_receiver(*receiver_id) + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(*sender_id as i128), + ); + self.send_message(&push_msg).await; + + self.send_message( + &CommunicationValue::new(CommunicationType::MessageState) + .with_id(cv.get_id()) + .with_receiver(*sender_id) + .with_sender(*receiver_id) + .add_typed_default( + DataType::SendTime, + DataValue::SignedNumber(timestamp as i128), + ) + .add_typed_default( + DataType::ChatPartnerId, + DataValue::SignedNumber(*receiver_id as i128), + ) + .add_typed_default( + DataType::MessageState, + DataValue::Str(MessageState::Sent.as_str().to_string()), + ), + ) + .await; } + return; } - } - async fn handle_messages_get(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_messages_get(cv)) - .await; - } + if cv.is_type(CommunicationType::MessagesGet) { + let my_id = cv.get_sender(); + let partner_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0); + let offset = cv.get_data(DataType::Offset).as_number().unwrap_or(0); + let amount = cv.get_data(DataType::Amount).as_number().unwrap_or(0); + let messages = chat_files::get_messages( + my_id as i64, + partner_id as i64, + offset as i64, + amount as i64, + ); + let mut msg_array: Vec = Vec::new(); + for m in messages.members() { + let message_time: i64 = m["message_time"].as_i64().unwrap_or(0); + let content: String = m["content"].as_str().unwrap_or("").to_string(); + let sent_by_self: bool = m["sent_by_self"].as_bool().unwrap_or(false); + let height: i64 = m["height"].as_i64().unwrap_or(0); + let sender_id: i64 = if sent_by_self { + my_id as i64 + } else { + if let Some(n) = cv.get_data(DataType::ChatPartnerId).as_number() { + n as i64 + } else if let Some(s) = cv.get_data(DataType::ChatPartnerId).as_str() { + s.parse::().unwrap_or(partner_id as i64) + } else { + partner_id as i64 + } + }; + let message_state: String = m["message_state"].as_str().unwrap_or("").to_string(); - async fn handle_get_chats(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_get_chats(cv)) - .await; - } + let mut container = Vec::new(); + container.push(( + DataType::SendTime, + DataValue::SignedNumber(message_time as i128), + )); + container.push((DataType::Content, DataValue::Str(content))); + container.push(( + DataType::SenderId, + DataValue::SignedNumber(sender_id as i128), + )); + container.push((DataType::MessageState, DataValue::Str(message_state))); + container.push((DataType::Height, DataValue::SignedNumber(height as i128))); + container.push(( + DataType::SenderId, + DataValue::UnsignedNumber(if sent_by_self { + my_id as u128 + } else { + partner_id as u128 + }), + )); + if let Some(rt) = m["reply_to"].as_i64() { + container.push(( + DataType::ReplyId, + DataValue::UnsignedNumber(rt as u64 as u128), + )); + } + msg_array.push(typed_container(container)); + } - async fn handle_add_conversation(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_add_conversation(cv)) - .await; - } + let resp = CommunicationValue::new(CommunicationType::MessagesGet) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default(DataType::Messages, DataValue::Array(msg_array)); - async fn handle_add_community(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_add_community(cv)) - .await; - } + self.send_message(&resp).await; + return; + } - async fn handle_get_communities(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_get_communities(cv)) - .await; - } + if cv.is_type(CommunicationType::GetChats) { + let user_id = cv.get_sender(); + let users = chats_util::get_users(user_id as i64); + let mut user_array = Vec::new(); + for user in users { + let mut container = Vec::new(); + container.push(( + DataType::UserId, + DataValue::SignedNumber(user.user_id as i128), + )); + if let Some(name) = user.user_name { + container.push((DataType::Username, DataValue::Str(name))); + } + if let Some(ts) = user.last_message_at { + container.push((DataType::LastMessageAt, DataValue::SignedNumber(ts as i128))); + } + user_array.push(typed_container(container)); + } + let resp = CommunicationValue::new(CommunicationType::GetChats) + .with_id(cv.get_id()) + .with_receiver(user_id) + .add_typed_default(DataType::UserIds, DataValue::Array(user_array)); + self.send_message(&resp).await; + return; + } - async fn handle_remove_community(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_remove_community(cv)) - .await; - } + if cv.is_type(CommunicationType::AddConversation) { + let user_id = cv.get_sender(); + let other_id = match cv.get_data(DataType::ChatPartnerId).as_number() { + Some(n) => n as i64, + None => cv + .get_data(DataType::ChatPartnerId) + .as_str() + .unwrap_or("0") + .parse() + .unwrap_or(0), + }; + let mut contact = get_user(user_id as i64, other_id).unwrap_or(Contact::new(other_id)); - async fn handle_global_settings_save(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_global_settings_save(cv)) - .await; - } + if let Some(name) = cv.get_data(DataType::ChatPartnerName).as_str() { + contact.user_name = Some(name.to_string()); + } - async fn handle_global_settings_load(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_global_settings_load(cv)) - .await; - } + contact.set_last_message_at( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64, + ); + mod_user(user_id as i64, &contact); + let resp = CommunicationValue::new(CommunicationType::AddConversation) + .with_id(cv.get_id()) + .with_receiver(user_id); + self.send_message(&resp).await; + return; + } - async fn handle_settings_save(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_settings_save(cv, 0)) - .await; - } + if cv.is_type(CommunicationType::AddCommunity) { + CommunitiesUtil::add_community( + cv.get_sender() as i64, + cv.get_data(DataType::CommunityAddress) + .as_str() + .unwrap() + .to_string(), + cv.get_data(DataType::CommunityTitle) + .as_str() + .unwrap() + .to_string(), + cv.get_data(DataType::Position) + .as_str() + .unwrap() + .to_string(), + ); + let resp = CommunicationValue::new(CommunicationType::AddCommunity) + .with_id(cv.get_id()) + .with_receiver(cv.get_sender()); + self.send_message(&resp).await; + return; + } - async fn handle_settings_load(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_settings_load(cv, 0)) - .await; - } + if cv.is_type(CommunicationType::GetCommunities) { + let mut comm_array = Vec::new(); + for c in CommunitiesUtil::get_communities(cv.get_sender() as i64) { + let mut container: Vec<(DataType, DataValue)> = Vec::new(); + if let Some(address) = c["address"].as_str() { + container.push(( + DataType::CommunityAddress, + DataValue::Str(address.to_string()), + )); + } + if let Some(title) = c["title"].as_str() { + container.push((DataType::CommunityTitle, DataValue::Str(title.to_string()))); + } + if let Some(position) = c["position"].as_str() { + container.push((DataType::Position, DataValue::Str(position.to_string()))); + } + comm_array.push(typed_container(container)); + } - async fn handle_settings_list(self: Arc, cv: &CommunicationValue) { - let _ = self - .send_message(&message_handlers::handle_settings_list(cv, 0)) - .await; + let resp = CommunicationValue::new(CommunicationType::GetCommunities) + .with_id(cv.get_id()) + .with_receiver(cv.get_sender()) + .add_typed_default(DataType::Communities, DataValue::Array(comm_array)); + self.send_message(&resp).await; + return; + } + + if cv.is_type(CommunicationType::RemoveCommunity) { + CommunitiesUtil::remove_community( + cv.get_sender() as i64, + cv.get_data(DataType::CommunityAddress) + .as_str() + .unwrap() + .to_string(), + ); + let resp = CommunicationValue::new(CommunicationType::RemoveCommunity) + .with_id(cv.get_id()) + .with_receiver(cv.get_sender()); + self.send_message(&resp).await; + return; + } + + if cv.is_type(CommunicationType::GlobalSettingsSave) { + let my_id = cv.get_sender(); + let Some(settings_value) = cv.get_data(DataType::Payload).as_str() else { + let response = CommunicationValue::new(CommunicationType::ErrorInvalidData) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::Message, + DataValue::Str("Missing settings payload".to_string()), + ); + self.send_message(&response).await; + return; + }; + + save_file( + &format!("users/{}", my_id), + "global.settings", + settings_value, + ); + + let mut response = CommunicationValue::new(CommunicationType::GlobalSettingsSave) + .with_receiver(my_id) + .with_id(cv.get_id()); + + if let Some(session_id) = cv.get_data(DataType::SessionId).as_number() { + response = response.add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + } + + self.send_message(&response).await; + return; + } + + if cv.is_type(CommunicationType::GlobalSettingsLoad) { + let my_id = cv.get_sender(); + let path = format!("users/{}", my_id); + let name = "global.settings"; + + if !has_file(&path, name) { + let mut response = CommunicationValue::new(CommunicationType::ErrorNotFound) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default(DataType::Path, DataValue::Str(name.to_string())); + + if let Some(session_id) = cv.get_data(DataType::SessionId).as_number() { + response = response.add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + } + + self.send_message(&response).await; + return; + } + + let settings_value_str = load_file(&path, name); + let mut response = CommunicationValue::new(CommunicationType::GlobalSettingsLoad) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default(DataType::Payload, DataValue::Str(settings_value_str)); + + if let Some(session_id) = cv.get_data(DataType::SessionId).as_number() { + response = response.add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + } + + self.send_message(&response).await; + return; + } + + if cv.is_type(CommunicationType::SettingsSave) { + let my_id = cv.get_sender(); + let Some(session_id) = cv.get_data(DataType::SessionId).as_number() else { + let response = CommunicationValue::new(CommunicationType::ErrorInvalidData) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::Message, + DataValue::Str("Missing session_id".to_string()), + ); + self.send_message(&response).await; + return; + }; + let Some(settings_name) = cv.get_data(DataType::SettingsName).as_str() else { + let response = CommunicationValue::new(CommunicationType::ErrorInvalidData) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::Message, + DataValue::Str("Missing settings_name".to_string()), + ) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + self.send_message(&response).await; + return; + }; + let Some(settings_value) = cv.get_data(DataType::Payload).as_str() else { + let response = CommunicationValue::new(CommunicationType::ErrorInvalidData) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::Message, + DataValue::Str("Missing settings payload".to_string()), + ) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + self.send_message(&response).await; + return; + }; + + if !settings_name + .chars() + .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.') + || settings_name.contains("..") + { + let response = CommunicationValue::new(CommunicationType::ErrorInvalidData) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::Message, + DataValue::Str("Invalid settings_name".to_string()), + ) + .add_typed_default( + DataType::SettingsName, + DataValue::Str(settings_name.to_string()), + ) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + self.send_message(&response).await; + return; + } + + save_file( + &format!("users/{}/settings/{}/", my_id, session_id), + &format!("{}.settings", settings_name), + settings_value, + ); + + let response = CommunicationValue::new(CommunicationType::SettingsSave) + .with_receiver(my_id) + .with_id(cv.get_id()) + .add_typed_default( + DataType::SettingsName, + DataValue::Str(settings_name.to_string()), + ) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + + self.send_message(&response).await; + return; + } + + if cv.is_type(CommunicationType::SettingsLoad) { + let my_id = cv.get_sender(); + let Some(session_id) = cv.get_data(DataType::SessionId).as_number() else { + let response = CommunicationValue::new(CommunicationType::ErrorInvalidData) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::Message, + DataValue::Str("Missing session_id".to_string()), + ); + self.send_message(&response).await; + return; + }; + let Some(settings_name) = cv.get_data(DataType::SettingsName).as_str() else { + let response = CommunicationValue::new(CommunicationType::ErrorInvalidData) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::Message, + DataValue::Str("Missing settings_name".to_string()), + ) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + self.send_message(&response).await; + return; + }; + + if !settings_name + .chars() + .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.') + || settings_name.contains("..") + { + let response = CommunicationValue::new(CommunicationType::ErrorInvalidData) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::Message, + DataValue::Str("Invalid settings_name".to_string()), + ) + .add_typed_default( + DataType::SettingsName, + DataValue::Str(settings_name.to_string()), + ) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + self.send_message(&response).await; + return; + } + + let settings_file = format!("{}.settings", settings_name); + let settings_path = format!("users/{}/settings/{}/", my_id, session_id); + if !has_file(&settings_path, &settings_file) { + let response = CommunicationValue::new(CommunicationType::ErrorNotFound) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::SettingsName, + DataValue::Str(settings_name.to_string()), + ) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + self.send_message(&response).await; + return; + } + + let settings_value_str = load_file(&settings_path, &settings_file); + let response = CommunicationValue::new(CommunicationType::SettingsLoad) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default(DataType::Payload, DataValue::Str(settings_value_str)) + .add_typed_default( + DataType::SettingsName, + DataValue::Str(settings_name.to_string()), + ) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + + self.send_message(&response).await; + return; + } + + if cv.is_type(CommunicationType::SettingsList) { + let my_id = cv.get_sender(); + let Some(session_id) = cv.get_data(DataType::SessionId).as_number() else { + let response = CommunicationValue::new(CommunicationType::ErrorInvalidData) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default( + DataType::Message, + DataValue::Str("Missing session_id".to_string()), + ); + self.send_message(&response).await; + return; + }; + + let settings = get_children(&format!("users/{}/settings/{}/", my_id, session_id)); + let mut settings_json = Vec::new(); + for s in settings { + let s = s.replace(".settings", ""); + if s.is_empty() { + continue; + } + let _ = settings_json.push(DataValue::Str(s)); + } + let response = CommunicationValue::new(CommunicationType::SettingsList) + .with_id(cv.get_id()) + .with_receiver(my_id) + .add_typed_default(DataType::Settings, DataValue::Array(settings_json)) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id as i128), + ); + + self.send_message(&response).await; + return; + } } // ------------------------------------------------------------------------- // Public API // ------------------------------------------------------------------------- - pub async fn send_message(&self, cv: &CommunicationValue) -> Result<(), String> { + pub async fn send_message(&self, cv: &CommunicationValue) { + if let Err(err) = self.send_message_result(cv).await { + log_t!("send_message_failed", err); + } + } + + async fn send_message_result(&self, cv: &CommunicationValue) -> Result<(), String> { let sender_guard = self.sender.read().await; if let Some(sender) = sender_guard.as_ref() { if !sender.is_open() { @@ -1808,21 +2216,24 @@ impl OmikronConnection { cv: &CommunicationValue, timeout_duration: Option, ) -> Result { - let (tx, rx) = oneshot::channel(); + let (tx, mut rx) = mpsc::channel(1); let msg_id = cv.get_id(); WAITING_TASKS.insert( msg_id, WaitingTask { task: Box::new(move |_, response_cv| { - let _ = tx.send(response_cv); + let inner_tx = tx.clone(); + tokio::spawn(async move { + let _ = inner_tx.send(response_cv).await; + }); true }), inserted_at: Instant::now(), }, ); - if let Err(send_err) = self.send_message(cv).await { + if let Err(send_err) = self.send_message_result(cv).await { WAITING_TASKS.remove(&msg_id); return Err(format!( "Request send failed (msg_id={}, reason={})", @@ -1832,8 +2243,8 @@ impl OmikronConnection { let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); - match tokio::time::timeout(timeout, rx).await { - Ok(Ok(response_cv)) => { + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response_cv)) => { let is_error = response_cv.is_type(CommunicationType::Error) || response_cv.is_type(CommunicationType::ErrorInternal) || response_cv.is_type(CommunicationType::ErrorNotFound) @@ -1854,7 +2265,7 @@ impl OmikronConnection { Ok(response_cv) } } - Ok(Err(_)) => { + Ok(_) => { WAITING_TASKS.remove(&msg_id); Err("Channel closed while awaiting response".to_string()) } @@ -1873,32 +2284,27 @@ impl OmikronConnection { } pub async fn await_connection(&self, timeout_duration: Option) -> Result<(), String> { - let mut rx = self.state_watch_tx.subscribe(); - if rx.borrow().is_connected() { + if self.state.read().await.is_connected() { return Ok(()); } let timeout = timeout_duration.unwrap_or(CONNECTION_TIMEOUT); + let start = Instant::now(); - let result: Result<(), String> = tokio::time::timeout(timeout, async { - loop { - rx.changed() - .await - .map_err(|_| "State watch channel closed".to_string())?; - if rx.borrow().is_connected() { - return Ok(()); - } + loop { + if self.state.read().await.is_connected() { + return Ok(()); } - }) - .await - .map_err(|_| { - format!( - "Connection not established within {} seconds", - timeout.as_secs() - ) - })?; - result + if start.elapsed() >= timeout { + return Err(format!( + "Connection not established within {} seconds", + timeout.as_secs() + )); + } + + sleep(Duration::from_millis(100)).await; + } } pub async fn has_auth_failure(&self) -> bool { @@ -1939,29 +2345,3 @@ pub async fn get_omikron_connection() -> Arc { conn.connect().await; conn } - -impl iota_connection::connection_handler::ConnectionHandler for OmikronConnection { - async fn send_message(&self, cv: &CommunicationValue) -> Result<(), String> { - OmikronConnection::send_message(self, cv).await - } - - async fn await_response( - &self, - cv: &CommunicationValue, - timeout: Option, - ) -> Result { - OmikronConnection::await_response(self, cv, timeout).await - } - - async fn is_connected(&self) -> bool { - OmikronConnection::is_connected(self).await - } - - async fn is_identified(&self) -> bool { - OmikronConnection::is_identified(self).await - } - - async fn stop(&self) { - OmikronConnection::stop(self).await - } -} diff --git a/omikron-connector/src/ping_pong_task.rs b/omikron-connector/src/ping_pong_task.rs index 2155b6c..9c81c60 100644 --- a/omikron-connector/src/ping_pong_task.rs +++ b/omikron-connector/src/ping_pong_task.rs @@ -2,7 +2,6 @@ use crate::omikron_connection::OmikronConnection; use dashmap::DashMap; use iota_state::APP_STATE; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use std::sync::atomic::Ordering; use std::sync::LazyLock; use std::time::Instant; use tokio::time::Duration; @@ -17,8 +16,6 @@ impl OmikronConnection { PING_TIMES.retain(|_, v| v.elapsed() < Duration::from_secs(30)); - self.missed_pongs.fetch_add(1, Ordering::Relaxed); - let ping_message = CommunicationValue::new(CommunicationType::Ping) .with_id(id) .add_typed_default( @@ -26,12 +23,10 @@ impl OmikronConnection { DataValue::Array(vec![DataValue::SignedNumber(*self.last_ping.lock().await as i128)]), ); - let _ = self.send_message(&ping_message).await; + self.send_message(&ping_message).await; } pub async fn handle_pong(&self, cv: &CommunicationValue) { - self.missed_pongs.store(0, Ordering::Relaxed); - let id = cv.get_id(); if let Some((_, send_time)) = PING_TIMES.remove(&id) { diff --git a/type-maps.yaml b/type-maps.yaml index ce3d5e6..dddd7a2 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -148,7 +148,6 @@ type_maps: MessageReactionAdd: 146 MessageReactionRemove: 147 MessageReactionLive: 148 - MessageDeleteLive: 150 DataTypes: ErrorType: 32 ErrorProtocol: 33 @@ -170,7 +169,7 @@ type_maps: CallState: 49 ScreenShare: 50 PrivateKeyHash: 51 - # Accepted: 52 now part of default MTP + Accepted: 52 AcceptedProfiles: 53 DeniedProfiles: 54 Content: 55 diff --git a/web-server/Cargo.toml b/web-server/Cargo.toml index 9827408..74de956 100644 --- a/web-server/Cargo.toml +++ b/web-server/Cargo.toml @@ -5,10 +5,4 @@ version = "0.1.0" edition = "2024" [dependencies] -mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = ["web-server"] } -bytes = "1" -http = "1" -iota-state = { path = "../iota-state" } -iota-util = { path = "../iota-util" } -iota-logger = { path = "../iota-logger" } -tokio = { version = "1.50.0", features = ["full"] } +mtp = { git = "https://git.methanium.net/Methanium/mtp.git" } diff --git a/web-server/src/lib.rs b/web-server/src/lib.rs index 4dc18ab..e886571 100644 --- a/web-server/src/lib.rs +++ b/web-server/src/lib.rs @@ -1,133 +1,6 @@ -use bytes::Bytes; -use iota_logger::log; -use iota_state::{ACTIVE_TASKS, SHUTDOWN}; -use iota_util::file_util::load_file_vec; -use mtp::host::HostConfig; -use mtp::webserver::{Http3Request, Http3Response, MTPWebServer, WebServerConfig}; -use std::net::{IpAddr, Ipv4Addr}; -use tokio::time::{Duration, sleep}; - -const CERT_PATH: &str = "certs/cert.pem"; -const KEY_PATH: &str = "certs/cert.key"; - -async fn root(_request: Http3Request, response: Http3Response) -> Http3Response { - static_file("index.html", response).await -} - -async fn static_file(path: &str, response: Http3Response) -> Http3Response { - let file = path.trim_start_matches('/'); - let file = if file.is_empty() { "index.html" } else { file }; - - if file.split('/').any(|component| component == "..") { - return response - .status(http::StatusCode::BAD_REQUEST) - .body("invalid path"); - } - - let path = std::path::Path::new("web").join(file); - let Some(parent) = path.parent().and_then(|path| path.to_str()) else { - return response - .status(http::StatusCode::NOT_FOUND) - .body("not found"); - }; - let Some(name) = path.file_name().and_then(|name| name.to_str()) else { - return response - .status(http::StatusCode::NOT_FOUND) - .body("not found"); - }; - - match load_file_vec(parent, name) { - Ok(body) => response - .status(http::StatusCode::OK) - .header("content-type", content_type(name)) - .body(Bytes::from(body)), - Err(_) => response - .status(http::StatusCode::NOT_FOUND) - .body("not found"), - } -} - -fn content_type(name: &str) -> &'static str { - match std::path::Path::new(name) - .extension() - .and_then(|ext| ext.to_str()) - { - Some("html") => "text/html; charset=utf-8", - Some("css") => "text/css; charset=utf-8", - Some("js") => "application/javascript; charset=utf-8", - Some("json") => "application/json", - Some("png") => "image/png", - Some("ico") => "image/x-icon", - Some("woff2") => "font/woff2", - _ => "application/octet-stream", - } -} - -pub async fn start(port: u16) -> bool { - let certificate = match tokio::fs::read(CERT_PATH).await { - Ok(certificate) => certificate, - Err(error) => { - log!("MTP web server certificate load failed: {}", error); - return false; - } - }; - let key = match tokio::fs::read(KEY_PATH).await { - Ok(key) => key, - Err(error) => { - log!("MTP web server key load failed: {}", error); - return false; - } - }; - - let host_config = HostConfig::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port, certificate, key); - let web_config = match WebServerConfig::new().route("/", root).and_then(|config| { - config.fallback(|request, response| async move { - static_file(request.uri.path(), response).await - }) - }) { - Ok(config) => config, - Err(error) => { - log!("MTP web server route setup failed: {}", error); - return false; - } - }; - - let mut server = match MTPWebServer::new(host_config, web_config).await { - Ok(server) => server, - Err(error) => { - log!("MTP web server startup failed: {}", error); - return false; - } - }; - - log!("MTP web server running on port {}", port); - tokio::spawn(async move { - ACTIVE_TASKS.insert("WebServer".into()); - loop { - tokio::select! { - result = server.accept() => { - match result { - Ok(Some(_connection)) => {} - Ok(None) => break, - Err(error) => log!("MTP webserver connection failed: {}", error), - } - } - _ = wait_for_shutdown() => { - server.shutdown().await; - break; - } - } - } - ACTIVE_TASKS.remove("WebServer"); - }); - true -} - -async fn wait_for_shutdown() { - loop { - if *SHUTDOWN.read().await { - break; - } - sleep(Duration::from_millis(100)).await; - } -} +// The web server is a TTP host & identification system, +// it "upgrades" connections after identification to +// +// either Own User (Cut down version of the Omikron Connection), +// or Community (Custom Connection), +// or Iota (Custom Connection).