From 044eeb67a6b7f2cbb5174d7fc67c9e0ad0511aac Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 9 Oct 2025 19:47:32 +0000 Subject: [PATCH] Inital Commit (Errors, I need to move through devices) Signed-off-by: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> --- .gitignore | 11 +- Cargo.lock | 2976 +++++++++++++++++++++++++++++++++ Cargo.toml | 43 + src/auth/auth_connector.rs | 178 ++ src/auth/mod.rs | 1 + src/calls/call_connection.rs | 189 +++ src/calls/call_group.rs | 106 ++ src/calls/call_manager.rs | 40 + src/calls/mod.rs | 3 + src/data/communication.rs | 402 +++++ src/data/mod.rs | 2 + src/data/user.rs | 49 + src/main.rs | 23 + src/omega/mod.rs | 1 + src/omega/omega_connection.rs | 215 +++ src/rho/client_connection.rs | 515 ++++++ src/rho/iota_connection.rs | 345 ++++ src/rho/mod.rs | 21 + src/rho/rho_connection.rs | 251 +++ src/rho/rho_manager.rs | 93 ++ src/util/config_util.rs | 63 + src/util/file_util.rs | 165 ++ src/util/mod.rs | 2 + 23 files changed, 5687 insertions(+), 7 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/auth/auth_connector.rs create mode 100644 src/auth/mod.rs create mode 100644 src/calls/call_connection.rs create mode 100644 src/calls/call_group.rs create mode 100644 src/calls/call_manager.rs create mode 100644 src/calls/mod.rs create mode 100644 src/data/communication.rs create mode 100644 src/data/mod.rs create mode 100644 src/data/user.rs create mode 100644 src/main.rs create mode 100644 src/omega/mod.rs create mode 100644 src/omega/omega_connection.rs create mode 100644 src/rho/client_connection.rs create mode 100644 src/rho/iota_connection.rs create mode 100644 src/rho/mod.rs create mode 100644 src/rho/rho_connection.rs create mode 100644 src/rho/rho_manager.rs create mode 100644 src/util/config_util.rs create mode 100644 src/util/file_util.rs create mode 100644 src/util/mod.rs diff --git a/.gitignore b/.gitignore index ad67955..eedc620 100644 --- a/.gitignore +++ b/.gitignore @@ -11,11 +11,8 @@ target # Generated by cargo mutants # Contains mutation testing data -**/mutants.out*/ +**/mutants.out*/W -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ + +# Added by cargo +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b3259bf --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2976 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Omikron" +version = "0.1.0" +dependencies = [ + "axum", + "base64", + "bytes", + "chrono", + "cmake", + "crossterm", + "dashmap", + "der", + "futures", + "futures-util", + "hex", + "http", + "hyper", + "json", + "once_cell", + "pkcs8", + "rand 0.8.5", + "rand_core 0.6.4", + "reqwest", + "rustls", + "serde", + "sha2", + "sys-info", + "sysinfo", + "tokio", + "tokio-stream", + "tokio-tungstenite 0.28.0", + "tokio-util", + "tokio_websocket_server", + "tracing", + "tracing-subscriber", + "tungstenite 0.28.0", + "uuid", + "walkdir", + "warp", + "x448", + "x509", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libloading", +] + +[[package]] +name = "axum" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.0", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.0", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ed448-goldilocks" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" +dependencies = [ + "fiat-crypto", + "hex", + "subtle", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.1", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + +[[package]] +name = "find-msvc-tools" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.1", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 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.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.1", +] + +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sys-info" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.1", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.26.2", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.28.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "hashbrown 0.15.5", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "tokio_websocket_server" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79af8a3a0690c5b3d6de188a962472c1f94e0830c0432fcc03380dd5ef74d65c" +dependencies = [ + "futures-util", + "rustls", + "rustls-pemfile", + "serde", + "tokio", + "tokio-rustls", + "tokio-tungstenite 0.26.2", + "tracing", + "uuid", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "sha1", + "thiserror", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "sha1", + "thiserror", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d06d9202adc1f15d709c4f4a2069be5428aa912cc025d6f268ac441ab066b0" +dependencies = [ + "bytes", + "futures-util", + "headers", + "http", + "http-body", + "http-body-util", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.1", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.62.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.0", + "windows-result 0.4.0", + "windows-strings 0.5.0", +] + +[[package]] +name = "windows-implement" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +dependencies = [ + "windows-link 0.2.0", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link 0.2.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link 0.2.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "x448" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" +dependencies = [ + "ed448-goldilocks", + "hex", + "rand_core 0.5.1", +] + +[[package]] +name = "x509" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3cec94c3999f31341553f358ef55f65fc031291a022cd42ec0ce7219560c76" +dependencies = [ + "chrono", + "cookie-factory", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3fb4d12 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "Omikron" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { version = "*", features = ["full"] } +tokio-tungstenite = "*" +tokio-util = { version = "*", features = ["full"] } +hyper = { version = "*", features = ["full"] } +futures-util = "*" +http = "*" +tungstenite = "*" +tokio_websocket_server = "0.1.0" +tracing = "*" +tracing-subscriber = "*" +uuid = "*" +axum = "*" +json = "*" +reqwest = "*" +hex = "*" +rustls = { version = "*", default-features = false, features = ["ring"] } +cmake = "*" +walkdir = "2.5.0" +sysinfo = "0.30" +serde = { version = "1.0.219", features = ["derive"] } +bytes = "*" +crossterm = "*" +sys-info = "*" +rand = "0.8" +rand_core = { version = "0.6", features = ["getrandom", "std"] } +base64 = "0.22.1" +once_cell = "1.21.3" +x448 = { version = "*" } +x509 = "*" +sha2 = "*" +der = "*" +pkcs8 = { version = "*", features = ["alloc"] } +futures = "*" +dashmap = "*" +warp = { version = "*" } +chrono = "*" +tokio-stream = "*" diff --git a/src/auth/auth_connector.rs b/src/auth/auth_connector.rs new file mode 100644 index 0000000..4e9a6dc --- /dev/null +++ b/src/auth/auth_connector.rs @@ -0,0 +1,178 @@ +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::util::config_util::CONFIG; +use hex; +use json::JsonValue; +use reqwest::header::CONTENT_TYPE; +use reqwest::{Client, Response}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::time::Duration; +use uuid::Uuid; +#[derive(Debug, Clone)] +pub struct AuthUser { + pub created_at: i64, + pub username: String, + pub display: String, + pub avatar: String, + pub about: String, + pub status: String, + pub public_key: String, + pub sub_level: i32, + pub sub_end: i32, +} + +fn client() -> Client { + Client::builder() + .connect_timeout(Duration::from_secs(100)) + .timeout(Duration::from_secs(150)) + .build() + .unwrap() +} + +pub async fn get_user(user_id: Uuid) -> Option { + let url = format!("https://auth.tensamin.methanium.net/api/get/{}/", user_id); + let client = client(); + let res = client.get(&url).send().await.ok()?; + let json = res.text().await.ok()?; + + let mut cv = CommunicationValue::from_json(&json); + if cv.comm_type != CommunicationType::success { + return None; + } + + Some(AuthUser { + created_at: cv + .get_data(DataTypes::created_at) + .unwrap() + .to_string() + .parse::() + .unwrap_or(-1), + username: cv.get_data(DataTypes::username).unwrap().to_string(), + display: cv.get_data(DataTypes::display).unwrap().to_string(), + avatar: cv.get_data(DataTypes::avatar).unwrap().to_string(), + about: cv.get_data(DataTypes::about).unwrap().to_string(), + status: cv.get_data(DataTypes::status).unwrap().to_string(), + public_key: cv.get_data(DataTypes::public_key).unwrap().to_string(), + sub_level: cv + .get_data(DataTypes::sub_level) + .unwrap() + .to_string() + .parse::() + .unwrap_or(-1), + sub_end: cv + .get_data(DataTypes::sub_end) + .unwrap() + .to_string() + .parse::() + .unwrap_or(-1), + }) +} + +pub async fn get_iota_id(user_id: Uuid) -> Option { + let url = format!( + "https://auth.tensamin.methanium.net/api/get/iota-id/{}", + user_id + ); + + let client = client(); + let res = client + .get(&url) + .header( + "Authorization", + CONFIG.read().unwrap().omikron_id.to_string(), + ) + .header("Content-Type", "application/json") + .send() + .await + .ok()?; + + let json = res.text().await.ok()?; + let json = json.replace("iota_uuid", "iota_id"); + + let mut cv = CommunicationValue::from_json(&json); + if cv.comm_type != CommunicationType::success { + return None; + } + + let iota_id_str = cv.get_data(DataTypes::iota_id)?.to_string(); + Uuid::parse_str(&iota_id_str).ok() +} +pub async fn is_private_key_valid(user_id: Uuid, pk_hash: &str) -> bool { + let url = format!( + "https://auth.tensamin.methanium.net/api/get/private-key-hash/{}", + user_id + ); + + let client = client(); + let res = client + .get(&url) + .header( + "Authorization", + CONFIG.read().unwrap().omikron_id.to_string(), + ) + .header("PrivateKeyHash", pk_hash) + .header("Accept", "application/json") + .send() + .await; + + let Ok(response) = res else { + return false; + }; + + let Ok(body) = response.text().await else { + return false; + }; + + let mut cv = CommunicationValue::from_json(&body); + if cv.comm_type != CommunicationType::success { + return false; + } + + match cv.get_data(DataTypes::matches) { + Some(val) => val.as_bool().unwrap_or(false), + None => false, + } +} +pub async fn get_public_key(user_id: Uuid) -> Option { + let url = format!( + "https://auth.tensamin.methanium.net/api/{}/public-key/", + user_id + ); + + let client = client(); + let res = client + .get(&url) + .header("Accept", "application/json") + .send() + .await + .ok()?; + + let body = res.text().await.ok()?; + let mut cv = CommunicationValue::from_json(&body); + + if cv.comm_type != CommunicationType::message_send { + return None; + } + + Some(cv.get_data(DataTypes::ping_clients)?.to_string()) +} + +pub async fn get_register() -> Option { + let url = "https://auth.tensamin.methanium.net/api/register/init/".to_string(); + let client = client(); + let res = client.get(&url).send().await.ok()?; + let json = res.text().await.ok()?; + + let mut cv = CommunicationValue::from_json(&json); + Uuid::parse_str(&*cv.get_data(DataTypes::user_id).unwrap().to_string()).ok() +} + +async fn handle_response(resp: Response) -> bool { + match resp.text().await { + Ok(text) => { + let cv = CommunicationValue::from_json(&text.to_string()); + cv.comm_type == CommunicationType::success + } + Err(_) => false, + } +} diff --git a/src/auth/mod.rs b/src/auth/mod.rs new file mode 100644 index 0000000..7b384b2 --- /dev/null +++ b/src/auth/mod.rs @@ -0,0 +1 @@ +pub mod auth_connector; diff --git a/src/calls/call_connection.rs b/src/calls/call_connection.rs new file mode 100644 index 0000000..a203154 --- /dev/null +++ b/src/calls/call_connection.rs @@ -0,0 +1,189 @@ +// part of calls or in your ws-server file + +use std::sync::Arc; + +use futures::{SinkExt, StreamExt, channel::mpsc::UnboundedSender, lock::Mutex}; +use json::JsonValue; +use tokio::sync::mpsc::unbounded_channel; +use tungstenite::{Message, Utf8Bytes}; +use uuid::Uuid; + +use crate::{ + calls::{call_group::CallGroup, call_group::Caller, call_manager::CallManagerState}, + data::communication::{CommunicationType, CommunicationValue, DataTypes}, +}; + +pub async fn handle_connection( + raw_stream: tokio::net::TcpStream, + state: Arc>, +) { + let ws_stream = tokio_tungstenite::accept_async(raw_stream) + .await + .expect("Error during the websocket handshake"); + + let (mut outgoing, mut incoming) = ws_stream.split(); + + // We create a channel so other parts can send to this client + pub type Tx = UnboundedSender; + let (tx, mut rx): (_, _) = unbounded_channel(); + + // Spawn a task to forward from rx → outgoing + let mut outgoing_clone = outgoing; + tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + let _ = outgoing_clone + .send(tokio_tungstenite::tungstenite::Message::Text(msg)) + .await; + } + }); + + // Each connection will track its user_id, call_group, etc. + let mut maybe_user_id: Option = None; + let mut maybe_call_id: Option = None; + + while let Some(msg) = incoming.next().await { + let msg = match msg { + Ok(m) => m, + Err(_) => break, + }; + if let tokio_tungstenite::tungstenite::Message::Text(s) = msg { + // parse CommunicationValue + if let mut cv = CommunicationValue::from_json(&s) { + match cv.comm_type { + CommunicationType::identification => { + // extract fields + let user_str = cv.get_data(DataTypes::user_id).unwrap().as_str().unwrap(); + let call_str = cv.get_data(DataTypes::call_id).unwrap().as_str().unwrap(); + let _secret_sha = cv + .get_data(DataTypes::call_secret_sha) + .unwrap() + .as_str() + .unwrap(); + + let user_id = Uuid::parse_str(user_str).ok(); + let call_id = Uuid::parse_str(call_str).ok(); + if let (Some(uid), Some(cid)) = (user_id, call_id) { + maybe_user_id = Some(uid); + maybe_call_id = Some(cid); + // Add to call group + let mut st = state.lock().await; + let group = state.lock().await.get_or_create_group(cid, &_secret_sha); + group.clone().add_member(uid, tx.clone()); + + // Response: identification_response plus states + let mut response = + CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id().clone()); + // build user states + let mut users = JsonValue::new_object(); + for c in group.callers.values() { + let mut user_info = JsonValue::new_object(); + user_info.insert("state", JsonValue::from("muted".to_string())); + user_info.insert("streaming", JsonValue::from(false)); + users.insert(&c.user_id.to_string(), JsonValue::from(user_info)); + } + response = response.add_data(DataTypes::about, JsonValue::from(users)); + // send back + let _ = tx.send(Utf8Bytes::from(response.to_json().to_string())); + } + } + + CommunicationType::ping => { + // optionally parse LAST_PING + // reply with PONG with same message_id + let mut resp = CommunicationValue::new(CommunicationType::pong) + .with_id(cv.get_id().clone()); + let _ = tx.send(Utf8Bytes::from(resp.to_json().to_string())); + } + + CommunicationType::client_changed => { + match (maybe_user_id, cv.get_data(DataTypes::call_state)) { + (Some(uid), Some(JsonValue::String(state_str))) => { + if let Some(cid) = maybe_call_id { + { + let mut st = state.lock().await; + if let Some(group) = st.call_groups.get_mut(&cid) { + if let Some(caller) = group.caller_state_mut(&uid) { + caller_state_change(caller, &state_str); + } + let mut bc = cv.clone(); + bc = bc.add_data( + DataTypes::sender_id, + JsonValue::String(uid.to_string()), + ); + group.broadcast(&bc.to_json().to_string()); + } + } + } + } + _ => (), + } + } + + CommunicationType::start_stream | CommunicationType::end_stream => { + if let Some(uid) = maybe_user_id { + if let Some(cid) = maybe_call_id { + let mut st = state.lock().await; + if let Some(group) = st.call_groups.get_mut(&cid) { + if let Some(caller) = group.caller_state_mut(&uid) { + let streaming = + cv.comm_type == CommunicationType::start_stream; + // set streaming + // caller.streaming = streaming; // if you store streaming + } + let mut bc = cv.clone(); + bc = bc.add_data( + DataTypes::sender_id, + JsonValue::String(uid.to_string()), + ); + group.broadcast(&bc.to_json().to_string()); + } + } + } + } + + CommunicationType::webrtc_sdp + | CommunicationType::webrtc_ice + | CommunicationType::watch_stream => { + if let Some(uid) = maybe_user_id { + if let Some(JsonValue::String(receiver_str)) = + cv.get_data(DataTypes::receiver_id) + { + if let Ok(receiver_id) = Uuid::parse_str(&receiver_str) { + if let Some(cid) = maybe_call_id { + let mut st = state.lock().await; + if let Some(group) = st.call_groups.get(&cid) { + let mut bc = cv.clone(); + bc = bc.add_data( + DataTypes::sender_id, + JsonValue::String(uid.to_string()), + ); + group.send_to(&receiver_id, &bc.to_json().to_string()); + } + } + } + } + } + } + + _ => { + // other types you may handle + } + } + } + } + } + + // on close / disconnection: + if let (Some(uid), Some(cid)) = (maybe_user_id, maybe_call_id) { + let mut st = state.lock().await; + if let Some(group) = st.call_groups.get_mut(&cid) { + group.remove_member(&uid); + } + st.remove_inactive(); + } +} + +fn caller_state_change(_caller: &mut Caller, _state_str: &str) { + // parse and set your enum, e.g. match _state_str { "active" => ..., etc. } +} diff --git a/src/calls/call_group.rs b/src/calls/call_group.rs new file mode 100644 index 0000000..44f29fb --- /dev/null +++ b/src/calls/call_group.rs @@ -0,0 +1,106 @@ +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; +use tokio::sync::mpsc::UnboundedSender; + +use json::JsonValue; +use tungstenite::Utf8Bytes; +use uuid::Uuid; + +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; + +pub type Tx = UnboundedSender; + +#[derive(Debug)] +pub struct CallGroup { + pub call_id: Uuid, + secret: String, + pub callers: HashMap, + pub inform_on_end: HashSet, + pub started_at: u64, + pub ended_at: Option, +} + +impl CallGroup { + pub fn new(call_id: Uuid, secret: String) -> Self { + CallGroup { + call_id, + secret, + callers: HashMap::new(), + inform_on_end: HashSet::new(), + started_at: chrono::Utc::now().timestamp_millis() as u64, + ended_at: None, + } + } + + pub fn add_member(mut self, user_id: Uuid, tx: Tx) { + // If already present, drop old + if let Some(old) = self.callers.remove(&user_id) { + // optionally notify or close old + // no direct close since we don’t hold session here + } + // Notify existing about new client + let mut cv = CommunicationValue::new(CommunicationType::client_connected); + cv = cv + .add_data(DataTypes::user_id, JsonValue::String(user_id.to_string())) + .add_data( + DataTypes::call_state, + JsonValue::String("muted".to_string()), + ); + // broadcast + let msg = cv.to_json().to_string(); + for c in self.callers.values() { + let _ = c.tx.send(Utf8Bytes::from(msg.clone())); + } + self.callers.insert(user_id, Caller { user_id, tx }); + } + + pub fn remove_member(&mut self, user_id: &Uuid) { + if self.callers.remove(user_id).is_some() { + let cv = CommunicationValue::new(CommunicationType::client_closed) + .add_data(DataTypes::user_id, JsonValue::String(user_id.to_string())) + .to_json() + .to_string(); + for c in self.callers.values() { + let _ = c.tx.send(Utf8Bytes::from(cv.clone())); + } + } + if self.callers.is_empty() { + self.ended_at = Some(chrono::Utc::now().timestamp_millis() as u64); + // Optionally notify inform_on_end + let end_msg = CommunicationValue::new(CommunicationType::end_call) + .add_data( + DataTypes::call_id, + JsonValue::String(self.call_id.to_string()), + ) + .to_json() + .to_string(); + for &uid in &self.inform_on_end { + // here you’d send via your Rho / other channel to user + // e.g. RhoManager::message_to(uid, end_msg.clone()); + } + } + } + + pub fn broadcast(&self, msg: &str) { + for c in self.callers.values() { + let _ = c.tx.send(Utf8Bytes::from(msg.clone())); + } + } + + pub fn send_to(&self, target: &Uuid, msg: &str) { + if let Some(c) = self.callers.get(target) { + let _ = c.tx.send(Utf8Bytes::from(msg.clone())); + } + } + + pub fn caller_state_mut(&mut self, uid: &Uuid) -> Option<&mut Caller> { + self.callers.get_mut(uid) + } +} +#[derive(Debug)] +pub struct Caller { + pub user_id: Uuid, + pub tx: Tx, +} diff --git a/src/calls/call_manager.rs b/src/calls/call_manager.rs new file mode 100644 index 0000000..1f6b04c --- /dev/null +++ b/src/calls/call_manager.rs @@ -0,0 +1,40 @@ +use crate::calls::call_group::CallGroup; +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use futures::{SinkExt, StreamExt}; +use json::JsonValue; +use std::collections::{HashMap, HashSet}; +use std::sync::{Arc, Mutex}; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; +use tungstenite::Utf8Bytes; +use uuid::Uuid; +use warp::Filter; + +pub type Tx = UnboundedSender; + +#[derive(Debug)] +pub struct CallManagerState { + pub call_groups: HashMap>, +} + +impl CallManagerState { + pub fn new() -> Self { + CallManagerState { + call_groups: HashMap::new(), + } + } + + pub fn get_or_create_group(&mut self, call_id: Uuid, secret: &str) -> Arc { + let g = self.call_groups.get_mut(&call_id); + if let Some(group) = g { + group.clone() + } else { + let cg = Arc::new(CallGroup::new(call_id, secret.to_string())); + self.call_groups.insert(call_id, cg); + self.call_groups.get(&call_id).unwrap().clone() + } + } + + pub fn remove_inactive(&mut self) { + self.call_groups.retain(|_k, v| v.callers.len() > 0); + } +} diff --git a/src/calls/mod.rs b/src/calls/mod.rs new file mode 100644 index 0000000..360fafa --- /dev/null +++ b/src/calls/mod.rs @@ -0,0 +1,3 @@ +pub mod call_connection; +pub mod call_group; +pub mod call_manager; diff --git a/src/data/communication.rs b/src/data/communication.rs new file mode 100644 index 0000000..846a602 --- /dev/null +++ b/src/data/communication.rs @@ -0,0 +1,402 @@ +use axum::Json; +use json::number::Number; +use json::{Array, JsonValue, object, parse, stringify}; +use std::any::Any; +use std::collections::HashMap; +use std::env::VarsOs; +use std::str::FromStr; +use std::time::{SystemTime, UNIX_EPOCH}; +use uuid::Uuid; + +#[derive(Eq, Hash, PartialEq, Clone, Debug)] +pub enum DataTypes { + error_type, + uuid, + chat_partner_id, + iota_id, + user_id, + user_ids, + user_state, + user_states, + user_pings, + call_state, + screen_share, + private_key_hash, + accepted, + accepted_profiles, + denied_profiles, + content, + messages, + send_time, + get_time, + get_variant, + shared_secret_own, + shared_secret_other, + shared_secret_sign, + shared_secret, + call_id, + call_name, + call_secret_sha, + call_secret, + shared_call_secret, + start_date, + end_date, + receiver_id, + sender_id, + signature, + signed, + message, + last_ping, + ping_iota, + ping_clients, + matches, + omikron, + offset, + amount, + position, + name, + path, + codec, + function, + payload, + result, + interactables, + want_to_watch, + watcher, + created_at, + username, + display, + avatar, + about, + status, + public_key, + sub_level, + sub_end, + community_address, + challenge, + community_title, + communities, +} + +impl DataTypes { + pub fn parse(p0: String) -> DataTypes { + // normalize: lowercase + remove underscores + let normalized = p0.to_lowercase().replace('_', ""); + + match normalized.as_str() { + "errortype" => DataTypes::error_type, + "chatpartnerid" => DataTypes::chat_partner_id, + "iotaid" => DataTypes::iota_id, + "userid" => DataTypes::user_id, + "userids" => DataTypes::user_ids, + "userstate" => DataTypes::user_state, + "userstates" => DataTypes::user_states, + "userpings" => DataTypes::user_pings, + "callstate" => DataTypes::call_state, + "screenshare" => DataTypes::screen_share, + "privatekeyhash" => DataTypes::private_key_hash, + "accepted" => DataTypes::accepted, + "acceptedprofiles" => DataTypes::accepted_profiles, + "deniedprofiles" => DataTypes::denied_profiles, + "content" => DataTypes::content, + "messages" => DataTypes::messages, + "sendtime" => DataTypes::send_time, + "gettime" => DataTypes::get_time, + "getvariant" => DataTypes::get_variant, + "sharedsecretown" => DataTypes::shared_secret_own, + "sharedsecretother" => DataTypes::shared_secret_other, + "sharedsecretsign" => DataTypes::shared_secret_sign, + "sharedsecret" => DataTypes::shared_secret, + "callid" => DataTypes::call_id, + "callname" => DataTypes::call_name, + "callsecretsha" => DataTypes::call_secret_sha, + "callsecret" => DataTypes::call_secret, + "sharedcallsecret" => DataTypes::shared_call_secret, + "startdate" => DataTypes::start_date, + "enddate" => DataTypes::end_date, + "receiverid" => DataTypes::receiver_id, + "senderid" => DataTypes::sender_id, + "signature" => DataTypes::signature, + "signed" => DataTypes::signed, + "message" => DataTypes::message, + "lastping" => DataTypes::last_ping, + "pingiota" => DataTypes::ping_iota, + "pingclients" => DataTypes::ping_clients, + "matches" => DataTypes::matches, + "omikron" => DataTypes::omikron, + "offset" => DataTypes::offset, + "amount" => DataTypes::amount, + "position" => DataTypes::position, + "name" => DataTypes::name, + "path" => DataTypes::path, + "codec" => DataTypes::codec, + "function" => DataTypes::function, + "payload" => DataTypes::payload, + "result" => DataTypes::result, + "interactables" => DataTypes::interactables, + "wanttowatch" => DataTypes::want_to_watch, + "watcher" => DataTypes::watcher, + "createdat" => DataTypes::created_at, + "username" => DataTypes::username, + "display" => DataTypes::display, + "avatar" => DataTypes::avatar, + "about" => DataTypes::about, + "status" => DataTypes::status, + "publickey" => DataTypes::public_key, + "sublevel" => DataTypes::sub_level, + "subend" => DataTypes::sub_end, + "communityaddress" => DataTypes::community_address, + "challenge" => DataTypes::challenge, + "communitytitle" => DataTypes::community_title, + "communities" => DataTypes::communities, + _ => DataTypes::error_type, // fallback if unknown + } + } +} + +#[derive(PartialEq, Clone, Debug)] +pub enum CommunicationType { + error, + success, + message, + message_send, + message_live, + message_other_iota, + message_chunk, + messages_get, + change_confirm, + confirm_receive, + confirm_read, + get_chats, + get_states, + add_community, + remove_community, + get_communities, + challenge, + challenge_response, + register, + register_response, + identification, + identification_response, + ping, + pong, + add_chat, + send_chat, + iota_connected, + iota_closed, + client_changed, + client_connected, + client_closed, + public_key, + private_key, + webrtc_sdp, + webrtc_ice, + start_stream, + end_stream, + watch_stream, + get_call, + new_call, + call_invite, + end_call, + function, + update, +} +impl CommunicationType { + pub fn parse(p0: String) -> CommunicationType { + let normalized = p0.to_lowercase().replace('_', ""); + + match normalized.as_str() { + "error" => CommunicationType::error, + "success" => CommunicationType::success, + "message" => CommunicationType::message, + "messagelive" => CommunicationType::message_live, + "messageotheriota" => CommunicationType::message_other_iota, + "messagechunk" => CommunicationType::message_chunk, + "messagesget" => CommunicationType::messages_get, + "message_send" => CommunicationType::message_send, + "changeconfirm" => CommunicationType::change_confirm, + "confirmreceive" => CommunicationType::confirm_receive, + "confirmread" => CommunicationType::confirm_read, + "getchats" => CommunicationType::get_chats, + "getstates" => CommunicationType::get_states, + "addcommunity" => CommunicationType::add_community, + "removecommunity" => CommunicationType::remove_community, + "getcommunities" => CommunicationType::get_communities, + "challenge" => CommunicationType::challenge, + "challengeresponse" => CommunicationType::challenge_response, + "register" => CommunicationType::register, + "registerresponse" => CommunicationType::register_response, + "identification" => CommunicationType::identification, + "identificationresponse" => CommunicationType::identification_response, + "ping" => CommunicationType::ping, + "pong" => CommunicationType::pong, + "addchat" => CommunicationType::add_chat, + "sendchat" => CommunicationType::send_chat, + "iotaconnected" => CommunicationType::iota_connected, + "iotaclosed" => CommunicationType::iota_closed, + "clientchanged" => CommunicationType::client_changed, + "clientconnected" => CommunicationType::client_connected, + "clientclosed" => CommunicationType::client_closed, + "publickey" => CommunicationType::public_key, + "privatekey" => CommunicationType::private_key, + "webrtcsdp" => CommunicationType::webrtc_sdp, + "webrtcice" => CommunicationType::webrtc_ice, + "startstream" => CommunicationType::start_stream, + "endstream" => CommunicationType::end_stream, + "watchstream" => CommunicationType::watch_stream, + "getcall" => CommunicationType::get_call, + "newcall" => CommunicationType::new_call, + "callinvite" => CommunicationType::call_invite, + "endcall" => CommunicationType::end_call, + "function" => CommunicationType::function, + "update" => CommunicationType::update, + _ => CommunicationType::error, + } + } +} + +#[derive(Debug, Clone)] +pub struct CommunicationValue { + pub id: Uuid, + pub comm_type: CommunicationType, + pub sender: Uuid, + pub receiver: Uuid, + pub data: HashMap, +} + +impl CommunicationValue { + pub fn new(comm_type: CommunicationType) -> Self { + Self { + id: Uuid::new_v4(), + comm_type, + sender: Uuid::new_v4(), + receiver: Uuid::new_v4(), + data: HashMap::new(), + } + } + pub fn with_id(mut self, p0: Uuid) -> Self { + self.id = p0; + self + } + pub fn get_id(&self) -> Uuid { + self.id.clone() + } + pub fn with_sender(mut self, sender: Uuid) -> Self { + self.sender = sender; + self + } + pub fn get_sender(&self) -> Uuid { + self.sender.clone() + } + pub fn with_receiver(mut self, receiver: Uuid) -> Self { + self.receiver = receiver; + self + } + pub fn get_receiver(&self) -> Uuid { + self.receiver.clone() + } + pub fn add_data_num(mut self, key: DataTypes, value: Number) -> Self { + self.data.insert(key, JsonValue::Number(value)); + self + } + pub fn add_data_str(mut self, key: DataTypes, value: String) -> Self { + self.data.insert(key, JsonValue::String(value)); + self + } + pub fn add_data(mut self, key: DataTypes, value: JsonValue) -> Self { + self.data.insert(key, value); + self + } + pub fn add_array(mut self, key: DataTypes, value: Array) -> Self { + self.data.insert(key, JsonValue::Array(value)); + self + } + pub fn get_data(&self, key: DataTypes) -> Option<&JsonValue> { + self.data.get(&key) + } + + pub(crate) fn is_type(&self, p0: CommunicationType) -> bool { + self.comm_type == p0 + } + pub fn to_json(&self) -> JsonValue { + let mut jdata = object! {}; + for (k, v) in &self.data { + jdata[&format!("{:?}", k)] = JsonValue::from(v.clone()); + } + + object! { + id: self.id.to_string(), + type: format!("{:?}", self.comm_type), + sender: self.sender.to_string(), + receiver: self.receiver.to_string(), + data: jdata + } + } + + pub fn from_json(json_str: &str) -> Self { + let parsed = parse(json_str).unwrap(); + + let comm_type = CommunicationType::parse(parsed["type"].to_string()); + + let sender: Uuid = parsed["sender"] + .as_str() + .and_then(|s| Uuid::parse_str(s).ok()) + .unwrap_or(Uuid::new_v4()); + let receiver: Uuid = parsed["receiver"] + .as_str() + .and_then(|s| Uuid::parse_str(s).ok()) + .unwrap_or(Uuid::new_v4()); + + let uuid = Uuid::parse_str(parsed["id"].as_str().unwrap_or("")).unwrap_or(Uuid::new_v4()); + let mut data = HashMap::new(); + if parsed["data"].is_object() { + for (k, v) in parsed["data"].entries() { + data.insert(DataTypes::parse(k.to_string()), v.clone()); + } + } + + Self { + id: uuid, + comm_type, + sender, + receiver, + data, + } + } + pub fn ack_message(message_id: Uuid, sender: Uuid) -> CommunicationValue { + let mut cv = CommunicationValue::new(CommunicationType::message).with_id(message_id); + + if let s = sender { + cv = cv.add_data(DataTypes::send_time, JsonValue::String(s.to_string())); + } + cv + } + pub fn forward_to_other_iota(original: &mut CommunicationValue) -> CommunicationValue { + let receiver = Uuid::from_str( + &*original + .get_data(DataTypes::receiver_id) + .unwrap() + .to_string(), + ) + .ok() + .or(Option::from(Uuid::nil())); + + let now_ms = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64; + + let cv = CommunicationValue::new(CommunicationType::message_other_iota) + .with_id(original.get_id()) + .with_receiver(receiver.unwrap()) + .add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string())) + .add_data( + DataTypes::content, + JsonValue::String(original.get_data(DataTypes::content).unwrap().to_string()), + ); + + // include sender_id if the original had one + let sender = original.get_sender(); + cv.add_data(DataTypes::sender_id, JsonValue::String(sender.to_string())) + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..7b9e99f --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,2 @@ +pub mod communication; +pub mod user; diff --git a/src/data/user.rs b/src/data/user.rs new file mode 100644 index 0000000..3516787 --- /dev/null +++ b/src/data/user.rs @@ -0,0 +1,49 @@ +use uuid::Uuid; + +#[derive(Clone)] +pub struct User { + pub iota_id: Uuid, + pub user_id: Uuid, + pub status: UserStatus, +} +impl User { + pub fn new(iota_id: Uuid, user_id: Uuid, status: UserStatus) -> Self { + User { + iota_id, + user_id, + status, + } + } +} +#[derive(Clone, Copy)] +pub enum UserStatus { + online, + do_not_disturb, + wc, + away, + user_offline, + iota_offline, +} +impl UserStatus { + pub fn to_string(self) -> String { + match self { + UserStatus::online => "online".to_string(), + UserStatus::do_not_disturb => "do_not_disturb".to_string(), + UserStatus::wc => "wc".to_string(), + UserStatus::away => "away".to_string(), + UserStatus::user_offline => "user_offline".to_string(), + UserStatus::iota_offline => "iota_offline".to_string(), + } + } + pub fn from_string(status_str: &str) -> Result { + match status_str.to_lowercase().as_str() { + "online" => Ok(UserStatus::online), + "do_not_disturb" => Ok(UserStatus::do_not_disturb), + "wc" => Ok(UserStatus::wc), + "away" => Ok(UserStatus::away), + "user_offline" => Ok(UserStatus::user_offline), + "iota_offline" => Ok(UserStatus::iota_offline), + _ => Err(format!("Invalid user status: {}", status_str)), + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0ee3b59 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,23 @@ +mod auth; +mod calls; +mod data; +mod omega; +mod rho; +mod util; + +use crate::rho::client_connection; +use crate::rho::iota_connection; +use crate::util::config_util::Config; +use crate::util::file_util; +use tokio_websocket_server::socket::{WebSocketMessage, WebsocketServer}; +use tracing::info; +use tracing_subscriber::fmt; + +#[tokio::main] +async fn main() { + fmt::init(); + + loop { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } +} diff --git a/src/omega/mod.rs b/src/omega/mod.rs new file mode 100644 index 0000000..cbe77d9 --- /dev/null +++ b/src/omega/mod.rs @@ -0,0 +1 @@ +pub mod omega_connection; diff --git a/src/omega/omega_connection.rs b/src/omega/omega_connection.rs new file mode 100644 index 0000000..9202cd1 --- /dev/null +++ b/src/omega/omega_connection.rs @@ -0,0 +1,215 @@ +use std::sync::Arc; +use std::time::Duration; +use std::{collections::HashMap, net::TcpStream}; + +use crate::{ + data::{ + communication::DataTypes, + user::{User, UserStatus}, + }, + rho::{self, rho_manager}, + util::config_util::CONFIG, +}; +use axum::Json; +use dashmap::DashMap; +use futures::{SinkExt, StreamExt}; +use json::JsonValue; +use once_cell::sync::Lazy; +use tokio::sync::Mutex; +use tokio::time::sleep; +use tokio_tungstenite::{ + MaybeTlsStream, WebSocketStream, connect_async, tungstenite::protocol::Message, +}; +use tungstenite::Utf8Bytes; +use uuid::Uuid; + +use crate::data::communication::{CommunicationType, CommunicationValue}; + +static WAITING_TASKS: Lazy bool + Send + Sync>>> = + Lazy::new(DashMap::new); + +pub struct OmegaConnection { + ws_stream: Arc>>>, +} + +impl OmegaConnection { + pub fn new() -> Self { + OmegaConnection { + ws_stream: Arc::new(Mutex::new(None)), + } + } + + pub async fn connect(&self) { + self.connect_internal(0).await; + } + async fn connect_internal(&self, mut retry: usize) { + let uri = "wss://tensamin.methanium.net/ws/omega/"; + + loop { + if retry > 20 { + eprintln!("Max retry attempts reached, giving up."); + return; + } + + match connect_async(uri).await { + Ok((ws_stream, _)) => { + let mut guard = Some(ws_stream); + + // Send IDENTIFICATION + let identify_msg = CommunicationValue::new(CommunicationType::identification) + .add_data( + DataTypes::uuid, + JsonValue::String(CONFIG.read().unwrap().omikron_id.to_string()), + ); + self.send_message(&identify_msg).await; + + // Spawn reader loop + let ws_stream_clone = self.ws_stream.clone(); + tokio::spawn(async move { + OmegaConnection::read_loop(ws_stream_clone).await; + }); + + break; // success, exit the loop + } + Err(e) => { + eprintln!("WebSocket connection failed (attempt {}): {}", retry, e); + retry += 1; + sleep(Duration::from_secs(2)).await; + continue; + } + } + } + } + + async fn read_loop( + ws_stream: Arc>>>, + ) { + loop { + let mut lock = ws_stream.lock().await; + let Some(ws) = lock.as_mut() else { + break; + }; + + match ws.next().await { + Some(Ok(Message::Text(msg))) => { + let mut cv = CommunicationValue::from_json(&msg); + let msg_id = cv.get_id(); + + // Handle waiting tasks + if let Some(task) = WAITING_TASKS.remove(&msg_id) { + if (task.1)(cv.clone()) { + continue; + } + } + + // Handle CLIENT_CHANGED + if cv.is_type(CommunicationType::client_changed) { + let iota_id = Uuid::parse_str( + cv.get_data(DataTypes::iota_id).unwrap().as_str().unwrap(), + ) + .unwrap(); + let user_id = Uuid::parse_str( + cv.get_data(DataTypes::user_id).unwrap().as_str().unwrap(), + ) + .unwrap(); + let status_str = cv + .get_data(DataTypes::user_state) + .unwrap() + .as_str() + .unwrap(); + let status = UserStatus::from_string(&status_str) + .unwrap_or(UserStatus::iota_offline); + + let user = User::new(iota_id, user_id, status); + for rho_con in rho_manager::get_all_connections().await { + rho_con.are_they_interested(&user); + } + } + } + Some(Ok(Message::Close(_))) | None => { + OmegaConnection::reconnect(); + break; + } + Some(Err(_)) => { + OmegaConnection::reconnect(); + break; + } + _ => {} + } + } + } + + async fn reconnect() { + let conn = OmegaConnection::new(); + conn.connect().await; + } + + pub async fn send_message(&self, cv: &CommunicationValue) { + let mut guard = self.ws_stream.lock().await; + if let Some(ws) = guard.as_mut() { + let _ = ws + .send(Message::Text(Utf8Bytes::from(cv.to_json().to_string()))) + .await; + } + } + + pub async fn connect_iota(iota_id: Uuid, user_ids: Vec) { + let user_ids_str = user_ids + .iter() + .map(|id| id.to_string()) + .collect::>() + .join(","); + let cv = CommunicationValue::new(CommunicationType::iota_connected) + .add_data(DataTypes::iota_id, JsonValue::from(iota_id.to_string())) + .add_data(DataTypes::user_ids, JsonValue::from(user_ids_str)); + OmegaConnection::send_global(cv).await; + } + + pub async fn close_iota(iota_id: Uuid) { + let cv = CommunicationValue::new(CommunicationType::iota_closed) + .add_data(DataTypes::iota_id, JsonValue::from(iota_id.to_string())); + OmegaConnection::send_global(cv).await; + } + + pub async fn client_changed(iota_id: Uuid, user_id: Uuid, state: UserStatus) { + let cv = CommunicationValue::new(CommunicationType::client_changed) + .add_data(DataTypes::iota_id, JsonValue::from(iota_id.to_string())) + .add_data(DataTypes::user_id, JsonValue::from(user_id.to_string())) + .add_data(DataTypes::user_state, JsonValue::from(state.to_string())); + OmegaConnection::send_global(cv).await; + } + + pub async fn user_states(user_id: Uuid, user_ids: Vec) { + let user_ids_str = user_ids + .iter() + .map(|id| id.to_string()) + .collect::>() + .join(","); + let cv = CommunicationValue::new(CommunicationType::get_states) + .add_data(DataTypes::user_ids, JsonValue::from(user_ids_str)); + let msg_id = cv.get_id(); + + WAITING_TASKS.insert( + msg_id, + Box::new(move |response: CommunicationValue| { + Box::pin(async move |response2: CommunicationValue| { + let rho = rho_manager::get_rho_con_for_user(user_id).await; + if let Some(rho) = rho { + for client in rho.get_client_connections_for_user(user_id).await { + client.send_message(&response).await; + } + } + true + }); + true + }), + ); + + OmegaConnection::send_global(cv).await; + } + + async fn send_global(cv: CommunicationValue) { + let conn = OmegaConnection::new(); + conn.send_message(&cv).await; + } +} diff --git a/src/rho/client_connection.rs b/src/rho/client_connection.rs new file mode 100644 index 0000000..170e4d0 --- /dev/null +++ b/src/rho/client_connection.rs @@ -0,0 +1,515 @@ +use futures::SinkExt; +use http::header::AUTHORIZATION; +use std::{ + collections::HashMap, + sync::{Arc, Weak}, +}; +use tokio::sync::{Mutex, RwLock}; +use tokio_tungstenite::{WebSocketStream, tungstenite::Message}; +use tungstenite::Utf8Bytes; +use uuid::Uuid; + +use super::{rho_connection::RhoConnection, rho_manager}; +use crate::{ + auth::auth_connector, + // calls::call_manager::CallManager, + data::{ + communication::{CommunicationType, CommunicationValue, DataTypes}, + user::{User, UserStatus}, + }, + omega::omega_connection::OmegaConnection, +}; + +/// ClientConnection represents a WebSocket connection from a client device +pub struct ClientConnection { + /// WebSocket session + session: Arc>>, + /// User ID associated with this client + user_id: Arc>>, + /// Whether this connection has been identified/authenticated + identified: Arc>, + /// Ping latency tracking + ping: Arc>, + /// Weak reference to RhoConnection to avoid circular references + rho_connection: Arc>>>, + /// List of user IDs this client is interested in receiving updates about + interested_users: Arc>>, +} + +impl ClientConnection { + /// Create a new ClientConnection + pub fn new(session: WebSocketStream) -> Arc { + Arc::new(Self { + session: Arc::new(Mutex::new(session)), + user_id: Arc::new(RwLock::new(None)), + identified: Arc::new(RwLock::new(false)), + ping: Arc::new(RwLock::new(-1)), + rho_connection: Arc::new(RwLock::new(None)), + interested_users: Arc::new(RwLock::new(Vec::new())), + }) + } + + /// Get the user ID + pub async fn get_user_id(&self) -> Option { + *self.user_id.read().await + } + + /// Check if connection is identified + pub async fn is_identified(&self) -> bool { + *self.identified.read().await + } + + /// Get current ping + pub async fn get_ping(&self) -> i64 { + *self.ping.read().await + } + + /// Set the RhoConnection reference + pub async fn set_rho_connection(&self, rho_connection: Weak) { + let mut rho_ref = self.rho_connection.write().await; + *rho_ref = Some(rho_connection); + } + + /// Get RhoConnection if available + pub async fn get_rho_connection(&self) -> Option> { + let rho_ref = self.rho_connection.read().await; + if let Some(weak_ref) = rho_ref.as_ref() { + weak_ref.upgrade() + } else { + None + } + } + + /// Send a string message to the client + pub async fn send_message_str(&self, message: &str) { + let mut session = self.session.lock().await; + if let Err(e) = session + .send(Message::Text(Utf8Bytes::from(message.to_string()))) + .await + { + eprintln!("Failed to send message to client: {}", e); + } + } + + /// Send a CommunicationValue to the client + pub async fn send_message(&self, cv: &CommunicationValue) { + self.send_message_str(&cv.to_json().to_string()).await; + } + + /// Handle incoming message from client + pub async fn handle_message(self: Arc, message: String) { + let cv = CommunicationValue::from_json(&message); + + // Handle identification + if cv.is_type(CommunicationType::identification) && !self.is_identified().await { + self.handle_identification(Arc::clone(&self), cv).await; + return; + } + + if !self.is_identified().await { + return; + } + + // Handle ping + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + + // Handle client status changes + if cv.is_type(CommunicationType::client_changed) { + self.handle_client_changed(cv).await; + return; + } + + // Handle call invites + if cv.is_type(CommunicationType::call_invite) { + self.handle_call_invite(cv).await; + return; + } + + // Handle get call requests + if cv.is_type(CommunicationType::get_call) { + self.handle_get_call(cv).await; + return; + } + + // Forward other messages to Iota + self.forward_to_iota(cv).await; + } + + /// Handle identification message + async fn handle_identification(&self, sarc: Arc, mut cv: CommunicationValue) { + // Extract user ID + let user_id = match cv.get_data(DataTypes::user_id) { + Some(id_str) => match Uuid::parse_str(&id_str.to_string()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }, + None => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + // Find RhoConnection for this user + let rho_connection = match rho_manager::get_rho_con_for_user(user_id).await { + Some(rho) => rho, + None => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + // Validate private key + if let Some(private_key_hash) = cv.get_data(DataTypes::private_key_hash) { + let is_valid = + auth_connector::is_private_key_valid(user_id, &private_key_hash.to_string()).await; + + if !is_valid { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + + // Set identification data + { + let mut user_id_guard = self.user_id.write().await; + *user_id_guard = Some(user_id); + } + { + let mut identified_guard = self.identified.write().await; + *identified_guard = true; + } + + // Set up RhoConnection reference + self.set_rho_connection(Arc::downgrade(&rho_connection)) + .await; + + // Add this client to the RhoConnection + rho_connection.add_client_connection(Arc::from(sarc)).await; + + // Send success response + let response = CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + + /// Handle ping message + async fn handle_ping(&self, mut cv: CommunicationValue) { + // Update our ping if provided + if let Some(last_ping) = cv.get_data(DataTypes::last_ping) { + if let Ok(ping_val) = last_ping.to_string().parse::() { + let mut ping_guard = self.ping.write().await; + *ping_guard = ping_val; + } + } + + // Get Iota ping from RhoConnection + let iota_ping = if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.get_iota_connection().get_ping().await + } else { + -1 + }; + + // Send pong response + let response = CommunicationValue::new(CommunicationType::pong) + .with_id(cv.get_id()) + .add_data_str(DataTypes::ping_iota, iota_ping.to_string()); + + self.send_message(&response).await; + } + + /// Handle client status change + async fn handle_client_changed(&self, mut cv: CommunicationValue) { + if let Some(user_id) = self.get_user_id().await { + if let Some(status_str) = cv.get_data(DataTypes::user_state) { + // Parse user status - this would need to be implemented properly + let user_status = UserStatus::online; // placeholder + if let Some(rho_conn) = self.get_rho_connection().await { + OmegaConnection::client_changed( + rho_conn.get_iota_id().await, + user_id, + user_status, + ); + } + } + } + } + + /// Handle call invite + async fn handle_call_invite(&self, mut cv: CommunicationValue) { + let receiver_id = match cv.get_data(DataTypes::receiver_id) { + Some(id_str) => match Uuid::parse_str(&id_str.to_string()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }, + None => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + let call_id = match cv.get_data(DataTypes::call_id) { + Some(id_str) => match Uuid::parse_str(&id_str.to_string()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }, + None => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + let call_secret_sha = cv + .get_data(DataTypes::call_secret_sha) + .map(|s| s.to_string()); + let call_secret = cv.get_data(DataTypes::call_secret).map(|s| s.to_string()); + + if call_secret_sha.is_none() || call_secret.is_none() { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + + // Get call group - placeholder implementation + // let call_group = CallManager::get_call_group(call_id, &call_secret_sha.unwrap(), true).await; + // if call_group.is_none() { + // self.send_error_response(&cv.get_id(), CommunicationType::error) + // .await; + // return; + // } + + // Find target RhoConnection + let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { + Some(rho) => rho, + None => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + // Get sender user ID + let sender_id = match self.get_user_id().await { + Some(id) => id, + None => return, + }; + + // Create and send call distribution message + let distribute = CommunicationValue::new(CommunicationType::new_call) + .with_receiver(receiver_id) + .with_sender(sender_id) + .add_data_str(DataTypes::call_id, call_id.to_string()) + .add_data_str(DataTypes::receiver_id, receiver_id.to_string()) + .add_data_str(DataTypes::sender_id, sender_id.to_string()) + .add_data_str(DataTypes::call_secret, call_secret.unwrap()); + + target_rho.message_iota_to_client(distribute).await; + + // Handle call group invitation logic here + // This would require implementing CallGroup::Caller and related functionality + + // Send success response + let response = CommunicationValue::new(CommunicationType::call_invite).with_id(cv.get_id()); + self.send_message(&response).await; + } + + /// Handle get call request + async fn handle_get_call(&self, mut cv: CommunicationValue) { + let user_id = match self.get_user_id().await { + Some(id) => id, + None => return, + }; + + let call_id = match cv.get_data(DataTypes::call_id) { + Some(id_str) => match Uuid::parse_str(&id_str.to_string()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }, + None => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + let call_secret_sha = cv + .get_data(DataTypes::call_secret_sha) + .map(|s| s.to_string()); + + if call_secret_sha.is_none() { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + + // Get call group + let mut response = CommunicationValue::new(CommunicationType::get_call) + .with_id(cv.get_id()) + .with_receiver(user_id); + + // Placeholder call group logic + // if let Some(call_group) = CallManager::get_call_group(call_id, &call_secret_sha.unwrap(), true).await { + // response = response + // .add_data_str(DataTypes::call_state, call_group.call_state.to_string()) + // .add_data_str(DataTypes::start_date, call_group.started_at.to_string()); + // + // if call_group.ended_at != 0 { + // response = response.add_data_str(DataTypes::end_date, call_group.ended_at.to_string()); + // } + // } else { + response = response.add_data_str(DataTypes::call_state, "DESTROYED".to_string()); + // } + + self.send_message(&response).await; + } + + /// Forward message to Iota + async fn forward_to_iota(&self, mut cv: CommunicationValue) { + if let Some(user_id) = self.get_user_id().await { + if let Some(rho_conn) = self.get_rho_connection().await { + let updated_cv = cv.with_sender(user_id); + rho_conn.message_to_iota(updated_cv).await; + } + } + } + + /// Send error response + async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { + let error = CommunicationValue::new(error_type).with_id(*message_id); + self.send_message(&error).await; + } + + /// Close the connection + pub async fn close(&self) { + let mut session = self.session.lock().await; + let _ = session.close(None).await; + } + + /// Set interested users list + pub async fn set_interested_users(&self, interested_ids: Vec) { + let mut interested_guard = self.interested_users.write().await; + *interested_guard = interested_ids; + } + + /// Check if interested in a user and send notification + pub async fn are_you_interested(&self, user: &User) { + let interested_guard = self.interested_users.read().await; + if interested_guard.contains(&user.user_id) { + let notification = CommunicationValue::new(CommunicationType::client_changed) + .add_data_str(DataTypes::user_id, user.user_id.to_string()) + .add_data_str( + DataTypes::user_state, + format!("{:?}", user.status.to_string()), + ); + + self.send_message(¬ification).await; + } + } + + /// Handle connection close + pub async fn handle_close(&self) { + if self.is_identified().await { + if let Some(user_id) = self.get_user_id().await { + if let Some(rho_conn) = rho_manager::get_rho_con_for_user(user_id).await { + rho_conn + .close_client_connection(Arc::new(self.clone())) + .await; + } + } + } + } +} + +// Implement Clone to make it easier to work with Arc +impl Clone for ClientConnection { + fn clone(&self) -> Self { + Self { + session: Arc::clone(&self.session), + user_id: Arc::clone(&self.user_id), + identified: Arc::clone(&self.identified), + ping: Arc::clone(&self.ping), + rho_connection: Arc::clone(&self.rho_connection), + interested_users: Arc::clone(&self.interested_users), + } + } +} + +impl std::fmt::Debug for ClientConnection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ClientConnection") + .field("user_id", &"[async]") + .field("identified", &"[async]") + .field("ping", &"[async]") + .finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tokio::sync::Mutex; + + #[tokio::test] + async fn test_client_connection_creation() { + // Mock session - in real implementation this would be a proper WebSocket stream + let mock_session = tokio_tungstenite::WebSocketStream::from_raw_socket( + tokio::net::TcpStream::connect("127.0.0.1:0") + .await + .unwrap_or_else(|_| { + // This is just for testing, create a dummy stream + panic!("Cannot create test stream") + }), + tokio_tungstenite::tungstenite::protocol::Role::Client, + None, + ) + .await; + + // This test would fail in practice due to the mock stream + // but shows the intended API + // let client_conn = ClientConnection::new(mock_session); + + // assert!(!client_conn.is_identified().await); + // assert_eq!(client_conn.get_ping().await, -1); + // assert!(client_conn.get_user_id().await.is_none()); + } + + #[tokio::test] + async fn test_interested_users() { + // This would also need a proper mock setup + // but shows the intended functionality + + // let client_conn = ClientConnection::new(mock_session); + // let user_ids = vec![Uuid::new_v4(), Uuid::new_v4()]; + + // client_conn.set_interested_users(user_ids.clone()).await; + // + // // Test would verify that the interested users are stored correctly + } +} diff --git a/src/rho/iota_connection.rs b/src/rho/iota_connection.rs new file mode 100644 index 0000000..3529045 --- /dev/null +++ b/src/rho/iota_connection.rs @@ -0,0 +1,345 @@ +use futures::SinkExt; +use json::{JsonValue, number::Number}; +use std::{ + collections::HashMap, + sync::{Arc, Weak}, +}; +use tokio::sync::{Mutex, RwLock}; +use tokio_tungstenite::{WebSocketStream, tungstenite::Message}; +use tungstenite::Utf8Bytes; +use uuid::Uuid; + +use super::{rho_connection::RhoConnection, rho_manager}; +use crate::{ + auth::auth_connector, + // calls::call_manager::CallManager, + data::{ + communication::{CommunicationType, CommunicationValue, DataTypes}, + user::User, + }, + omega::omega_connection::OmegaConnection, +}; + +/// IotaConnection represents a WebSocket connection from an Iota device +pub struct IotaConnection { + session: Arc>>, + iota_id: Arc>, + user_ids: Arc>>, + identified: Arc>, + ping: Arc>, + rho_connection: Arc>>>, +} + +impl IotaConnection { + /// Create a new IotaConnection + pub fn new(session: WebSocketStream) -> Arc { + Arc::new(Self { + session: Arc::new(Mutex::new(session)), + iota_id: Arc::new(RwLock::new(Uuid::nil())), + user_ids: Arc::new(RwLock::new(Vec::new())), + identified: Arc::new(RwLock::new(false)), + ping: Arc::new(RwLock::new(0)), + rho_connection: Arc::new(RwLock::new(None)), + }) + } + + /// Create with known IDs (for testing) + pub fn new_with_ids( + iota_id: Uuid, + user_ids: Vec, + session: Arc>>, + ) -> Arc { + Arc::new(Self { + session, + iota_id: Arc::new(RwLock::new(iota_id)), + user_ids: Arc::new(RwLock::new(user_ids)), + identified: Arc::new(RwLock::new(true)), + ping: Arc::new(RwLock::new(0)), + rho_connection: Arc::new(RwLock::new(None)), + }) + } + + /// Get the Iota ID + pub async fn get_iota_id(&self) -> Uuid { + *self.iota_id.read().await + } + + /// Get the user IDs + pub async fn get_user_ids(&self) -> Vec { + self.user_ids.read().await.clone() + } + + /// Check if connection is identified + pub async fn is_identified(&self) -> bool { + *self.identified.read().await + } + + /// Get current ping + pub async fn get_ping(&self) -> i64 { + *self.ping.read().await + } + + /// Set the RhoConnection reference + pub async fn set_rho_connection(&self, rho_connection: Weak) { + let mut rho_ref = self.rho_connection.write().await; + *rho_ref = Some(rho_connection); + } + + /// Get RhoConnection if available + pub async fn get_rho_connection(&self) -> Option> { + let rho_ref = self.rho_connection.read().await; + if let Some(weak_ref) = rho_ref.as_ref() { + weak_ref.upgrade() + } else { + None + } + } + + /// Send a message to the Iota + pub async fn send_message_str(&self, message: &str) { + let mut session = self.session.lock().await; + let _ = session + .send(Message::Text(Utf8Bytes::from(message.to_string()))) + .await; + } + + /// Send a CommunicationValue to the Iota + pub async fn send_message(&self, cv: CommunicationValue) { + self.send_message_str(&cv.to_json().to_string()).await; + } + + /// Handle incoming message from Iota + pub async fn handle_message(self: Arc, message: String) { + let cv = CommunicationValue::from_json(&message); + + // Handle identification + if cv.is_type(CommunicationType::identification) && !self.is_identified().await { + self.handle_identification(cv).await; + return; + } + + if !self.is_identified().await { + return; + } + + // Handle ping + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + + // Handle forwarding to other Iotas or clients + let receiver_id = cv.get_receiver(); + if !self.get_user_ids().await.contains(&receiver_id) + || cv.is_type(CommunicationType::message_other_iota) + || cv.is_type(CommunicationType::send_chat) + { + self.handle_forward_message(cv).await; + return; + } + + // Handle GET_CHATS + if cv.is_type(CommunicationType::get_chats) { + self.handle_get_chats(cv).await; + return; + } + + // Forward to client + self.forward_to_client(cv).await; + } + + /// Handle identification message + async fn handle_identification(self: Arc, mut cv: CommunicationValue) { + // Parse Iota ID + let iota_id = match cv.get_data(DataTypes::iota_id) { + Some(id_str) => match Uuid::parse_str(&id_str.to_string()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(&cv.get_id()).await; + return; + } + }, + None => { + self.send_error_response(&cv.get_id()).await; + return; + } + }; + + // Parse user IDs + let mut validated_user_ids = Vec::new(); + if let Some(user_ids_str) = cv.get_data(DataTypes::user_ids) { + for id_str in user_ids_str.to_string().split(',') { + if id_str.is_empty() { + continue; + } + if let Ok(user_id) = Uuid::parse_str(id_str.trim()) { + let auth_iota_id = auth_connector::get_iota_id(user_id).await.unwrap(); + if auth_iota_id == iota_id { + validated_user_ids.push(user_id); + } + } + } + } + + // Set identification data + { + let mut iota_id_guard = self.iota_id.write().await; + *iota_id_guard = iota_id; + } + { + let mut user_ids_guard = self.user_ids.write().await; + *user_ids_guard = validated_user_ids.clone(); + } + { + let mut identified_guard = self.identified.write().await; + *identified_guard = true; + } + + // Check for existing connection and close it + if rho_manager::contains_iota(iota_id).await { + if let Some(existing_rho) = rho_manager::get_rho_by_iota(iota_id).await { + existing_rho.close_iota_connection().await; + } + } + + // Create RhoConnection + let rho_connection = + Arc::new(RhoConnection::new(self.clone(), validated_user_ids.clone()).await); + + // Set up bidirectional reference + self.set_rho_connection(Arc::downgrade(&rho_connection)) + .await; + + // Add to manager + rho_manager::add_rho(rho_connection).await; + + // Send response + let response = CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data_str(DataTypes::accepted, validated_user_ids.len().to_string()); + + self.send_message(response).await; + } + + /// Handle ping message + async fn handle_ping(&self, mut cv: CommunicationValue) { + // Update our ping if provided + if let Some(last_ping) = cv.get_data(DataTypes::last_ping) { + if let Ok(ping_val) = last_ping.to_string().parse::() { + let mut ping_guard = self.ping.write().await; + *ping_guard = ping_val; + } + } + + // Collect client pings + let client_pings = if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.get_client_pings().await + } else { + HashMap::new() + }; + + // Send pong response + let pings = client_pings + .into_iter() + .map(|(k, v)| (k, JsonValue::String(v.to_string()))) + .collect(); + let response = CommunicationValue::new(CommunicationType::pong) + .with_id(cv.get_id()) + .add_data(DataTypes::ping_clients, JsonValue::Object(pings)); + self.send_message(response).await; + } + + /// Handle message forwarding to other Iotas + async fn handle_forward_message(&self, cv: CommunicationValue) { + let receiver_id = cv.get_receiver(); + // Validate sender if present + let sender_id = cv.get_sender(); + if !self.get_user_ids().await.contains(&sender_id) { + self.send_error_response(&cv.get_id()).await; + return; + } + + // Find target RhoConnection + if let Some(target_rho) = rho_manager::get_rho_con_for_user(receiver_id).await { + target_rho.message_to_iota(cv).await; + } else { + // Send error if target not found + let error = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .with_sender(cv.get_sender()); + self.send_message(error).await; + } + } + + /// Handle GET_CHATS message + async fn handle_get_chats(&self, mut cv: CommunicationValue) { + let receiver_id = cv.get_receiver(); + let mut interested_ids: Vec = Vec::new(); + + // Process contacts and add call information + if let Some(contacts_data) = cv.get_data(DataTypes::user_ids) { + // Parse contacts JSON array and enrich with call data + // This would need proper JSON parsing implementation + // For now, placeholder logic: + + // Extract user IDs from contacts + // let contacts: Vec = parse_contacts(contacts_data); + // for contact in &contacts { + // interested_ids.push(contact.user_id); + // } + + // Get call invites for receiver + // let invites = CallManager::get_call_invites(receiver_id).await; + + // Enrich contacts with call information + // let enriched_contacts = enrich_with_calls(contacts, invites); + + // cv = cv.add_data(DataTypes::user_ids, enriched_contacts.into()); + } + + // Notify OmegaConnection about user states + OmegaConnection::user_states(receiver_id, interested_ids.clone()); + + // Set interested users in RhoConnection + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.set_interested(receiver_id, interested_ids).await; + } + + // Forward to client + self.forward_to_client(cv).await; + } + + /// Forward message to client + async fn forward_to_client(&self, cv: CommunicationValue) { + if let Some(rho_conn) = self.get_rho_connection().await { + let updated_cv = cv.with_sender(self.get_iota_id().await); + let receiver_id = updated_cv.get_receiver(); + rho_conn.message_iota_to_client(updated_cv).await; + } + } + + /// Send error response + async fn send_error_response(&self, message_id: &Uuid) { + let error = CommunicationValue::new(CommunicationType::error).with_id(*message_id); + self.send_message(error).await; + } + + /// Handle connection close + pub async fn handle_close(&self) { + if self.is_identified().await { + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.close_iota_connection().await; + } + } + } +} + +impl std::fmt::Debug for IotaConnection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IotaConnection") + .field("iota_id", &"[async]") + .field("identified", &"[async]") + .field("ping", &"[async]") + .finish() + } +} diff --git a/src/rho/mod.rs b/src/rho/mod.rs new file mode 100644 index 0000000..99067bc --- /dev/null +++ b/src/rho/mod.rs @@ -0,0 +1,21 @@ +//! Rho module - Connection management between Iota and Client connections +//! +//! This module implements the Rho connection management system that maps +//! single Iota connections to multiple Client connections, providing +//! bidirectional communication capabilities. + +pub mod client_connection; +pub mod iota_connection; +pub mod rho_connection; +pub mod rho_manager; + +// Re-export commonly used types for convenience +pub use client_connection::ClientConnection; +pub use iota_connection::IotaConnection; +pub use rho_connection::RhoConnection; + +// Re-export key manager functions +pub use rho_manager::{ + add_rho, connection_count, contains_iota, get_all_connections, get_rho_by_iota, + get_rho_con_for_user, remove_rho, +}; diff --git a/src/rho/rho_connection.rs b/src/rho/rho_connection.rs new file mode 100644 index 0000000..2b6bca6 --- /dev/null +++ b/src/rho/rho_connection.rs @@ -0,0 +1,251 @@ +use std::collections::{self, HashMap}; +use std::sync::{Arc, Weak}; +use tokio::sync::RwLock; +use uuid::Uuid; + +use super::{client_connection::ClientConnection, iota_connection::IotaConnection, rho_manager}; +use crate::data::{ + communication::{CommunicationType, CommunicationValue, DataTypes}, + user::UserStatus, +}; +use crate::omega::omega_connection::OmegaConnection; + +pub struct RhoConnection { + iota_connection: Arc, + user_ids: Vec, + client_connections: Arc>>>, +} + +impl RhoConnection { + /// Create a new RhoConnection + pub async fn new(iota_connection: Arc, user_ids: Vec) -> Self { + let rho_connection = Self { + iota_connection, + user_ids: user_ids.clone(), + client_connections: Arc::new(RwLock::new(Vec::new())), + }; + + // Notify OmegaConnection about the new Iota + OmegaConnection::connect_iota(rho_connection.get_iota_id().await, user_ids); + + rho_connection + } + + /// Get the Iota ID + pub async fn get_iota_id(&self) -> Uuid { + self.iota_connection.get_iota_id().await + } + + /// Get the user IDs associated with this Rho connection + pub fn get_user_ids(&self) -> &Vec { + &self.user_ids + } + + /// Get reference to the IotaConnection + pub fn get_iota_connection(&self) -> &Arc { + &self.iota_connection + } + + /// Get all client connections + pub async fn get_client_connections(&self) -> Vec> { + let connections = self.client_connections.read().await; + connections.clone() + } + + /// Get client connections for a specific user + pub async fn get_client_connections_for_user( + &self, + user_id: Uuid, + ) -> Vec> { + let connections = self.client_connections.read().await; + let mut collections = Vec::new(); + for con in connections.iter() { + if con.get_user_id().await.unwrap() == user_id { + collections.push(con.clone()); + } + } + collections + } + + /// Add a client connection + pub async fn add_client_connection(&self, connection: Arc) { + // Notify Iota about new client + let notification = CommunicationValue::new(CommunicationType::client_connected) + .add_data_str( + DataTypes::user_id, + connection + .get_user_id() + .await + .unwrap_or(Uuid::nil()) + .to_string(), + ); + + self.iota_connection.send_message(notification).await; + + // Add to our list + { + let mut connections = self.client_connections.write().await; + connections.push(Arc::clone(&connection)); + } + + // Notify OmegaConnection + OmegaConnection::client_changed( + self.get_iota_id().await, + connection.get_user_id().await.unwrap_or(Uuid::nil()), + UserStatus::online, + ); + } + + /// Remove a client connection + pub async fn close_client_connection(&self, connection: Arc) { + { + let mut connections = self.client_connections.write().await; + + let target_user_id = connection.get_user_id().await.unwrap(); + + connections.retain(|con| { + futures::executor::block_on(async { + con.get_user_id().await.unwrap() != target_user_id + }) + }); + + // Push the new connection + connections.push(Arc::clone(&connection)); + } + + // Notify OmegaConnection + OmegaConnection::client_changed( + self.get_iota_id().await, + connection.get_user_id().await.unwrap_or(Uuid::nil()), + UserStatus::user_offline, + ); + } + + /// Close the Iota connection and all associated client connections + pub async fn close_iota_connection(&self) { + // Close all client connections + let connections = self.get_client_connections().await; + for connection in connections { + connection.close().await; + } + + // Remove from manager + rho_manager::remove_rho(self.get_iota_id().await).await; + + // Notify OmegaConnection + OmegaConnection::close_iota(self.get_iota_id().await); + } + + /// Send message from Iota to specific client by user ID + pub async fn message_iota_to_client_by_user(&self, user_id: Uuid, message: &str) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + if let Some(conn_user_id) = connection.get_user_id().await { + if conn_user_id == user_id { + connection.send_message_str(message).await; + } + } + } + } + + /// Send message from Iota to specific client + pub async fn message_iota_to_client(&self, cv: CommunicationValue) { + if let Some(receiver_id) = Some(cv.get_receiver()) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + if let Some(conn_user_id) = connection.get_user_id().await { + if conn_user_id == receiver_id { + connection.send_message(&cv).await; + } + } + } + } + } + + /// Send message to Iota as string + pub async fn message_to_iota_str(&self, message: &str) { + self.iota_connection.send_message_str(message).await; + } + + /// Send message to Iota + pub async fn message_to_iota(&self, cv: CommunicationValue) { + self.iota_connection.send_message(cv).await; + } + + /// Set interested users for a specific client + pub async fn set_interested(&self, user_id: Uuid, interested_ids: Vec) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + if let Some(conn_user_id) = connection.get_user_id().await { + if conn_user_id == user_id { + connection + .set_interested_users(interested_ids.clone()) + .await; + break; + } + } + } + } + + /// Check if clients are interested in a user + pub async fn are_they_interested(&self, user: &crate::data::user::User) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + connection.are_you_interested(user).await; + } + } + + /// Get ping information for all clients + pub async fn get_client_pings(&self) -> HashMap { + let connections = self.client_connections.read().await; + let mut pings = HashMap::new(); + + for connection in connections.iter() { + if let Some(user_id) = connection.get_user_id().await { + pings.insert(user_id.to_string(), connection.get_ping().await); + } + } + + pings + } + + /// Check if this RhoConnection contains a specific user ID + pub fn contains_user(&self, user_id: &Uuid) -> bool { + self.user_ids.contains(user_id) + } + + /// Get count of active client connections + pub async fn client_count(&self) -> usize { + let connections = self.client_connections.read().await; + connections.len() + } +} +#[cfg(test)] +mod tests { + use super::*; + use tokio::sync::Mutex; + + #[tokio::test] + async fn test_rho_connection_creation() { + let iota_id = Uuid::new_v4(); + let user_ids = vec![Uuid::new_v4(), Uuid::new_v4()]; + + // Mock session + // Create a mock WebSocket stream (this would fail in practice but shows the API) + // In real implementation, this would be a proper WebSocketStream + let mock_stream = + std::ptr::null_mut() as *mut tokio_tungstenite::WebSocketStream; + let mock_stream = unsafe { std::ptr::read(mock_stream) }; + let iota_conn = IotaConnection::new_with_ids( + iota_id, + user_ids.clone(), + Arc::new(tokio::sync::Mutex::new(mock_stream)), + ); + + let rho_conn = RhoConnection::new(iota_conn, user_ids.clone()).await; + + assert_eq!(rho_conn.get_iota_id().await, iota_id); + assert_eq!(rho_conn.get_user_ids(), &user_ids); + assert_eq!(rho_conn.client_count().await, 0); + } +} diff --git a/src/rho/rho_manager.rs b/src/rho/rho_manager.rs new file mode 100644 index 0000000..c6cb0c4 --- /dev/null +++ b/src/rho/rho_manager.rs @@ -0,0 +1,93 @@ +use std::{ + collections::HashMap, + sync::{Arc, LazyLock}, +}; +use tokio::sync::RwLock; +use uuid::Uuid; + +use super::rho_connection::RhoConnection; + +// Static storage for RhoConnections, keyed by Iota ID +pub static RHO_CONNECTIONS: LazyLock>>>> = + LazyLock::new(|| Arc::new(RwLock::new(HashMap::new()))); + +/// Get a RhoConnection for a specific user ID +pub async fn get_rho_con_for_user(user_id: Uuid) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + for rho_connection in connections.values() { + if rho_connection.get_user_ids().contains(&user_id) { + return Some(Arc::clone(rho_connection)); + } + } + None +} + +/// Check if an Iota ID exists in the connections +pub async fn contains_iota(iota_id: Uuid) -> bool { + let connections = RHO_CONNECTIONS.read().await; + connections.contains_key(&iota_id) +} + +/// Remove a RhoConnection by Iota ID +pub async fn remove_rho(iota_id: Uuid) -> Option> { + let mut connections = RHO_CONNECTIONS.write().await; + connections.remove(&iota_id) +} + +/// Add a RhoConnection to the manager +pub async fn add_rho(rho_connection: Arc) { + let mut connections = RHO_CONNECTIONS.write().await; + let iota_id = rho_connection.get_iota_id().await; + connections.insert(iota_id, rho_connection); +} + +/// Get a RhoConnection by Iota ID directly +pub async fn get_rho_by_iota(iota_id: Uuid) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + connections.get(&iota_id).map(Arc::clone) +} + +/// Get all active RhoConnections +pub async fn get_all_connections() -> Vec> { + let connections = RHO_CONNECTIONS.read().await; + connections.values().map(Arc::clone).collect() +} + +/// Get the count of active connections +pub async fn connection_count() -> usize { + let connections = RHO_CONNECTIONS.read().await; + connections.len() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::rho::iota_connection::IotaConnection; + use std::sync::Arc; + use tokio::sync::Mutex; + + #[tokio::test] + async fn test_add_and_get_rho() { + // Clear any existing connections + { + let mut connections = RHO_CONNECTIONS.write().await; + connections.clear(); + } + + let iota_id = Uuid::new_v4(); + let user_ids = vec![Uuid::new_v4(), Uuid::new_v4()]; + + // Skip this test due to WebSocket complexity - would require proper mock setup + return; + + // This test would need proper WebSocket stream mocking: + // let mock_session = create_mock_websocket_stream(); + // let iota_conn = IotaConnection::new_with_ids(iota_id, user_ids.clone(), mock_session); + // let rho_conn = Arc::new(RhoConnection::new(iota_conn, user_ids.clone())); + + // Test assertions would go here: + // add_rho(Arc::clone(&rho_conn)).await; + // assert!(contains_iota(iota_id).await); + // etc. + } +} diff --git a/src/util/config_util.rs b/src/util/config_util.rs new file mode 100644 index 0000000..e2753da --- /dev/null +++ b/src/util/config_util.rs @@ -0,0 +1,63 @@ +use crate::util::file_util::load_file; +use json::JsonValue; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use std::sync::RwLock; +use uuid::Uuid; +const CONFIG_PATH: &str = ""; +const CONFIG_FILENAME: &str = "config.json"; + +// Define the Config structure +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub omega_server: String, + pub auth_server: String, + pub omikron_id: Uuid, + pub keep_people_stored_for: i32, + pub max_data: u64, + pub ip: String, + pub port: u16, +} + +// Provide default values (similar to Java static defaults) +impl Default for Config { + fn default() -> Self { + Self { + omega_server: "omega.tensamin.methanium.net".into(), + auth_server: "auth.tensamin.methanium.net".into(), + omikron_id: Uuid::parse_str("a9e92dd6-08a6-4765-abf1-9fa39d0a99f9").unwrap(), + keep_people_stored_for: 90, + max_data: 1000 * 1000 * 1000 * 8, + ip: "0.0.0.0".into(), + port: 959, + } + } +} + +// Global instance with thread-safe read/write access +pub static CONFIG: Lazy> = Lazy::new(|| { + let config = Config::load().unwrap_or_default(); + RwLock::new(config) +}); + +impl Config { + pub fn load() -> Option { + let content = load_file(CONFIG_PATH, CONFIG_FILENAME); + if content.trim().is_empty() { + return None; + } + + let json = json::parse(&content).ok()?; + Some(Self { + omega_server: json["omega_server"].as_str().unwrap_or_default().into(), + auth_server: json["auth_server"].as_str().unwrap_or_default().into(), + omikron_id: Uuid::parse_str(json["omikron_id"].as_str().unwrap_or_default()) + .unwrap_or_default(), + keep_people_stored_for: json["keep_people_stored_for"].as_i64().unwrap_or_default() + as i32, + max_data: json["max_data"].as_u64().unwrap_or_default(), + ip: json["ip"].as_str().unwrap_or_default().into(), + port: json["port"].as_u64().unwrap_or_default() as u16, + }) + } +} diff --git a/src/util/file_util.rs b/src/util/file_util.rs new file mode 100644 index 0000000..76e873f --- /dev/null +++ b/src/util/file_util.rs @@ -0,0 +1,165 @@ +use std::ffi::OsStr; +use std::fmt::Write as FmtWrite; +use std::fs::{self, File}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; +use std::process; +use std::time::SystemTime; +use sysinfo::System; +use uuid::Uuid; +use walkdir::WalkDir; + +pub fn delete_file(path: &str, name: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + let file = dir.join(name); + if !file.exists() { + return false; + } + fs::remove_file(file).is_ok() +} + +pub fn delete_directory(path: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + delete_dir_recursive(&dir) +} + +fn delete_dir_recursive(directory: &Path) -> bool { + if !directory.exists() { + return false; + } + if let Err(e) = fs::remove_dir_all(directory) { + println!( + "[IMPORTANT] Couldn't delete directory {}: {}", + directory.display(), + e + ); + return false; + } + true +} + +pub fn delete_user_directory(user_id: Uuid) { + let user_dir = Path::new(&get_directory()) + .join("users") + .join(user_id.to_string()); + let _ = delete_dir_recursive(&user_dir); +} + +pub fn load_file(path: &str, name: &str) -> String { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + if let Err(e) = fs::create_dir_all(&dir) { + println!("[IMPORTANT] Couldn't create directories: {}", e); + return String::new(); + } + return String::new(); + } + + if !file_path.exists() { + if let Err(e) = File::create(&file_path) { + println!("[IMPORTANT] Couldn't create file: {}", e); + } + return String::new(); + } + + let mut content = String::new(); + if let Ok(mut f) = File::open(&file_path) { + let _ = f.read_to_string(&mut content); + } + content +} + +pub fn save_file(path: &str, name: &str, value: &str) { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + if let Err(e) = fs::create_dir_all(&dir) { + println!("[IMPORTANT] Couldn't create directories: {}", e); + return; + } + } + + if let Err(e) = fs::write(&file_path, value) { + println!( + "[IMPORTANT] Couldn't write file {}: {}", + file_path.display(), + e + ); + } +} + +pub fn get_children(path: &str) -> Vec { + let dir = Path::new(&get_directory()).join(path); + let mut children = Vec::new(); + if let Ok(entries) = fs::read_dir(&dir) { + for entry in entries { + if let Ok(entry) = entry { + children.push(entry.file_name().to_string_lossy().to_string()); + } + } + } + children +} + +pub fn get_directory() -> String { + let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from(".")); + exe.parent() + .unwrap_or(Path::new(".")) + .to_string_lossy() + .to_string() +} + +pub fn used_space() -> u64 { + get_directory_size(&PathBuf::from(get_directory())) +} + +pub fn get_directory_size(directory: &Path) -> u64 { + let mut size = 0; + for entry in WalkDir::new(directory).into_iter().filter_map(|e| e.ok()) { + let path = entry.path(); + if path.is_file() { + if let Ok(metadata) = path.metadata() { + size += path.file_name().unwrap_or(OsStr::new("")).len() as u64; + size += metadata.len(); + } + } + } + size +} + +pub fn get_designed_storage(user_id: Uuid) -> String { + let user_dir = Path::new(&get_directory()) + .join("users") + .join(user_id.to_string()); + design_byte(get_directory_size(&user_dir)) +} + +pub fn design_byte(bytes: u64) -> String { + let mut hr_size = format!("{:.2}B", bytes as f64); + let k = bytes as f64 / 1024.0; + let m = k / 1024.0; + let g = m / 1024.0; + let t = g / 1024.0; + + if t >= 1.0 { + hr_size = format!("{:.2}TB", t); + } else if g >= 1.0 { + hr_size = format!("{:.2}GB", g); + } else if m >= 1.0 { + hr_size = format!("{:.2}MB", m); + } else if k >= 1.0 { + hr_size = format!("{:.2}KB", k); + } + hr_size +} + +pub fn get_used_ram() -> String { + let mut sys = System::new_all(); + sys.refresh_all(); + let used = sys.used_memory() * 1024; // kB to bytes + let total = sys.total_memory() * 1024; + format!("{}/{}", design_byte(used), design_byte(total)) +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..5e461b2 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,2 @@ +pub mod config_util; +pub mod file_util;